Skip to content

Commit

Permalink
Add GraphQL support for the permission scanner
Browse files Browse the repository at this point in the history
  • Loading branch information
gersbach committed Jan 6, 2025
2 parents 9d4b7b7 + 7fa85d2 commit 82ffa4c
Show file tree
Hide file tree
Showing 9 changed files with 396 additions and 197 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.115"
serde_yaml = "0.9.34"
petgraph = "0.6.4"
graphql-parser = "0.4.0"
pretty_assertions = "1.4.0"
indexmap = { version = "2.2.6", features = ["std"] }
regex = "1.10.4"
Expand Down
126 changes: 42 additions & 84 deletions crates/forge_analyzer/src/checkers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1092,99 +1092,57 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow {
let mut permissions_within_call: Vec<String> = vec![];
let intrinsic_func_type = intrinsic_argument.name.unwrap();

let (resolver, regex_map) = match intrinsic_func_type {
IntrinsicName::RequestJiraSoftware => (
interp.jira_software_permission_resolver,
interp.jira_software_regex_map,
),
IntrinsicName::RequestJiraServiceManagement => (
interp.jira_service_management_permission_resolver,
interp.jira_service_management_regex_map,
),
IntrinsicName::RequestConfluence => (
interp.confluence_permission_resolver,
interp.confluence_regex_map,
),
IntrinsicName::RequestJira => {
(interp.jira_permission_resolver, interp.jira_regex_map)
}
IntrinsicName::RequestBitbucket => (
interp.bitbucket_permission_resolver,
interp.bitbucket_regex_map,
),
_ => unreachable!("Invalid intrinsic function type"),
};

if intrinsic_argument.first_arg.is_none() {
interp.permissions.drain(..);
} else {
intrinsic_argument
.first_arg
.iter()
.for_each(|first_arg_vec| {
if let Some(second_arg_vec) = intrinsic_argument.second_arg.clone() {
first_arg_vec.iter().for_each(|first_arg| {
let first_arg = first_arg.replace(&['\"'][..], "");
second_arg_vec.iter().for_each(|second_arg| {
if intrinsic_func_type
== IntrinsicName::RequestJiraServiceManagement
{
let permissions = check_url_for_permissions(
interp.jira_service_management_permission_resolver,
interp.jira_service_management_regex_map,
translate_request_type(Some(second_arg)),
&first_arg,
);
permissions_within_call.extend_from_slice(&permissions)
} else if intrinsic_func_type
== IntrinsicName::RequestConfluence
{
let permissions = check_url_for_permissions(
interp.confluence_permission_resolver,
interp.confluence_regex_map,
translate_request_type(Some(second_arg)),
&first_arg,
);
permissions_within_call.extend_from_slice(&permissions)
} else if intrinsic_func_type == IntrinsicName::RequestJira {
let permissions = check_url_for_permissions(
interp.jira_permission_resolver,
interp.jira_regex_map,
translate_request_type(Some(second_arg)),
&first_arg,
);
permissions_within_call.extend_from_slice(&permissions)
} else if intrinsic_func_type == IntrinsicName::RequestBitbucket
{
let permissions = check_url_for_permissions(
interp.bitbucket_permission_resolver,
interp.bitbucket_regex_map,
translate_request_type(Some(second_arg)),
&first_arg,
);
permissions_within_call.extend_from_slice(&permissions)
}
first_arg_vec.iter().for_each(|first_arg| {
let first_arg = first_arg.replace(&['\"'][..], "");
let request_types = intrinsic_argument
.second_arg
.as_ref()
.map(|args| {
args.iter()
.map(|arg| translate_request_type(Some(arg)))
.collect::<Vec<_>>()
.into_iter()
})
})
} else {
first_arg_vec.iter().for_each(|first_arg| {
let first_arg = first_arg.replace(&['\"'][..], "");
if intrinsic_func_type
== IntrinsicName::RequestJiraServiceManagement
{
let permissions = check_url_for_permissions(
interp.jira_service_management_permission_resolver,
interp.jira_service_management_regex_map,
RequestType::Get,
&first_arg,
);
permissions_within_call.extend_from_slice(&permissions)
} else if intrinsic_func_type == IntrinsicName::RequestConfluence {
let permissions = check_url_for_permissions(
interp.confluence_permission_resolver,
interp.confluence_regex_map,
RequestType::Get,
&first_arg,
);
permissions_within_call.extend_from_slice(&permissions)
} else if intrinsic_func_type == IntrinsicName::RequestJira {
let permissions = check_url_for_permissions(
interp.jira_permission_resolver,
interp.jira_regex_map,
RequestType::Get,
&first_arg,
);
permissions_within_call.extend_from_slice(&permissions)
} else if intrinsic_func_type == IntrinsicName::RequestBitbucket {
let permissions = check_url_for_permissions(
interp.bitbucket_permission_resolver,
interp.bitbucket_regex_map,
RequestType::Get,
&first_arg,
);
permissions_within_call.extend_from_slice(&permissions)
}
})
}
});
.unwrap_or_else(|| vec![RequestType::Get].into_iter());

for req_type in request_types {
let permissions = check_url_for_permissions(
resolver, regex_map, req_type, &first_arg,
);
permissions_within_call.extend_from_slice(&permissions);
}
});
});
interp
.permissions
.retain(|permissions| !permissions_within_call.contains(permissions));
Expand Down
1 change: 1 addition & 0 deletions crates/forge_analyzer/src/definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,7 @@ enum LowerStage {

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IntrinsicName {
RequestJiraSoftware,
RequestJiraServiceManagement,
RequestConfluence,
RequestJira,
Expand Down
10 changes: 8 additions & 2 deletions crates/forge_analyzer/src/interp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,11 +390,13 @@ pub struct Interp<'cx, C: Runner<'cx>> {
pub callstack_arguments: Vec<Vec<Value>>,
pub value_manager: ValueManager,
pub permissions: Vec<String>,
pub jira_software_permission_resolver: &'cx PermissionHashMap,
pub jira_service_management_permission_resolver: &'cx PermissionHashMap,
pub jira_permission_resolver: &'cx PermissionHashMap,
pub confluence_permission_resolver: &'cx PermissionHashMap,
pub jira_service_management_regex_map: &'cx HashMap<String, Regex>,
pub bitbucket_permission_resolver: &'cx PermissionHashMap,
pub jira_software_regex_map: &'cx HashMap<String, Regex>,
pub jira_service_management_regex_map: &'cx HashMap<String, Regex>,
pub jira_regex_map: &'cx HashMap<String, Regex>,
pub confluence_regex_map: &'cx HashMap<String, Regex>,
pub bitbucket_regex_map: &'cx HashMap<String, Regex>,
Expand Down Expand Up @@ -510,6 +512,8 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> {
call_all: bool,
call_uncalled: bool,
permissions: Vec<String>,
jira_software_permission_resolver: &'cx PermissionHashMap,
jira_software_regex_map: &'cx HashMap<String, Regex>,
jira_service_management_permission_resolver: &'cx PermissionHashMap,
jira_service_management_regex_map: &'cx HashMap<String, Regex>,
jira_permission_resolver: &'cx PermissionHashMap,
Expand Down Expand Up @@ -544,11 +548,13 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> {
expecting_value: VecDeque::default(),
},
permissions,
jira_software_permission_resolver,
jira_service_management_permission_resolver,
jira_permission_resolver,
confluence_permission_resolver,
jira_service_management_regex_map,
bitbucket_permission_resolver,
jira_software_regex_map,
jira_service_management_regex_map,
jira_regex_map,
confluence_regex_map,
bitbucket_regex_map,
Expand Down
66 changes: 61 additions & 5 deletions crates/forge_permission_resolver/src/permissions_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ struct RequestDetails {
default
)]
permission: Vec<PermissionData>,

// For parsing Jira Software as that swagger doesn't follow "x-atlassian-oauth2-scopes" scope style
#[serde(default)]
security: Vec<SecurityData>,
}

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

#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)]
struct SecurityData {
#[serde(default, rename = "OAuth2")]
oauth2: Vec<String>,
}

#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
pub enum RequestType {
Get,
Expand Down Expand Up @@ -85,6 +95,11 @@ pub fn check_url_for_permissions(
vec![]
}

pub fn get_permission_resolver_jira_software() -> (PermissionHashMap, HashMap<String, Regex>) {
let jira_software_url = "https://developer.atlassian.com/cloud/jira/software/swagger.v3.json";
get_permission_resolver(jira_software_url)
}

pub fn get_permission_resolver_jira_service_management(
) -> (PermissionHashMap, HashMap<String, Regex>) {
let jira_service_management_url =
Expand Down Expand Up @@ -199,12 +214,25 @@ fn get_request_type(
}

fn get_scopes(endpoint_data: &RequestDetails) -> Vec<String> {
endpoint_data
let mut scopes = endpoint_data
.permission
.iter()
.flat_map(|data| &*data.scopes)
.cloned()
.collect()
.collect::<Vec<_>>();

if scopes.is_empty() {
// For Jira Software if the initial scopes are empty, try the scopes from the security field
scopes.extend(
endpoint_data
.security
.iter()
.flat_map(|sec| &sec.oauth2)
.cloned(),
);
}

scopes
}

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

println!("Permission Map: {:?}", permission_map);
println!("Regex Map: {:?}", regex_map);

assert!(!result.is_empty(), "Should have parsed permissions");
assert!(
result.contains(&String::from("manage:servicedesk-customer")),
Expand Down Expand Up @@ -362,4 +387,35 @@ mod test {
"Should require admin:repository:bitbucket permission"
);
}

#[test]
fn test_get_issues_for_epic() {
let (permission_map, regex_map) = get_permission_resolver_jira_software();
let url = "/rest/agile/1.0/sprint/23";
let request_type = RequestType::Get;
let result = check_url_for_permissions(&permission_map, &regex_map, request_type, url);

assert!(!result.is_empty(), "Should have parsed permissions");
assert!(
result.contains(&String::from("read:sprint:jira-software")),
"Should require read:sprint:jira-software permission"
);
}

#[test]
fn test_get_all_boards() {
let (permission_map, regex_map) = get_permission_resolver_jira_software();
let url = "/rest/agile/1.0/board";
let request_type = RequestType::Get;
let result = check_url_for_permissions(&permission_map, &regex_map, request_type, url);

assert!(!result.is_empty(), "Should have parsed permissions");

let expected_permission: Vec<String> = vec![
String::from("read:board-scope:jira-software"),
String::from("read:project:jira"),
];

assert_eq!(result, expected_permission);
}
}
1 change: 1 addition & 0 deletions crates/fsrt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ tracing-tree.workspace = true
tracing.workspace = true
walkdir.workspace = true
graphql-parser = "0.4.0"
glob = "0.3.2"
Loading

0 comments on commit 82ffa4c

Please sign in to comment.