Skip to content

Commit 8becec8

Browse files
Classify Jira API requests with url pattern matching
1 parent 7fa85d2 commit 8becec8

File tree

5 files changed

+85
-5
lines changed

5 files changed

+85
-5
lines changed

crates/forge_analyzer/src/checkers.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1093,6 +1093,10 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow {
10931093
let intrinsic_func_type = intrinsic_argument.name.unwrap();
10941094

10951095
let (resolver, regex_map) = match intrinsic_func_type {
1096+
IntrinsicName::RequestJiraAny => (
1097+
interp.jira_any_permission_resolver,
1098+
interp.jira_any_regex_map,
1099+
),
10961100
IntrinsicName::RequestJiraSoftware => (
10971101
interp.jira_software_permission_resolver,
10981102
interp.jira_software_regex_map,

crates/forge_analyzer/src/definitions.rs

+42-3
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,7 @@ enum LowerStage {
617617

618618
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
619619
pub enum IntrinsicName {
620+
RequestJiraAny,
620621
RequestJiraSoftware,
621622
RequestJiraServiceManagement,
622623
RequestConfluence,
@@ -991,6 +992,23 @@ impl FunctionAnalyzer<'_> {
991992
*prop == *"get" || *prop == *"getSecret" || *prop == *"query"
992993
}
993994

995+
fn resolve_jira_api_type(url: &str) -> Option<IntrinsicName> {
996+
match url {
997+
url if url.starts_with("/rest/servicedeskapi/") => {
998+
Some(IntrinsicName::RequestJiraServiceManagement)
999+
}
1000+
url if url.starts_with("/rest/agile/") => Some(IntrinsicName::RequestJiraSoftware),
1001+
// Accept Jira API v2.0 or v3.0
1002+
url if url.starts_with("/rest/api/3/") || url.starts_with("/rest/api/2/") => {
1003+
Some(IntrinsicName::RequestJira)
1004+
}
1005+
_ => {
1006+
warn!("Invalid Jira API URL format: {}", url);
1007+
None
1008+
}
1009+
}
1010+
}
1011+
9941012
match *callee {
9951013
[PropPath::Unknown((ref name, ..))] if *name == *"fetch" => Some(Intrinsic::Fetch),
9961014
[PropPath::Def(def), ref authn @ .., PropPath::Static(ref last)]
@@ -1000,15 +1018,36 @@ impl FunctionAnalyzer<'_> {
10001018
&& Some(&ImportKind::Default)
10011019
== self.res.is_imported_from(def, "@forge/api") =>
10021020
{
1021+
let first_arg = first_arg?;
1022+
let is_as_app = authn.first() == Some(&PropPath::MemberCall("asApp".into()));
1023+
10031024
let function_name = if *last == "requestJira" {
1004-
IntrinsicName::RequestJira
1025+
// Resolve Jira API requests to either JSM/JS/Jira as all are bundled within requestJira()
1026+
match first_arg {
1027+
Expr::Tpl(template) => {
1028+
let url = template
1029+
.quasis
1030+
.iter()
1031+
.map(|quasi| quasi.raw.as_str())
1032+
.collect::<String>();
1033+
1034+
resolve_jira_api_type(&url).unwrap_or_else(|| {
1035+
warn!("Falling back to any Jira request");
1036+
IntrinsicName::RequestJiraAny
1037+
})
1038+
}
1039+
_ => {
1040+
// Conservatively assume any of Jira APIs may be used if we can't statically determine which one
1041+
warn!("First parameter to requestJira() is invalid");
1042+
IntrinsicName::RequestJiraAny
1043+
}
1044+
}
10051045
} else if *last == "requestBitbucket" {
10061046
IntrinsicName::RequestBitbucket
10071047
} else {
10081048
IntrinsicName::RequestConfluence
10091049
};
1010-
let first_arg = first_arg?;
1011-
let is_as_app = authn.first() == Some(&PropPath::MemberCall("asApp".into()));
1050+
10121051
match classify_api_call(first_arg) {
10131052
ApiCallKind::Unknown => {
10141053
if is_as_app {

crates/forge_analyzer/src/interp.rs

+6
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_any_permission_resolver: &'cx PermissionHashMap,
393394
pub jira_software_permission_resolver: &'cx PermissionHashMap,
394395
pub jira_service_management_permission_resolver: &'cx PermissionHashMap,
395396
pub jira_permission_resolver: &'cx PermissionHashMap,
396397
pub confluence_permission_resolver: &'cx PermissionHashMap,
397398
pub bitbucket_permission_resolver: &'cx PermissionHashMap,
399+
pub jira_any_regex_map: &'cx HashMap<String, Regex>,
398400
pub jira_software_regex_map: &'cx HashMap<String, Regex>,
399401
pub jira_service_management_regex_map: &'cx HashMap<String, Regex>,
400402
pub jira_regex_map: &'cx HashMap<String, Regex>,
@@ -512,6 +514,8 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> {
512514
call_all: bool,
513515
call_uncalled: bool,
514516
permissions: Vec<String>,
517+
jira_any_permission_resolver: &'cx PermissionHashMap,
518+
jira_any_regex_map: &'cx HashMap<String, Regex>,
515519
jira_software_permission_resolver: &'cx PermissionHashMap,
516520
jira_software_regex_map: &'cx HashMap<String, Regex>,
517521
jira_service_management_permission_resolver: &'cx PermissionHashMap,
@@ -548,11 +552,13 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> {
548552
expecting_value: VecDeque::default(),
549553
},
550554
permissions,
555+
jira_any_permission_resolver,
551556
jira_software_permission_resolver,
552557
jira_service_management_permission_resolver,
553558
jira_permission_resolver,
554559
confluence_permission_resolver,
555560
bitbucket_permission_resolver,
561+
jira_any_regex_map,
556562
jira_software_regex_map,
557563
jira_service_management_regex_map,
558564
jira_regex_map,

crates/forge_permission_resolver/src/permissions_resolver.rs

+20
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,26 @@ pub fn check_url_for_permissions(
9595
vec![]
9696
}
9797

98+
pub fn get_permission_resolver_jira_any() -> (PermissionHashMap, HashMap<String, Regex>) {
99+
// Combine all Jira variations to achieve a generic "any" Jira
100+
let (jira_map, jira_regex) = get_permission_resolver_jira();
101+
let (jsm_map, jsm_regex) = get_permission_resolver_jira_service_management();
102+
let (js_map, js_regex) = get_permission_resolver_jira_software();
103+
104+
let mut combined_permission_map = PermissionHashMap::default();
105+
let mut combined_regex_map = HashMap::default();
106+
107+
combined_permission_map.extend(jira_map);
108+
combined_permission_map.extend(jsm_map);
109+
combined_permission_map.extend(js_map);
110+
111+
combined_regex_map.extend(jira_regex);
112+
combined_regex_map.extend(jsm_regex);
113+
combined_regex_map.extend(js_regex);
114+
115+
(combined_permission_map, combined_regex_map)
116+
}
117+
98118
pub fn get_permission_resolver_jira_software() -> (PermissionHashMap, HashMap<String, Regex>) {
99119
let jira_software_url = "https://developer.atlassian.com/cloud/jira/software/swagger.v3.json";
100120
get_permission_resolver(jira_software_url)

crates/fsrt/src/main.rs

+13-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ mod test;
77
use clap::{Parser, ValueHint};
88
use forge_permission_resolver::permissions_resolver::{
99
get_permission_resolver_bitbucket, get_permission_resolver_confluence,
10-
get_permission_resolver_jira, get_permission_resolver_jira_service_management,
11-
get_permission_resolver_jira_software,
10+
get_permission_resolver_jira, get_permission_resolver_jira_any,
11+
get_permission_resolver_jira_service_management, get_permission_resolver_jira_software,
1212
};
1313
use glob::glob;
1414
use std::{
@@ -410,6 +410,7 @@ pub(crate) fn scan_directory<'a>(
410410

411411
let permissions = permissions_declared.into_iter().collect::<Vec<_>>();
412412

413+
let (jira_any_permission_resolver, jira_any_regex_map) = get_permission_resolver_jira_any();
413414
let (jira_software_permission_resolver, jira_software_regex_map) =
414415
get_permission_resolver_jira_software();
415416
let (jira_service_management_permission_resolver, jira_service_management_regex_map) =
@@ -424,6 +425,8 @@ pub(crate) fn scan_directory<'a>(
424425
false,
425426
true,
426427
permissions.clone(),
428+
&jira_any_permission_resolver,
429+
&jira_any_regex_map,
427430
&jira_software_permission_resolver,
428431
&jira_software_regex_map,
429432
&jira_service_management_permission_resolver,
@@ -441,6 +444,8 @@ pub(crate) fn scan_directory<'a>(
441444
false,
442445
false,
443446
permissions.clone(),
447+
&jira_any_permission_resolver,
448+
&jira_any_regex_map,
444449
&jira_software_permission_resolver,
445450
&jira_software_regex_map,
446451
&jira_service_management_permission_resolver,
@@ -457,6 +462,8 @@ pub(crate) fn scan_directory<'a>(
457462
false,
458463
false,
459464
permissions.clone(),
465+
&jira_any_permission_resolver,
466+
&jira_any_regex_map,
460467
&jira_software_permission_resolver,
461468
&jira_software_regex_map,
462469
&jira_service_management_permission_resolver,
@@ -475,6 +482,8 @@ pub(crate) fn scan_directory<'a>(
475482
false,
476483
false,
477484
permissions.clone(),
485+
&jira_any_permission_resolver,
486+
&jira_any_regex_map,
478487
&jira_software_permission_resolver,
479488
&jira_software_regex_map,
480489
&jira_service_management_permission_resolver,
@@ -493,6 +502,8 @@ pub(crate) fn scan_directory<'a>(
493502
false,
494503
true,
495504
permissions,
505+
&jira_any_permission_resolver,
506+
&jira_any_regex_map,
496507
&jira_software_permission_resolver,
497508
&jira_software_regex_map,
498509
&jira_service_management_permission_resolver,

0 commit comments

Comments
 (0)