From 503933141583d3af149c018c714148b500cc3529 Mon Sep 17 00:00:00 2001 From: dxu2atlassian <136645827+dxu2atlassian@users.noreply.github.com> Date: Sun, 12 Jan 2025 15:57:15 -0800 Subject: [PATCH] End to end bitbucket unit tests with bug fix in main function --- crates/forge_analyzer/src/checkers.rs | 37 +++++---- crates/forge_analyzer/src/reporter.rs | 4 + crates/fsrt/src/main.rs | 9 ++- crates/fsrt/src/test.rs | 109 ++++++++++++++++++++++++++ 4 files changed, 136 insertions(+), 23 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 185b9bd..87fdb18 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1,22 +1,3 @@ -use core::fmt; -use forge_permission_resolver::permissions_resolver::{ - check_url_for_permissions, PermissionHashMap, RequestType, -}; -use forge_utils::FxHashMap; -use itertools::Itertools; -use regex::Regex; -use smallvec::SmallVec; -use std::{ - cmp::max, - collections::HashMap, - collections::HashSet, - iter::{self, zip}, - mem, - ops::ControlFlow, - path::PathBuf, -}; -use tracing::{debug, info, warn}; - use crate::interp::ProjectionVec; use crate::utils::projvec_from_str; use crate::{ @@ -36,6 +17,24 @@ use crate::{ }, worklist::WorkList, }; +use core::fmt; +use forge_permission_resolver::permissions_resolver::{ + check_url_for_permissions, PermissionHashMap, RequestType, +}; +use forge_utils::FxHashMap; +use itertools::Itertools; +use regex::Regex; +use smallvec::SmallVec; +use std::{ + cmp::max, + collections::HashMap, + collections::HashSet, + iter::{self, zip}, + mem, + ops::ControlFlow, + path::PathBuf, +}; +use tracing::{debug, info, warn}; #[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Default)] pub enum Taint { diff --git a/crates/forge_analyzer/src/reporter.rs b/crates/forge_analyzer/src/reporter.rs index 1be3e22..ea27393 100644 --- a/crates/forge_analyzer/src/reporter.rs +++ b/crates/forge_analyzer/src/reporter.rs @@ -26,6 +26,10 @@ impl Vulnerability { pub fn check_name(&self) -> &str { &self.check_name } + + pub fn description(&self) -> &str { + &self.description + } } pub trait IntoVuln { diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 717551e..63e68f0 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -671,6 +671,7 @@ pub(crate) fn scan_directory<'a>( .collect::>() .join("\n"); + let mut final_perms: HashSet<&String> = perm_interp.permissions.iter().collect(); let ast = parse_schema::<&str>(&joined_schema); if let std::result::Result::Ok(doc) = ast { @@ -710,15 +711,15 @@ pub(crate) fn scan_directory<'a>( }) .collect(); - let final_perms: HashSet<&String> = perm_interp + final_perms = perm_interp .permissions .iter() .filter(|f| !oauth_scopes.contains(f.as_str())) .collect(); + } - if run_permission_checker && !final_perms.is_empty() { - reporter.add_vulnerabilities([PermissionVuln::new(final_perms)]); - } + if run_permission_checker && !final_perms.is_empty() { + reporter.add_vulnerabilities([PermissionVuln::new(final_perms)]); } Ok(reporter.into_report()) diff --git a/crates/fsrt/src/test.rs b/crates/fsrt/src/test.rs index a9cf7fc..c0e9d44 100644 --- a/crates/fsrt/src/test.rs +++ b/crates/fsrt/src/test.rs @@ -20,6 +20,8 @@ trait ReportExt { #[cfg(feature = "graphql_schema")] fn contains_perm_vuln(&self, expected_len: usize) -> bool; + fn vuln_description_contains(&self, check_name: &str, description_snippet: &str) -> bool; + fn contains_vulns(&self, expected_len: i32) -> bool; fn contains_authz_vuln(&self, expected_len: usize) -> bool; @@ -59,6 +61,17 @@ impl ReportExt for Report { == expected_len } + #[inline] + fn vuln_description_contains(&self, check_name: &str, description_snippet: &str) -> bool { + self.into_vulns() + .iter() + .filter(|vuln| { + vuln.check_name() == check_name && vuln.description().contains(&description_snippet) + }) + .count() + == 1 + } + #[inline] fn contains_vulns(&self, expected_len: i32) -> bool { self.into_vulns().len() == expected_len as usize @@ -887,3 +900,99 @@ fn authz_function_called_in_object_bitbucket() { let scan_result = scan_directory_test(test_forge_project); assert!(scan_result.contains_vulns(1)) } + +#[test] // Tests manifest has an extra scope defined but not being used, we expect a permission vuln. +fn extra_scope_bitbucket() { + let test_forge_project = MockForgeProject::files_from_string( + "// src/index.tsx + import ForgeUI, { render, Fragment, Macro, Text } from '@forge/ui'; + import api, { route, fetch } from '@forge/api'; + const App = () => { + let testObject = { + someFunction() { + const res = api.asUser().requestBitbucket(route`/repositories/mockworkspace/mockreposlug/default-reviewers/jcg`, { + method: 'PUT', + body: {} + }); + return res; + } + } + testObject.someFunction() + return ( + + Hello world! + + ); + }; + export const run = render(} />); + + // manifest.yaml + modules: + macro: + - key: basic-hello-world + function: main + title: basic + handler: nothing + description: Inserts Hello world! + function: + - key: main + handler: index.run + app: + id: ari:cloud:ecosystem::app/07b89c0f-949a-4905-9de9-6c9521035986 + permissions: + scopes: + - 'admin:repository:bitbucket' + - 'unused:permission:defined'" + ); + + let scan_result = scan_directory_test(test_forge_project); + assert!(scan_result.contains_vulns(1)); + assert!(scan_result.vuln_description_contains("Least-Privilege", "unused:permission:defined")); +} + +#[test] // Tests manifest with no extra scopes, we expect no vulns. +fn no_extra_scope_bitbucket() { + let test_forge_project = MockForgeProject::files_from_string( + "// src/index.tsx + import ForgeUI, { render, Fragment, Macro, Text } from '@forge/ui'; + import api, { route, fetch } from '@forge/api'; + const App = () => { + let testObject = { + someFunction() { + const res = api.asUser().requestBitbucket(route`/repositories/mockworkspace/mockreposlug/default-reviewers/jcg`, { + method: 'PUT', + body: {} + }); + return res; + } + } + testObject.someFunction() + return ( + + Hello world! + + ); + }; + export const run = render(} />); + + // manifest.yaml + modules: + macro: + - key: basic-hello-world + function: main + title: basic + handler: nothing + description: Inserts Hello world! + function: + - key: main + handler: index.run + app: + id: ari:cloud:ecosystem::app/07b89c0f-949a-4905-9de9-6c9521035986 + permissions: + scopes: + - 'admin:repository:bitbucket'" + ); + + let scan_result = scan_directory_test(test_forge_project); + assert!(scan_result.contains_vulns(0)); +}