Skip to content

Commit f9f8bac

Browse files
authored
Merge branch 'main' into enable-perm-interp
2 parents 0e64617 + f085d62 commit f9f8bac

File tree

11 files changed

+327
-7
lines changed

11 files changed

+327
-7
lines changed

Cargo.lock

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

compass-scopes.yaml

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Sourced from https://developer.atlassian.com/cloud/compass/forge-graphql-toolkit/Classes/CompassRequests/
2+
# Required oauth scopes for compass graphql requests
3+
4+
addEventSource:
5+
- write:component:compass
6+
- write:event:compass
7+
8+
addLabels:
9+
- write:component:compass
10+
11+
attachDataManager:
12+
- write:component:compass
13+
14+
attachEventSource:
15+
- write:component:compass
16+
17+
createComponent:
18+
- write:component:compass
19+
20+
createCustomFieldDefinition:
21+
- write:component:compass
22+
23+
createEvent:
24+
- write:event:compass
25+
26+
createEventSource:
27+
- write:event:compass
28+
29+
createExternalAlias:
30+
- write:component:compass
31+
32+
createLink:
33+
- write:component:compass
34+
35+
createRelationship:
36+
- write:component:compass
37+
38+
deleteComponent:
39+
- write:component:compass
40+
41+
deleteExternalAlias:
42+
- write:component:compass
43+
44+
deleteLink:
45+
- write:component:compass
46+
47+
deleteRelationship:
48+
- write:component:compass
49+
50+
detachDataManager:
51+
- write:component:compass
52+
53+
detachEventSource:
54+
- write:component:compass
55+
56+
getAllComponentTypes:
57+
- read:component:compass
58+
59+
getComponent:
60+
- read:component:compass
61+
62+
getComponentByExternalAlias:
63+
- read:component:compass
64+
65+
getComponentsByReferences:
66+
- read:component:compass
67+
68+
getEventSource:
69+
- read:event:compass
70+
71+
insertMetricValueByExternalId:
72+
- write:metric:compass
73+
74+
removeLabels:
75+
- write:component:compass
76+
77+
searchComponents:
78+
- read:component:compass
79+
80+
syncComponentByExternalAlias:
81+
- write:component:compass
82+
- write:event:compass
83+
84+
synchronizeLinkAssociations:
85+
- write:component:compass
86+
- write:event:compass
87+
- write:metric:compass
88+
89+
unlinkExternalSource:
90+
- write:component:compass
91+
92+
updateComponent:
93+
- write:component:compass
94+
- write:event:compass
95+
96+
updateDataManager:
97+
- write:component:compass
98+
99+
updateEventSources:
100+
- write:component:compass
101+
- write:event:compass

crates/forge_analyzer/src/checkers.rs

