Skip to content

Commit e7c807b

Browse files
author
gersbach
committed
EAS-2582 : Map Scopes to OAuth
1 parent 7fa85d2 commit e7c807b

File tree

4 files changed

+63
-16
lines changed

4 files changed

+63
-16
lines changed

README.md

+8
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ Arguments:
2121
-f, --function <FUNCTION> A specific function to scan. Must be an entrypoint specified in `manifest.yml`
2222
-h, --help Print help information
2323
-V, --version Print version information
24+
--check-permissions Runs the permission checker
25+
--graphql-schema-path <LOCATION> Uses the graphql schema in location; othwerwise selects ~/.config dir
2426
```
2527

2628
## Installation
@@ -64,6 +66,12 @@ until then you can test `fsrt` by manually invoking:
6466
fsrt ./test-apps/jira-damn-vulnerable-forge-app
6567
```
6668

69+
Testing with a GraphQl Schema:
70+
71+
```sh
72+
cargo test --features graphql_schema
73+
```
74+
6775
## Contributions
6876

6977
Contributions to FSRT are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.

crates/fsrt/Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ license.workspace = true
88
[lints]
99
workspace = true
1010

11+
[features]
12+
graphql_schema = []
13+
1114
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1215

1316
[dependencies]

crates/fsrt/src/main.rs

