Skip to content

Commit d0b4168

Browse files
compass asApp/asUser
1 parent b7cc884 commit d0b4168

File tree

6 files changed

+109
-3
lines changed

6 files changed

+109
-3
lines changed

crates/forge_analyzer/src/checkers.rs

+3
Original file line numberDiff line numberDiff line change
@@ -1119,6 +1119,9 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow {
11191119
interp.bitbucket_permission_resolver,
11201120
interp.bitbucket_regex_map,
11211121
),
1122+
IntrinsicName::RequestCompass => {
1123+
(interp.compass_permission_resolver, interp.compass_regex_map)
1124+
}
11221125
IntrinsicName::Other => {
11231126
(&PermissionHashMap::new(), &HashMap::<String, Regex>::new())
11241127
}

crates/forge_analyzer/src/definitions.rs

+20
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,7 @@ pub enum IntrinsicName {
629629
RequestConfluence,
630630
RequestJira,
631631
RequestBitbucket,
632+
RequestCompass,
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), 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.first() {
1206+
Some(PropPath::MemberCall(name)) if name == "asApp" => {
1207+
Some(Intrinsic::ApiCall(IntrinsicName::RequestCompass))
1208+
}
1209+
Some(PropPath::MemberCall(name)) if name == "asUser" => {
1210+
Some(Intrinsic::SafeCall(IntrinsicName::RequestCompass))
1211+
}
1212+
_ => None,
1213+
}
1214+
}
11951215
_ => None,
11961216
}
11971217
}

crates/forge_analyzer/src/interp.rs

+6
Original file line numberDiff line numberDiff line change
@@ -396,12 +396,14 @@ pub struct Interp<'cx, C: Runner<'cx>> {
396396
pub jira_permission_resolver: &'cx PermissionHashMap,
397397
pub confluence_permission_resolver: &'cx PermissionHashMap,
398398
pub bitbucket_permission_resolver: &'cx PermissionHashMap,
399+
pub compass_permission_resolver: &'cx PermissionHashMap,
399400
pub jira_any_regex_map: &'cx HashMap<String, Regex>,
400401
pub jira_software_regex_map: &'cx HashMap<String, Regex>,
401402
pub jira_service_management_regex_map: &'cx HashMap<String, Regex>,
402403
pub jira_regex_map: &'cx HashMap<String, Regex>,
403404
pub confluence_regex_map: &'cx HashMap<String, Regex>,
404405
pub bitbucket_regex_map: &'cx HashMap<String, Regex>,
406+
pub compass_regex_map: &'cx HashMap<String, Regex>,
405407
_checker: PhantomData<C>,
406408
}
407409