+13-3
Original file line numberDiff line numberDiff line change
@@ -1050,7 +1050,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow {
10501050
if let Intrinsic::ApiCall(name) | Intrinsic::SafeCall(name) | Intrinsic::Authorize(name) =
10511051
intrinsic
10521052
{
1053-
intrinsic_argument.name = Some(*name);
1053+
intrinsic_argument.name = Some(name.clone());
10541054
let (first, second) = (operands.first(), operands.get(1));
10551055
if let Some(operand) = first {
10561056
match operand {
@@ -1092,9 +1092,19 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow {
10921092
}
10931093
}
10941094

1095-
let mut permissions_within_call: Vec<String> = vec![];
10961095
let intrinsic_func_type = intrinsic_argument.name.unwrap();
10971096

1097+
// Handle RequestCompass case first
1098+
if let IntrinsicName::RequestCompass(api_name) = intrinsic_func_type {
1099+
if let Some(oauth_scopes) = interp.compass_permission_resolver.get(&api_name) {
1100+
interp
1101+
.permissions
1102+
.retain(|p| !oauth_scopes.contains(&p.as_str()));
1103+
}
1104+
return initial_state;
1105+
}
1106+
1107+
let mut permissions_within_call: Vec<String> = vec![];
10981108
let (resolver, regex_map) = match intrinsic_func_type {
10991109
IntrinsicName::RequestJiraAny => (
11001110
interp.jira_any_permission_resolver,
@@ -1119,7 +1129,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow {
11191129
interp.bitbucket_permission_resolver,
11201130
interp.bitbucket_regex_map,
11211131
),
1122-
IntrinsicName::Other => {
1132+
IntrinsicName::RequestCompass(_) | IntrinsicName::Other => {
11231133
(&PermissionHashMap::new(), &HashMap::<String, Regex>::new())
11241134
}
11251135
};

crates/forge_analyzer/src/definitions.rs

+21-1
Original file line numberDiff line numberDiff line change
@@ -621,14 +621,15 @@ enum LowerStage {
621621
Create,
622622
}
623623

624-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
624+
#[derive(Debug, Clone, PartialEq, Eq)]
625625
pub enum IntrinsicName {
626626
RequestJiraAny,
627627
RequestJiraSoftware,
628628
RequestJiraServiceManagement,
629629
RequestConfluence,
630630
RequestJira,
631631
RequestBitbucket,
632+
RequestCompass(String),
632633
Other,
633634
}
634635

@@ -1192,6 +1193,25 @@ impl FunctionAnalyzer<'_> {
11921193
package.cloned().map(Intrinsic::SecretFunction)
11931194
}
11941195

1196+
// graphqlGateway's compass
1197+
// import graphqlGateway from "@atlassian/forge-graphql";
1198+
// const { errors, data} = await graphqlGateway.compass.asApp().getComponent({ componentId });
1199+
// [PropPath::Def(def), PropPath::Static(ref compass), ref auth
1200+
[PropPath::Def(def), PropPath::Static(ref compass), PropPath::MemberCall(ref authn), PropPath::Static(ref function_name)]
1201+
if *compass == *"compass"
1202+
&& Some(&ImportKind::Default)
1203+
== self.res.is_imported_from(def, "@atlassian/forge-graphql") =>
1204+
{
1205+
match authn.as_str() {
1206+
"asApp" => {
1207+
Some(Intrinsic::ApiCall(IntrinsicName::RequestCompass(function_name.to_string())))
1208+
}
1209+
"asUser" => {
1210+
Some(Intrinsic::SafeCall(IntrinsicName::RequestCompass(function_name.to_string())))
1211+
}
1212+
_ => None,
1213+
}
1214+
}
11951215
_ => None,
11961216
}
11971217
}

crates/forge_analyzer/src/interp.rs

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use std::{
1111
};
1212

1313
use forge_permission_resolver::permissions_resolver::PermissionHashMap;
14+
use forge_permission_resolver::permissions_resolver_compass::CompassPermissionResolver;
1415
use forge_utils::{FxHashMap, FxHashSet};
1516
use itertools::Itertools;
1617
use regex::Regex;
@@ -396,6 +397,7 @@ pub struct Interp<'cx, C: Runner<'cx>> {
396397
pub jira_permission_resolver: &'cx PermissionHashMap,
397398
pub confluence_permission_resolver: &'cx PermissionHashMap,
398399
pub bitbucket_permission_resolver: &'cx PermissionHashMap,
400+
pub compass_permission_resolver: &'cx CompassPermissionResolver,
399401
pub jira_any_regex_map: &'cx HashMap<String, Regex>,
400402
pub jira_software_regex_map: &'cx HashMap<String, Regex>,
401403
pub jira_service_management_regex_map: &'cx HashMap<String, Regex>,
@@ -526,6 +528,7 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> {
526528
confluence_regex_map: &'cx HashMap<String, Regex>,
527529
bitbucket_permission_resolver: &'cx PermissionHashMap,
528530
bitbucket_regex_map: &'cx HashMap<String, Regex>,
531+
compass_permission_resolver: &'cx CompassPermissionResolver,
529532
) -> Self {
530533
let call_graph = CallGraph::new(env);
531534

@@ -558,6 +561,7 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> {
558561
jira_permission_resolver,
559562
confluence_permission_resolver,
560563
bitbucket_permission_resolver,
564+
compass_permission_resolver,
561565
jira_any_regex_map,
562566
jira_software_regex_map,
563567
jira_service_management_regex_map,

crates/forge_permission_resolver/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ workspace = true
1313
[dependencies]
1414
serde.workspace = true
1515
serde_json.workspace = true
16+
serde_yaml.workspace = true
1617
tracing.workspace = true
1718
regex.workspace = true
1819
ureq = { version = "2.9.6", features = ["json", "charset"] }
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
pub mod permissions_resolver;
2+
pub mod permissions_resolver_compass;

crates/forge_permission_resolver/src/permissions_resolver.rs

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::permissions_resolver_compass::CompassPermissionResolver;
12
use regex::Regex;
23
use serde::Deserialize;
34
use std::{cmp::Reverse, collections::HashMap, hash::Hash};
@@ -142,6 +143,10 @@ pub fn get_permission_resolver_bitbucket() -> (PermissionHashMap, HashMap<String
142143
get_permission_resolver(bitbucket_url)
143144
}
144145

146+
pub fn get_permission_resolver_compass() -> CompassPermissionResolver {
147+
CompassPermissionResolver::new()
148+
}
149+
145150
pub fn get_permission_resolver(url: &str) -> (PermissionHashMap, HashMap<String, Regex>) {
146151
let mut endpoint_map: PermissionHashMap = HashMap::default();
147152
let mut endpoint_regex: HashMap<String, Regex> = HashMap::default();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use serde_yaml;
2+
use std::collections::HashMap;
3+
use tracing::warn;
4+
5+
#[derive(Debug)]
6+
pub struct CompassPermissionResolver {
7+
api_scopes_map: HashMap<&'static str, Vec<&'static str>>,
8+
}
9+
10+
impl CompassPermissionResolver {
11+
/// Factory method to create a new `CompassPermissionResolver`
12+
pub fn new() -> Self {
13+
let scopes = load_compass_api_scopes();
14+
CompassPermissionResolver {
15+
api_scopes_map: scopes,
16+
}
17+
}
18+
19+
/// Retrieves permissions for a given key. Prints a warning if the key is missing.
20+
pub fn get(&self, key: &str) -> Option<&[&str]> {
21+
if let Some(scopes) = self.api_scopes_map.get(key) {
22+
Some(scopes)
23+
} else {
24+
warn!(
25+
"Warning: compass API '{}' not found in the api->scopes mapping.",
26+
key
27+
);
28+
None
29+
}
30+
}
31+
}
32+
33+
impl Default for CompassPermissionResolver {
34+
fn default() -> Self {
35+
Self::new()
36+
}
37+
}
38+
39+
// This is for handling https://developer.atlassian.com/cloud/compass/forge-graphql-toolkit/
40+
fn load_compass_api_scopes() -> HashMap<&'static str, Vec<&'static str>> {
41+
let file = include_str!("../../../compass-scopes.yaml");
42+
serde_yaml::from_str(file)
43+
.expect("Error: Failed to parse compass-scopes YAML file. Ensure it is properly formatted.")
44+
}
45+
46+
#[cfg(test)]
47+
mod test {
48+
use super::*;
49+
50+
#[test]
51+
fn test_load_compass_api_scopes() {
52+
let scopes = load_compass_api_scopes();
53+
54+
// Verify that the scopes are loaded correctly
55+
assert!(scopes.contains_key("addEventSource"));
56+
assert_eq!(
57+
scopes.get("addEventSource").unwrap(),
58+
&vec![
59+
"write:component:compass".to_string(),
60+
"write:event:compass".to_string()
61+
]
62+
);
63+
64+
assert!(scopes.contains_key("addLabels"));
65+
assert_eq!(
66+
scopes.get("addLabels").unwrap(),
67+
&vec!["write:component:compass".to_string()]
68+
);
69+
}
70+
}

crates/fsrt/src/main.rs

+10-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ mod test;
66

77
use clap::{Parser, ValueHint};
88
use forge_permission_resolver::permissions_resolver::{
9-
get_permission_resolver_bitbucket, get_permission_resolver_confluence,
10-
get_permission_resolver_jira, get_permission_resolver_jira_any,
11-
get_permission_resolver_jira_service_management, get_permission_resolver_jira_software,
9+
get_permission_resolver_bitbucket, get_permission_resolver_compass,
10+
get_permission_resolver_confluence, get_permission_resolver_jira,
11+
get_permission_resolver_jira_any, get_permission_resolver_jira_service_management,
12+
get_permission_resolver_jira_software,
1213
};
1314
use glob::glob;
1415
use std::{
@@ -414,6 +415,7 @@ pub(crate) fn scan_directory<'a>(
414415
let (confluence_permission_resolver, confluence_regex_map) =
415416
get_permission_resolver_confluence();
416417
let (bitbucket_permission_resolver, bitbucket_regex_map) = get_permission_resolver_bitbucket();
418+
let compass_permission_resolver = get_permission_resolver_compass();
417419

418420
let mut definition_analysis_interp = Interp::<DefinitionAnalysisRunner>::new(
419421
&proj.env,
@@ -432,6 +434,7 @@ pub(crate) fn scan_directory<'a>(
432434
&confluence_regex_map,
433435
&bitbucket_permission_resolver,
434436
&bitbucket_regex_map,
437+
&compass_permission_resolver,
435438
);
436439

437440
let mut interp = Interp::new(
@@ -451,6 +454,7 @@ pub(crate) fn scan_directory<'a>(
451454
&confluence_regex_map,
452455
&bitbucket_permission_resolver,
453456
&bitbucket_regex_map,
457+
&compass_permission_resolver,
454458
);
455459
let mut authn_interp = Interp::new(
456460
&proj.env,
@@ -469,6 +473,7 @@ pub(crate) fn scan_directory<'a>(
469473
&confluence_regex_map,
470474
&bitbucket_permission_resolver,
471475
&bitbucket_regex_map,
476+
&compass_permission_resolver,
472477
);
473478

474479
let mut reporter = Reporter::new();
@@ -489,6 +494,7 @@ pub(crate) fn scan_directory<'a>(
489494
&confluence_regex_map,
490495
&bitbucket_permission_resolver,
491496
&bitbucket_regex_map,
497+
&compass_permission_resolver,
492498
);
493499
reporter.add_app(opts.appkey.clone().unwrap_or_default(), name.to_owned());
494500

@@ -509,6 +515,7 @@ pub(crate) fn scan_directory<'a>(
509515
&confluence_regex_map,
510516
&bitbucket_permission_resolver,
511517
&bitbucket_regex_map,
518+
&compass_permission_resolver,
512519
);
513520
for func in &proj.funcs {
514521
let mut def_checker = DefinitionAnalysisRunner::new();

0 commit comments

Comments
 (0)