Skip to content

Commit de06b15

Browse files
author
gersbach
committed
Add GraphQL support for the permission scanner
2 parents 9d4b7b7 + 7fa85d2 commit de06b15

File tree

9 files changed

+405
-204
lines changed

9 files changed

+405
-204
lines changed

Cargo.lock

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ serde = { version = "1.0.197", features = ["derive"] }
1616
serde_json = "1.0.115"
1717
serde_yaml = "0.9.34"
1818
petgraph = "0.6.4"
19-
graphql-parser = "0.4.0"
2019
pretty_assertions = "1.4.0"
2120
indexmap = { version = "2.2.6", features = ["std"] }
2221
regex = "1.10.4"

crates/forge_analyzer/src/checkers.rs

+42-84
Original file line numberDiff line numberDiff line change
@@ -1092,99 +1092,57 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow {
10921092
let mut permissions_within_call: Vec<String> = vec![];
10931093
let intrinsic_func_type = intrinsic_argument.name.unwrap();
10941094

1095+
let (resolver, regex_map) = match intrinsic_func_type {
1096+
IntrinsicName::RequestJiraSoftware => (
1097+
interp.jira_software_permission_resolver,
1098+
interp.jira_software_regex_map,
1099+
),
1100+
IntrinsicName::RequestJiraServiceManagement => (
1101+
interp.jira_service_management_permission_resolver,
1102+
interp.jira_service_management_regex_map,
1103+
),
1104+
IntrinsicName::RequestConfluence => (
1105+
interp.confluence_permission_resolver,
1106+
interp.confluence_regex_map,
1107+
),
1108+
IntrinsicName::RequestJira => {
1109+
(interp.jira_permission_resolver, interp.jira_regex_map)
1110+
}
1111+
IntrinsicName::RequestBitbucket => (
1112+
interp.bitbucket_permission_resolver,
1113+
interp.bitbucket_regex_map,
1114+
),
1115+
_ => unreachable!("Invalid intrinsic function type"),
1116+
};
1117+
10951118
if intrinsic_argument.first_arg.is_none() {
10961119
interp.permissions.drain(..);
10971120
} else {
10981121
intrinsic_argument
10991122
.first_arg
11001123
.iter()
11011124
.for_each(|first_arg_vec| {
1102-
if let Some(second_arg_vec) = intrinsic_argument.second_arg.clone() {
1103-
first_arg_vec.iter().for_each(|first_arg| {
1104-
let first_arg = first_arg.replace(&['\"'][..], "");
1105-
second_arg_vec.iter().for_each(|second_arg| {
1106-
if intrinsic_func_type
1107-
== IntrinsicName::RequestJiraServiceManagement
1108-
{
1109-
let permissions = check_url_for_permissions(
1110-
interp.jira_service_management_permission_resolver,
1111-
interp.jira_service_management_regex_map,
1112-
translate_request_type(Some(second_arg)),
1113-
&first_arg,
1114-
);
1115-
permissions_within_call.extend_from_slice(&permissions)
1116-
} else if intrinsic_func_type
1117-
== IntrinsicName::RequestConfluence
1118-
{
1119-
let permissions = check_url_for_permissions(
1120-
interp.confluence_permission_resolver,
1121-
interp.confluence_regex_map,
1122-
translate_request_type(Some(second_arg)),
1123-
&first_arg,
1124-
);
1125-
permissions_within_call.extend_from_slice(&permissions)
1126-
} else if intrinsic_func_type == IntrinsicName::RequestJira {
1127-
let permissions = check_url_for_permissions(
1128-
interp.jira_permission_resolver,
1129-
interp.jira_regex_map,
1130-
translate_request_type(Some(second_arg)),
1131-
&first_arg,
1132-
);
1133-
permissions_within_call.extend_from_slice(&permissions)
1134-
} else if intrinsic_func_type == IntrinsicName::RequestBitbucket
1135-
{
1136-
let permissions = check_url_for_permissions(
1137-
interp.bitbucket_permission_resolver,
1138-
interp.bitbucket_regex_map,
1139-
translate_request_type(Some(second_arg)),
1140-
&first_arg,
1141-
);
1142-
permissions_within_call.extend_from_slice(&permissions)
1143-
}
1125+
first_arg_vec.iter().for_each(|first_arg| {
1126+
let first_arg = first_arg.replace(&['\"'][..], "");
1127+
let request_types = intrinsic_argument
1128+
.second_arg
1129+
.as_ref()
1130+
.map(|args| {
1131+
args.iter()
1132+
.map(|arg| translate_request_type(Some(arg)))
1133+
.collect::<Vec<_>>()
1134+
.into_iter()
11441135
})
1145-
})
1146-
} else {
1147-
first_arg_vec.iter().for_each(|first_arg| {
1148-
let first_arg = first_arg.replace(&['\"'][..], "");
1149-
if intrinsic_func_type
1150-
== IntrinsicName::RequestJiraServiceManagement
1151-
{
1152-
let permissions = check_url_for_permissions(
1153-
interp.jira_service_management_permission_resolver,
1154-
interp.jira_service_management_regex_map,
1155-
RequestType::Get,
1156-
&first_arg,
1157-
);
1158-
permissions_within_call.extend_from_slice(&permissions)
1159-
} else if intrinsic_func_type == IntrinsicName::RequestConfluence {
1160-
let permissions = check_url_for_permissions(
1161-
interp.confluence_permission_resolver,
1162-
interp.confluence_regex_map,
1163-
RequestType::Get,
1164-
&first_arg,
1165-
);
1166-
permissions_within_call.extend_from_slice(&permissions)
1167-
} else if intrinsic_func_type == IntrinsicName::RequestJira {
1168-
let permissions = check_url_for_permissions(
1169-
interp.jira_permission_resolver,
1170-
interp.jira_regex_map,
1171-
RequestType::Get,
1172-
&first_arg,
1173-
);
1174-
permissions_within_call.extend_from_slice(&permissions)
1175-
} else if intrinsic_func_type == IntrinsicName::RequestBitbucket {
1176-
let permissions = check_url_for_permissions(
1177-
interp.bitbucket_permission_resolver,
1178-
interp.bitbucket_regex_map,
1179-
RequestType::Get,
1180-
&first_arg,
1181-
);
1182-
permissions_within_call.extend_from_slice(&permissions)
1183-
}
1184-
})
1185-
}
1186-
});
1136+
.unwrap_or_else(|| vec![RequestType::Get].into_iter());
11871137

1138+
for req_type in request_types {
1139+
let permissions = check_url_for_permissions(
1140+
resolver, regex_map, req_type, &first_arg,
1141+
);
1142+
permissions_within_call.extend_from_slice(&permissions);
1143+
}
1144+
});
1145+
});
11881146
interp
11891147
.permissions
11901148
.retain(|permissions| !permissions_within_call.contains(permissions));

crates/forge_analyzer/src/definitions.rs

+10-7
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,6 @@ pub fn run_resolver(
146146

147147
// This for loop parses each token of each code statement in the file.
148148
for (curr_mod, module) in modules.iter_enumerated() {
149-
150149
let mut string_collector = StringCollector { strings: vec![] };
151150

152151
module.visit_children_with(&mut string_collector);
@@ -589,7 +588,7 @@ pub struct Environment {
589588
pub defs: Definitions,
590589
default_exports: FxHashMap<ModId, DefId>,
591590
pub resolver: ResolverTable,
592-
pub all_strings: Vec<Atom>,
591+
pub all_strings: Vec<String>,
593592
}
594593

595594
struct ImportCollector<'cx> {
@@ -624,6 +623,7 @@ enum LowerStage {
624623

625624
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
626625
pub enum IntrinsicName {
626+
RequestJiraSoftware,
627627
RequestJiraServiceManagement,
628628
RequestConfluence,
629629
RequestJira,
@@ -3306,19 +3306,22 @@ impl Visit for GlobalCollector<'_> {
33063306
}
33073307

33083308
struct StringCollector {
3309-
strings: Vec<Atom>,
3309+
strings: Vec<String>,
33103310
}
33113311

33123312
impl Visit for StringCollector {
33133313
fn visit_str(&mut self, n: &Str) {
3314-
self.add_str(n);
3314+
self.add_str(n.value.as_str().to_string());
3315+
}
3316+
3317+
fn visit_tpl(&mut self, n: &Tpl) {
3318+
self.add_str(n.quasis.iter().map(|val| val.raw.as_str()).collect());
33153319
}
33163320
}
33173321

33183322
impl StringCollector {
3319-
pub fn add_str(&mut self, n: &Str) {
3320-
let a = n.value.clone();
3321-
self.strings.push(a);
3323+
pub fn add_str(&mut self, n: String) {
3324+
self.strings.push(n);
33223325
}
33233326
}
33243327

crates/forge_analyzer/src/interp.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -390,11 +390,13 @@ pub struct Interp<'cx, C: Runner<'cx>> {
390390
pub callstack_arguments: Vec<Vec<Value>>,
391391
pub value_manager: ValueManager,
392392
pub permissions: Vec<String>,
393+
pub jira_software_permission_resolver: &'cx PermissionHashMap,
393394
pub jira_service_management_permission_resolver: &'cx PermissionHashMap,
394395
pub jira_permission_resolver: &'cx PermissionHashMap,
395396
pub confluence_permission_resolver: &'cx PermissionHashMap,
396-
pub jira_service_management_regex_map: &'cx HashMap<String, Regex>,
397397
pub bitbucket_permission_resolver: &'cx PermissionHashMap,
398+
pub jira_software_regex_map: &'cx HashMap<String, Regex>,
399+
pub jira_service_management_regex_map: &'cx HashMap<String, Regex>,
398400
pub jira_regex_map: &'cx HashMap<String, Regex>,
399401
pub confluence_regex_map: &'cx HashMap<String, Regex>,
400402
pub bitbucket_regex_map: &'cx HashMap<String, Regex>,
@@ -510,6 +512,8 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> {
510512
call_all: bool,
511513
call_uncalled: bool,
512514
permissions: Vec<String>,
515+
jira_software_permission_resolver: &'cx PermissionHashMap,
516+
jira_software_regex_map: &'cx HashMap<String, Regex>,
513517
jira_service_management_permission_resolver: &'cx PermissionHashMap,
514518
jira_service_management_regex_map: &'cx HashMap<String, Regex>,
515519
jira_permission_resolver: &'cx PermissionHashMap,
@@ -544,11 +548,13 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> {
544548
expecting_value: VecDeque::default(),
545549
},
546550
permissions,
551+
jira_software_permission_resolver,
547552
jira_service_management_permission_resolver,
548553
jira_permission_resolver,
549554
confluence_permission_resolver,
550-
jira_service_management_regex_map,
551555
bitbucket_permission_resolver,
556+
jira_software_regex_map,
557+
jira_service_management_regex_map,
552558
jira_regex_map,
553559
confluence_regex_map,
554560
bitbucket_regex_map,

crates/forge_permission_resolver/src/permissions_resolver.rs

+61-5
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ struct RequestDetails {
3535
default
3636
)]
3737
permission: Vec<PermissionData>,
38+
39+
// For parsing Jira Software as that swagger doesn't follow "x-atlassian-oauth2-scopes" scope style
40+
#[serde(default)]
41+
security: Vec<SecurityData>,
3842
}
3943

4044
#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)]
@@ -44,6 +48,12 @@ struct PermissionData {
4448
scopes: Vec<String>,
4549
}
4650

51+
#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)]
52+
struct SecurityData {
53+
#[serde(default, rename = "OAuth2")]
54+
oauth2: Vec<String>,
55+
}
56+
4757
#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
4858
pub enum RequestType {
4959
Get,
@@ -85,6 +95,11 @@ pub fn check_url_for_permissions(
8595
vec![]
8696
}
8797

98+
pub fn get_permission_resolver_jira_software() -> (PermissionHashMap, HashMap<String, Regex>) {
99+
let jira_software_url = "https://developer.atlassian.com/cloud/jira/software/swagger.v3.json";
100+
get_permission_resolver(jira_software_url)
101+
}
102+
88103
pub fn get_permission_resolver_jira_service_management(
89104
) -> (PermissionHashMap, HashMap<String, Regex>) {
90105
let jira_service_management_url =
@@ -199,12 +214,25 @@ fn get_request_type(
199214
}
200215

201216
fn get_scopes(endpoint_data: &RequestDetails) -> Vec<String> {
202-
endpoint_data
217+
let mut scopes = endpoint_data
203218
.permission
204219
.iter()
205220
.flat_map(|data| &*data.scopes)
206221
.cloned()
207-
.collect()
222+
.collect::<Vec<_>>();
223+
224+
if scopes.is_empty() {
225+
// For Jira Software if the initial scopes are empty, try the scopes from the security field
226+
scopes.extend(
227+
endpoint_data
228+
.security
229+
.iter()
230+
.flat_map(|sec| &sec.oauth2)
231+
.cloned(),
232+
);
233+
}
234+
235+
scopes
208236
}
209237

210238
#[cfg(test)]
@@ -285,9 +313,6 @@ mod test {
285313
let request_type = RequestType::Get;
286314
let result = check_url_for_permissions(&permission_map, &regex_map, request_type, url);
287315

288-
println!("Permission Map: {:?}", permission_map);
289-
println!("Regex Map: {:?}", regex_map);
290-
291316
assert!(!result.is_empty(), "Should have parsed permissions");
292317
assert!(
293318
result.contains(&String::from("manage:servicedesk-customer")),
@@ -362,4 +387,35 @@ mod test {
362387
"Should require admin:repository:bitbucket permission"
363388
);
364389
}
390+
391+
#[test]
392+
fn test_get_issues_for_epic() {
393+
let (permission_map, regex_map) = get_permission_resolver_jira_software();
394+
let url = "/rest/agile/1.0/sprint/23";
395+
let request_type = RequestType::Get;
396+
let result = check_url_for_permissions(&permission_map, &regex_map, request_type, url);
397+
398+
assert!(!result.is_empty(), "Should have parsed permissions");
399+
assert!(
400+
result.contains(&String::from("read:sprint:jira-software")),
401+
"Should require read:sprint:jira-software permission"
402+
);
403+
}
404+
405+
#[test]
406+
fn test_get_all_boards() {
407+
let (permission_map, regex_map) = get_permission_resolver_jira_software();
408+
let url = "/rest/agile/1.0/board";
409+
let request_type = RequestType::Get;
410+
let result = check_url_for_permissions(&permission_map, &regex_map, request_type, url);
411+
412+
assert!(!result.is_empty(), "Should have parsed permissions");
413+
414+
let expected_permission: Vec<String> = vec![
415+
String::from("read:board-scope:jira-software"),
416+
String::from("read:project:jira"),
417+
];
418+
419+
assert_eq!(result, expected_permission);
420+
}
365421
}

crates/fsrt/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ tracing-tree.workspace = true
2626
tracing.workspace = true
2727
walkdir.workspace = true
2828
graphql-parser = "0.4.0"
29+
glob = "0.3.2"

0 commit comments

Comments
 (0)