+41-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use std::{
2020

2121
use graphql_parser::{
2222
query::{Mutation, Query, SelectionSet},
23-
schema::ObjectType,
23+
schema::{EnumType, EnumValue, ObjectType},
2424
};
2525

2626
use graphql_parser::{
@@ -618,6 +618,40 @@ pub(crate) fn scan_directory<'a>(
618618
&mut path.to_owned()
619619
};
620620

621+
let mut scope_path = path.clone();
622+
623+
scope_path.push("schema/shared/agg-shared-scopes.nadel");
624+
625+
let scope_map = fs::read_to_string(&scope_path).unwrap_or_default();
626+
627+
let ast = parse_schema::<&str>(&scope_map).unwrap_or_default();
628+
629+
let mut scope_name_to_oauth = HashMap::new();
630+
631+
ast.definitions.iter().for_each(|val| {
632+
if let graphql_parser::schema::Definition::TypeDefinition(TypeDefinition::Enum(
633+
EnumType { values, .. },
634+
)) = val
635+
{
636+
values.iter().for_each(
637+
|EnumValue {
638+
directives, name, ..
639+
}| {
640+
if let Some(directive) = directives.first() {
641+
if let graphql_parser::schema::Value::String(oauth_scope) = &directive
642+
.arguments
643+
.first()
644+
.expect("Should only be one directive")
645+
.1
646+
{
647+
scope_name_to_oauth.insert(name, oauth_scope);
648+
}
649+
}
650+
},
651+
)
652+
}
653+
});
654+
621655
path.push("schema/*/*.nadel");
622656

623657
let joined_schema = glob(path.to_str().unwrap_or_default())
@@ -653,10 +687,15 @@ pub(crate) fn scan_directory<'a>(
653687
used_graphql_perms.extend_from_slice(&graphql_perms_defid);
654688
used_graphql_perms.extend_from_slice(&graphql_perms_varid);
655689

690+
let oauth_scopes: Vec<&&String> = used_graphql_perms
691+
.iter()
692+
.map(|val| scope_name_to_oauth.get(&val.as_str()).unwrap())
693+
.collect();
694+
656695
let final_perms: Vec<&String> = perm_interp
657696
.permissions
658697
.iter()
659-
.filter(|f| !used_graphql_perms.contains(&**f))
698+
.filter(|f| !oauth_scopes.contains(&f))
660699
.collect();
661700

662701
if run_permission_checker && !final_perms.is_empty() {

crates/fsrt/src/test.rs

+11-14
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ trait ReportExt {
1717

1818
fn contains_secret_vuln(&self, expected_len: usize) -> bool;
1919

20+
#[cfg(feature = "graphql_schema")]
2021
fn contains_perm_vuln(&self, expected_len: usize) -> bool;
2122

2223
fn contains_vulns(&self, expected_len: i32) -> bool;
@@ -48,6 +49,7 @@ impl ReportExt for Report {
4849
== expected_len
4950
}
5051

52+
#[cfg(feature = "graphql_schema")]
5153
#[inline]
5254
fn contains_perm_vuln(&self, expected_len: usize) -> bool {
5355
self.into_vulns()
@@ -336,7 +338,6 @@ fn secret_vuln_object() {
336338
);
337339

338340
let scan_result = scan_directory_test(test_forge_project);
339-
println!("scan_result {scan_result:?}");
340341
assert!(scan_result.contains_secret_vuln(1));
341342
assert!(scan_result.contains_vulns(1))
342343
}
@@ -635,13 +636,12 @@ fn basic_authz_vuln() {
635636
);
636637

637638
let scan_result = scan_directory_test(test_forge_project);
638-
println!("vuln, {:#?}", scan_result);
639639
assert!(scan_result.contains_authz_vuln(1));
640640
assert!(scan_result.contains_vulns(1));
641641
}
642642

643+
#[cfg(feature = "graphql_schema")]
643644
#[test]
644-
#[ignore]
645645
fn excess_scope() {
646646
let mut test_forge_project = MockForgeProject::files_from_string(
647647
"// src/index.tsx
@@ -663,13 +663,13 @@ fn excess_scope() {
663663
.push("read:component:compass".into());
664664

665665
let scan_result = scan_directory_test(test_forge_project);
666-
println!("scan_result {:#?}", scan_result);
667666
assert!(scan_result.contains_perm_vuln(1));
668667
assert!(scan_result.contains_vulns(1))
669668
}
670669

670+
#[cfg(feature = "graphql_schema")]
671671
#[test]
672-
fn correct_scopes() {
672+
fn graphql_correct_scopes() {
673673
let mut test_forge_project = MockForgeProject::files_from_string(
674674
"// src/index.tsx
675675
import ForgeUI, { render, Macro } from '@forge/ui';
@@ -706,16 +706,15 @@ fn correct_scopes() {
706706
.test_manifest
707707
.permissions
708708
.scopes
709-
.push("read:component:compass".into());
709+
.push("compass:atlassian-external".into());
710710

711711
let scan_result = scan_directory_test(test_forge_project);
712-
println!("scan_result {:#?}", scan_result);
713712
assert!(scan_result.contains_vulns(0))
714713
}
715714

715+
#[cfg(feature = "graphql_schema")]
716716
#[test]
717-
#[ignore]
718-
fn excess_scope_with_fragments() {
717+
fn graphql_excess_scope_with_fragments() {
719718
let mut test_forge_project = MockForgeProject::files_from_string(
720719
"// src/index.tsx
721720
import ForgeUI, { render, Macro } from '@forge/ui';
@@ -740,13 +739,13 @@ fn excess_scope_with_fragments() {
740739
.push("read:component:compass".into());
741740

742741
let scan_result = scan_directory_test(test_forge_project);
743-
println!("scan_result {:#?}", scan_result);
744742
assert!(scan_result.contains_perm_vuln(1));
745743
assert!(scan_result.contains_vulns(1))
746744
}
747745

746+
#[cfg(feature = "graphql_schema")]
748747
#[test]
749-
fn correct_scopes_with_fragment() {
748+
fn graphql_correct_scopes_with_fragment() {
750749
let mut test_forge_project = MockForgeProject::files_from_string(
751750
"// src/index.tsx
752751
import ForgeUI, { render, Macro } from '@forge/ui';
@@ -769,10 +768,9 @@ fn correct_scopes_with_fragment() {
769768
.test_manifest
770769
.permissions
771770
.scopes
772-
.push("read:component:compass".into());
771+
.push("compass:atlassian-external".into());
773772

774773
let scan_result = scan_directory_test(test_forge_project);
775-
println!("scan_result {:#?}", scan_result);
776774
assert!(scan_result.contains_vulns(0))
777775
}
778776

@@ -887,6 +885,5 @@ fn authz_function_called_in_object_bitbucket() {
887885
);
888886

889887
let scan_result = scan_directory_test(test_forge_project);
890-
println!("scan_result {:#?}", scan_result);
891888
assert!(scan_result.contains_vulns(1))
892889
}

0 commit comments

Comments
 (0)