@@ -526,6 +528,8 @@ 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 PermissionHashMap,
532+
compass_regex_map: &'cx HashMap<String, Regex>,
529533
) -> Self {
530534
let call_graph = CallGraph::new(env);
531535

@@ -558,12 +562,14 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> {
558562
jira_permission_resolver,
559563
confluence_permission_resolver,
560564
bitbucket_permission_resolver,
565+
compass_permission_resolver,
561566
jira_any_regex_map,
562567
jira_software_regex_map,
563568
jira_service_management_regex_map,
564569
jira_regex_map,
565570
confluence_regex_map,
566571
bitbucket_regex_map,
572+
compass_regex_map,
567573
_checker: PhantomData,
568574
runner_visited: RefCell::new(FxHashSet::default()),
569575
}

crates/forge_permission_resolver/src/permissions_resolver.rs

+21
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,11 @@ pub fn get_permission_resolver_bitbucket() -> (PermissionHashMap, HashMap<String
142142
get_permission_resolver(bitbucket_url)
143143
}
144144

145+
pub fn get_permission_resolver_compass() -> (PermissionHashMap, HashMap<String, Regex>) {
146+
let compass_url = "https://developer.atlassian.com/cloud/compass/swagger.v3.json";
147+
get_permission_resolver(compass_url)
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();
@@ -408,6 +413,22 @@ mod test {
408413
);
409414
}
410415

416+
#[test]
417+
#[ignore]
418+
fn test_resolving_compass_permissions() {
419+
// TODO: it seems no permissions are being returned for compass urls
420+
let (permission_map, regex_map) = get_permission_resolver_compass();
421+
422+
let url = "/compass/v1/events";
423+
let request_type = RequestType::Post;
424+
let result = check_url_for_permissions(&permission_map, &regex_map, request_type, url);
425+
426+
assert!(
427+
!result.is_empty(),
428+
"Should have parsed permissions for compass endpoint"
429+
);
430+
}
431+
411432
#[test]
412433
fn test_get_issues_for_epic() {
413434
let (permission_map, regex_map) = get_permission_resolver_jira_software();

crates/fsrt/src/main.rs

+15-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::{
@@ -418,6 +419,7 @@ pub(crate) fn scan_directory<'a>(
418419
let (confluence_permission_resolver, confluence_regex_map) =
419420
get_permission_resolver_confluence();
420421
let (bitbucket_permission_resolver, bitbucket_regex_map) = get_permission_resolver_bitbucket();
422+
let (compass_permission_resolver, compass_regex_map) = get_permission_resolver_compass();
421423

422424
let mut definition_analysis_interp = Interp::<DefinitionAnalysisRunner>::new(
423425
&proj.env,
@@ -436,6 +438,8 @@ pub(crate) fn scan_directory<'a>(
436438
&confluence_regex_map,
437439
&bitbucket_permission_resolver,
438440
&bitbucket_regex_map,
441+
&compass_permission_resolver,
442+
&compass_regex_map,
439443
);
440444

441445
let mut interp = Interp::new(
@@ -455,6 +459,8 @@ pub(crate) fn scan_directory<'a>(
455459
&confluence_regex_map,
456460
&bitbucket_permission_resolver,
457461
&bitbucket_regex_map,
462+
&compass_permission_resolver,
463+
&compass_regex_map,
458464
);
459465
let mut authn_interp = Interp::new(
460466
&proj.env,
@@ -473,6 +479,8 @@ pub(crate) fn scan_directory<'a>(
473479
&confluence_regex_map,
474480
&bitbucket_permission_resolver,
475481
&bitbucket_regex_map,
482+
&compass_permission_resolver,
483+
&compass_regex_map,
476484
);
477485

478486
let mut reporter = Reporter::new();
@@ -493,6 +501,8 @@ pub(crate) fn scan_directory<'a>(
493501
&confluence_regex_map,
494502
&bitbucket_permission_resolver,
495503
&bitbucket_regex_map,
504+
&compass_permission_resolver,
505+
&compass_regex_map,
496506
);
497507
reporter.add_app(opts.appkey.clone().unwrap_or_default(), name.to_owned());
498508

@@ -513,6 +523,8 @@ pub(crate) fn scan_directory<'a>(
513523
&confluence_regex_map,
514524
&bitbucket_permission_resolver,
515525
&bitbucket_regex_map,
526+
&compass_permission_resolver,
527+
&compass_regex_map,
516528
);
517529
for func in &proj.funcs {
518530
let mut def_checker = DefinitionAnalysisRunner::new();

crates/fsrt/src/test.rs

+44
Original file line numberDiff line numberDiff line change
@@ -1187,3 +1187,47 @@ fn no_extra_scope_bitbucket() {
11871187
let scan_result = scan_directory_test(test_forge_project);
11881188
assert!(scan_result.contains_vulns(0));
11891189
}
1190+
1191+
#[test] // Tests manifest with no extra scopes, we expect no vulns.
1192+
fn graphql_compass() {
1193+
let test_forge_project = MockForgeProject::files_from_string(
1194+
"// src/index.tsx
1195+
import ForgeUI, { render, Fragment, Macro, Text } from '@forge/ui';
1196+
import graphqlGateway from '@atlassian/forge-graphql';
1197+
1198+
const App = () => {
1199+
const {
1200+
errors,
1201+
data
1202+
} = await graphqlGateway.compass.asApp().getComponent(1);
1203+
1204+
return (
1205+
<Fragment>
1206+
<Text>Hello world!</Text>
1207+
</Fragment>
1208+
);
1209+
};
1210+
export const run = render(<Macro app={<App />} />);
1211+
1212+
// manifest.yaml
1213+
modules:
1214+
macro:
1215+
- key: basic-hello-world
1216+
function: main
1217+
title: basic
1218+
handler: nothing
1219+
description: Inserts Hello world!
1220+
function:
1221+
- key: main
1222+
handler: index.run
1223+
app:
1224+
id: ari:cloud:ecosystem::app/07b89c0f-949a-4905-9de9-6c9521035986
1225+
permissions:
1226+
scopes:
1227+
",
1228+
);
1229+
1230+
let scan_result = scan_directory_test(test_forge_project);
1231+
assert!(scan_result.contains_authz_vuln(1));
1232+
dbg!(scan_result.into_vulns()[0].description());
1233+
}

0 commit comments

Comments
 (0)