From fecbc4d6bde2cff4fbd72fe1d23f89b7b52bc8c3 Mon Sep 17 00:00:00 2001 From: dxu2atlassian <136645827+dxu2atlassian@users.noreply.github.com> Date: Mon, 23 Dec 2024 14:54:46 -0800 Subject: [PATCH] Classify Jira API requests with url pattern matching --- crates/forge_analyzer/src/definitions.rs | 45 +++++++++++++++++-- .../src/permissions_resolver.rs | 6 --- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 0e48f71..0f2e383 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -991,24 +991,61 @@ 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)] if (*last == *"requestJira" || *last == *"requestConfluence" - || *last == *"requestBitbucket") // TODO: resolve Jira API requests to the correct permission map, here JSM (and likely JS) is bundled inside Jira + || *last == *"requestBitbucket") && 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 generic Jira request"); + IntrinsicName::RequestJira // TODO: how should we handle this edge case? + }) + } + _ => { + warn!("First parameter to requestJira() is invalid"); + IntrinsicName::RequestJira // TODO: how should we handle this edge case? + } + } } 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_permission_resolver/src/permissions_resolver.rs b/crates/forge_permission_resolver/src/permissions_resolver.rs index c6d464a..62466ba 100644 --- a/crates/forge_permission_resolver/src/permissions_resolver.rs +++ b/crates/forge_permission_resolver/src/permissions_resolver.rs @@ -395,9 +395,6 @@ mod test { let request_type = RequestType::Get; let result = check_url_for_permissions(&permission_map, ®ex_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("read:sprint:jira-software")), @@ -412,9 +409,6 @@ mod test { let request_type = RequestType::Get; let result = check_url_for_permissions(&permission_map, ®ex_map, request_type, url); - println!("Permission Map: {:?}", permission_map); - println!("Regex Map: {:?}", regex_map); - assert!(!result.is_empty(), "Should have parsed permissions"); let expected_permission: Vec = vec![