Skip to content

Commit f6201c2

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

File tree

4 files changed

+61
-16
lines changed

4 files changed

+61
-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

+9-14
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,6 @@ fn secret_vuln_object() {
336336
);
337337

338338
let scan_result = scan_directory_test(test_forge_project);
339-
println!("scan_result {scan_result:?}");
340339
assert!(scan_result.contains_secret_vuln(1));
341340
assert!(scan_result.contains_vulns(1))
342341
}
@@ -635,13 +634,12 @@ fn basic_authz_vuln() {
635634
);
636635

637636
let scan_result = scan_directory_test(test_forge_project);
638-
println!("vuln, {:#?}", scan_result);
639637
assert!(scan_result.contains_authz_vuln(1));
640638
assert!(scan_result.contains_vulns(1));
641639
}
642640

641+
#[cfg(feature = "graphql_schema")]
643642
#[test]
644-
#[ignore]
645643
fn excess_scope() {
646644
let mut test_forge_project = MockForgeProject::files_from_string(
647645
"// src/index.tsx
@@ -663,13 +661,13 @@ fn excess_scope() {
663661
.push("read:component:compass".into());
664662

665663
let scan_result = scan_directory_test(test_forge_project);
666-
println!("scan_result {:#?}", scan_result);
667664
assert!(scan_result.contains_perm_vuln(1));
668665
assert!(scan_result.contains_vulns(1))
669666
}
670667

668+
#[cfg(feature = "graphql_schema")]
671669
#[test]
672-
fn correct_scopes() {
670+
fn graphql_correct_scopes() {
673671
let mut test_forge_project = MockForgeProject::files_from_string(
674672
"// src/index.tsx
675673
import ForgeUI, { render, Macro } from '@forge/ui';
@@ -706,16 +704,15 @@ fn correct_scopes() {
706704
.test_manifest
707705
.permissions
708706
.scopes
709-
.push("read:component:compass".into());
707+
.push("compass:atlassian-external".into());
710708

711709
let scan_result = scan_directory_test(test_forge_project);
712-
println!("scan_result {:#?}", scan_result);
713710
assert!(scan_result.contains_vulns(0))
714711
}
715712

713+
#[cfg(feature = "graphql_schema")]
716714
#[test]
717-
#[ignore]
718-
fn excess_scope_with_fragments() {
715+
fn graphql_excess_scope_with_fragments() {
719716
let mut test_forge_project = MockForgeProject::files_from_string(
720717
"// src/index.tsx
721718
import ForgeUI, { render, Macro } from '@forge/ui';
@@ -740,13 +737,13 @@ fn excess_scope_with_fragments() {
740737
.push("read:component:compass".into());
741738

742739
let scan_result = scan_directory_test(test_forge_project);
743-
println!("scan_result {:#?}", scan_result);
744740
assert!(scan_result.contains_perm_vuln(1));
745741
assert!(scan_result.contains_vulns(1))
746742
}
747743

744+
#[cfg(feature = "graphql_schema")]
748745
#[test]
749-
fn correct_scopes_with_fragment() {
746+
fn graphql_correct_scopes_with_fragment() {
750747
let mut test_forge_project = MockForgeProject::files_from_string(
751748
"// src/index.tsx
752749
import ForgeUI, { render, Macro } from '@forge/ui';
@@ -769,10 +766,9 @@ fn correct_scopes_with_fragment() {
769766
.test_manifest
770767
.permissions
771768
.scopes
772-
.push("read:component:compass".into());
769+
.push("compass:atlassian-external".into());
773770

774771
let scan_result = scan_directory_test(test_forge_project);
775-
println!("scan_result {:#?}", scan_result);
776772
assert!(scan_result.contains_vulns(0))
777773
}
778774

@@ -887,6 +883,5 @@ fn authz_function_called_in_object_bitbucket() {
887883
);
888884

889885
let scan_result = scan_directory_test(test_forge_project);
890-
println!("scan_result {:#?}", scan_result);
891886
assert!(scan_result.contains_vulns(1))
892887
}

0 commit comments

Comments
 (0)