diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index e664c06..f94a3eb 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1093,6 +1093,10 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let intrinsic_func_type = intrinsic_argument.name.unwrap(); let (resolver, regex_map) = match intrinsic_func_type { + IntrinsicName::RequestJiraAny => ( + interp.jira_any_permission_resolver, + interp.jira_any_regex_map, + ), IntrinsicName::RequestJiraSoftware => ( interp.jira_software_permission_resolver, interp.jira_software_regex_map, diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index e9b7bd2..07fe2f5 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -617,6 +617,7 @@ enum LowerStage { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum IntrinsicName { + RequestJiraAny, RequestJiraSoftware, RequestJiraServiceManagement, RequestConfluence, @@ -991,6 +992,23 @@ impl FunctionAnalyzer<'_> { *prop == *"get" || *prop == *"getSecret" || *prop == *"query" } + fn resolve_jira_api_type(url: &str) -> Option { + match url { + url if url.starts_with("/rest/servicedeskapi/") => { + Some(IntrinsicName::RequestJiraServiceManagement) + } + url if url.starts_with("/rest/agile/") => Some(IntrinsicName::RequestJiraSoftware), + // Accept Jira API v2.0 or v3.0 + url if url.starts_with("/rest/api/3/") || url.starts_with("/rest/api/2/") => { + Some(IntrinsicName::RequestJira) + } + _ => { + warn!("Invalid Jira API URL format: {}", url); + None + } + } + } + match *callee { [PropPath::Unknown((ref name, ..))] if *name == *"fetch" => Some(Intrinsic::Fetch), [PropPath::Def(def), ref authn @ .., PropPath::Static(ref last)] @@ -1000,15 +1018,36 @@ impl FunctionAnalyzer<'_> { && Some(&ImportKind::Default) == self.res.is_imported_from(def, "@forge/api") => { + let first_arg = first_arg?; + let is_as_app = authn.first() == Some(&PropPath::MemberCall("asApp".into())); + let function_name = if *last == "requestJira" { - IntrinsicName::RequestJira + // Resolve Jira API requests to either JSM/JS/Jira as all are bundled within requestJira() + match first_arg { + Expr::Tpl(template) => { + let url = template + .quasis + .iter() + .map(|quasi| quasi.raw.as_str()) + .collect::(); + + resolve_jira_api_type(&url).unwrap_or_else(|| { + warn!("Falling back to any Jira request"); + IntrinsicName::RequestJiraAny + }) + } + _ => { + // Conservatively assume any of Jira APIs may be used if we can't statically determine which one + warn!("First parameter to requestJira() is invalid"); + IntrinsicName::RequestJiraAny + } + } } else if *last == "requestBitbucket" { IntrinsicName::RequestBitbucket } else { IntrinsicName::RequestConfluence }; - let first_arg = first_arg?; - let is_as_app = authn.first() == Some(&PropPath::MemberCall("asApp".into())); + match classify_api_call(first_arg) { ApiCallKind::Unknown => { if is_as_app { diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index f4649a7..f116546 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -390,11 +390,13 @@ pub struct Interp<'cx, C: Runner<'cx>> { pub callstack_arguments: Vec>, pub value_manager: ValueManager, pub permissions: Vec, + pub jira_any_permission_resolver: &'cx PermissionHashMap, 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 bitbucket_permission_resolver: &'cx PermissionHashMap, + pub jira_any_regex_map: &'cx HashMap, pub jira_software_regex_map: &'cx HashMap, pub jira_service_management_regex_map: &'cx HashMap, pub jira_regex_map: &'cx HashMap, @@ -512,6 +514,8 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { call_all: bool, call_uncalled: bool, permissions: Vec, + jira_any_permission_resolver: &'cx PermissionHashMap, + jira_any_regex_map: &'cx HashMap, jira_software_permission_resolver: &'cx PermissionHashMap, jira_software_regex_map: &'cx HashMap, jira_service_management_permission_resolver: &'cx PermissionHashMap, @@ -548,11 +552,13 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { expecting_value: VecDeque::default(), }, permissions, + jira_any_permission_resolver, jira_software_permission_resolver, jira_service_management_permission_resolver, jira_permission_resolver, confluence_permission_resolver, bitbucket_permission_resolver, + jira_any_regex_map, jira_software_regex_map, jira_service_management_regex_map, jira_regex_map, diff --git a/crates/forge_permission_resolver/src/permissions_resolver.rs b/crates/forge_permission_resolver/src/permissions_resolver.rs index 62466ba..5da8af4 100644 --- a/crates/forge_permission_resolver/src/permissions_resolver.rs +++ b/crates/forge_permission_resolver/src/permissions_resolver.rs @@ -95,6 +95,26 @@ pub fn check_url_for_permissions( vec![] } +pub fn get_permission_resolver_jira_any() -> (PermissionHashMap, HashMap) { + // Combine all Jira variations to achieve a generic "any" Jira + let (jira_map, jira_regex) = get_permission_resolver_jira(); + let (jsm_map, jsm_regex) = get_permission_resolver_jira_service_management(); + let (js_map, js_regex) = get_permission_resolver_jira_software(); + + let mut combined_permission_map = PermissionHashMap::default(); + let mut combined_regex_map = HashMap::default(); + + combined_permission_map.extend(jira_map); + combined_permission_map.extend(jsm_map); + combined_permission_map.extend(js_map); + + combined_regex_map.extend(jira_regex); + combined_regex_map.extend(jsm_regex); + combined_regex_map.extend(js_regex); + + (combined_permission_map, combined_regex_map) +} + pub fn get_permission_resolver_jira_software() -> (PermissionHashMap, HashMap) { let jira_software_url = "https://developer.atlassian.com/cloud/jira/software/swagger.v3.json"; get_permission_resolver(jira_software_url) diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index eb164de..30b53ac 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -7,8 +7,8 @@ mod test; use clap::{Parser, ValueHint}; use forge_permission_resolver::permissions_resolver::{ get_permission_resolver_bitbucket, get_permission_resolver_confluence, - get_permission_resolver_jira, get_permission_resolver_jira_service_management, - get_permission_resolver_jira_software, + get_permission_resolver_jira, get_permission_resolver_jira_any, + get_permission_resolver_jira_service_management, get_permission_resolver_jira_software, }; use glob::glob; use std::{ @@ -410,6 +410,7 @@ pub(crate) fn scan_directory<'a>( let permissions = permissions_declared.into_iter().collect::>(); + let (jira_any_permission_resolver, jira_any_regex_map) = get_permission_resolver_jira_any(); let (jira_software_permission_resolver, jira_software_regex_map) = get_permission_resolver_jira_software(); let (jira_service_management_permission_resolver, jira_service_management_regex_map) = @@ -424,6 +425,8 @@ pub(crate) fn scan_directory<'a>( false, true, permissions.clone(), + &jira_any_permission_resolver, + &jira_any_regex_map, &jira_software_permission_resolver, &jira_software_regex_map, &jira_service_management_permission_resolver, @@ -441,6 +444,8 @@ pub(crate) fn scan_directory<'a>( false, false, permissions.clone(), + &jira_any_permission_resolver, + &jira_any_regex_map, &jira_software_permission_resolver, &jira_software_regex_map, &jira_service_management_permission_resolver, @@ -457,6 +462,8 @@ pub(crate) fn scan_directory<'a>( false, false, permissions.clone(), + &jira_any_permission_resolver, + &jira_any_regex_map, &jira_software_permission_resolver, &jira_software_regex_map, &jira_service_management_permission_resolver, @@ -475,6 +482,8 @@ pub(crate) fn scan_directory<'a>( false, false, permissions.clone(), + &jira_any_permission_resolver, + &jira_any_regex_map, &jira_software_permission_resolver, &jira_software_regex_map, &jira_service_management_permission_resolver, @@ -493,6 +502,8 @@ pub(crate) fn scan_directory<'a>( false, true, permissions, + &jira_any_permission_resolver, + &jira_any_regex_map, &jira_software_permission_resolver, &jira_software_regex_map, &jira_service_management_permission_resolver,