Skip to content

Commit

Permalink
adding bitbucket api permission resolver with unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
dxu2-atlassian committed Dec 13, 2024
1 parent 03e073e commit 7898f16
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 5 deletions.
22 changes: 20 additions & 2 deletions crates/forge_analyzer/src/checkers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1119,6 +1119,16 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow {
&first_arg,
);
permissions_within_call.extend_from_slice(&permissions)
} else if intrinsic_func_type == IntrinsicName::RequestBitbucket
{
// TODO: added bitbucket permissions, verify this is right
let permissions = check_url_for_permissions(
interp.bitbucket_permission_resolver,
interp.bitbucket_regex_map,
translate_request_type(Some(second_arg)),
&first_arg,
);
permissions_within_call.extend_from_slice(&permissions)
}
})
})
Expand All @@ -1141,6 +1151,15 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow {
&first_arg,
);
permissions_within_call.extend_from_slice(&permissions)
} else if intrinsic_func_type == IntrinsicName::RequestBitbucket {
// TODO: added bitbucket permissions, verify this is right
let permissions = check_url_for_permissions(
interp.bitbucket_permission_resolver,
interp.bitbucket_regex_map,
RequestType::Get,
&first_arg,
);
permissions_within_call.extend_from_slice(&permissions)
}
})
}
Expand All @@ -1150,8 +1169,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow {
.permissions
.retain(|permissions| !permissions_within_call.contains(permissions));
}

// remvove all permissions that it finds
// remove all permissions that it finds
}
initial_state
}
Expand Down
7 changes: 6 additions & 1 deletion crates/forge_analyzer/src/definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,7 @@ enum LowerStage {
pub enum IntrinsicName {
RequestConfluence,
RequestJira,
RequestBitbucket,
Other,
}

Expand Down Expand Up @@ -991,12 +992,16 @@ impl FunctionAnalyzer<'_> {
match *callee {
[PropPath::Unknown((ref name, ..))] if *name == *"fetch" => Some(Intrinsic::Fetch),
[PropPath::Def(def), ref authn @ .., PropPath::Static(ref last)]
if (*last == *"requestJira" || *last == *"requestConfluence")
if (*last == *"requestJira"
|| *last == *"requestConfluence"
|| *last == *"requestBitbucket")
&& Some(&ImportKind::Default)
== self.res.is_imported_from(def, "@forge/api") =>
{
let function_name = if *last == "requestJira" {
IntrinsicName::RequestJira
} else if *last == "requestBitbucket" {
IntrinsicName::RequestBitbucket
} else {
IntrinsicName::RequestConfluence
};
Expand Down
6 changes: 6 additions & 0 deletions crates/forge_analyzer/src/interp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -392,8 +392,10 @@ pub struct Interp<'cx, C: Runner<'cx>> {
pub permissions: Vec<String>,
pub jira_permission_resolver: &'cx PermissionHashMap,
pub confluence_permission_resolver: &'cx PermissionHashMap,
pub bitbucket_permission_resolver: &'cx PermissionHashMap,
pub jira_regex_map: &'cx HashMap<String, Regex>,
pub confluence_regex_map: &'cx HashMap<String, Regex>,
pub bitbucket_regex_map: &'cx HashMap<String, Regex>,
_checker: PhantomData<C>,
}

Expand Down Expand Up @@ -510,6 +512,8 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> {
jira_regex_map: &'cx HashMap<String, Regex>,
confluence_permission_resolver: &'cx PermissionHashMap,
confluence_regex_map: &'cx HashMap<String, Regex>,
bitbucket_permission_resolver: &'cx PermissionHashMap,
bitbucket_regex_map: &'cx HashMap<String, Regex>,
) -> Self {
let call_graph = CallGraph::new(env);

Expand Down Expand Up @@ -538,8 +542,10 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> {
permissions,
jira_permission_resolver,
confluence_permission_resolver,
bitbucket_permission_resolver,
jira_regex_map,
confluence_regex_map,
bitbucket_regex_map,
_checker: PhantomData,
runner_visited: RefCell::new(FxHashSet::default()),
}
Expand Down
2 changes: 1 addition & 1 deletion crates/forge_loader/src/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ pub struct ForgeModules<'a> {
#[serde(rename = "macro", default, borrow)]
macros: Vec<MacroMod<'a>>,

// Jira modules
// Jira Modules
#[serde(rename = "jira:adminPage", default, borrow)]
pub jira_admin_page: Vec<JiraAdminPage<'a>>,
#[serde(rename = "jira:customField", default, borrow)]
Expand Down
73 changes: 73 additions & 0 deletions crates/forge_permission_resolver/src/permissions_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ pub fn get_permission_resolver_confluence() -> (PermissionHashMap, HashMap<Strin
get_permission_resolver(confluence_url)
}

pub fn get_permission_resolver_bitbucket() -> (PermissionHashMap, HashMap<String, Regex>) {
let bitbucket_url = "https://api.bitbucket.org/swagger.json";
get_permission_resolver(bitbucket_url)
}

pub fn get_permission_resolver(url: &str) -> (PermissionHashMap, HashMap<String, Regex>) {
let mut endpoint_map: PermissionHashMap = HashMap::default();
let mut endpoint_regex: HashMap<String, Regex> = HashMap::default();
Expand Down Expand Up @@ -265,4 +270,72 @@ mod test {

assert_eq!(result, expected_permission);
}

#[test]
fn test_resolving_default_reviewer_check_permissions() {
let (permission_map, regex_map) = get_permission_resolver_bitbucket();
let url = "/repositories/mockworkspace/mockreposlug/default-reviewers/jcg";
let request_type = RequestType::Get;
let result = check_url_for_permissions(&permission_map, &regex_map, request_type, url);

assert!(
!result.is_empty(),
"Should have parsed permissions for checking default reviewers endpoint"
);
assert!(
result.contains(&String::from("read:pullrequest:bitbucket")),
"Should require read:pullrequest:bitbucket permission"
);
}

#[test]
fn test_resolving_default_reviewer_add_permissions() {
let (permission_map, regex_map) = get_permission_resolver_bitbucket();
let url = "/repositories/mockworkspace/mockreposlug/default-reviewers/jcg";
let request_type = RequestType::Put;
let result = check_url_for_permissions(&permission_map, &regex_map, request_type, url);

assert!(
!result.is_empty(),
"Should have parsed permissions for adding default reviewers endpoint"
);
assert!(
result.contains(&String::from("admin:repository:bitbucket")),
"Should require admin:repository:bitbucket permission"
);
}

#[test]
fn test_resolving_repositories_workspace_permissions() {
let (permission_map, regex_map) = get_permission_resolver_bitbucket();
let url = "/repositories/asecurityteam";
let request_type = RequestType::Get;
let result = check_url_for_permissions(&permission_map, &regex_map, request_type, url);

assert!(
!result.is_empty(),
"Should have parsed permissions for workspace repositories endpoint"
);
assert!(
result.contains(&String::from("read:repository:bitbucket")),
"Should require read:repository:bitbucket permission"
);
}

#[test]
fn test_resolving_branch_restriction_permissions() {
let (permission_map, regex_map) = get_permission_resolver_bitbucket();
let url = "/repositories/asecurityteam/mock/branch-restrictions";
let request_type = RequestType::Get;
let result = check_url_for_permissions(&permission_map, &regex_map, request_type, url);

assert!(
!result.is_empty(),
"Should have parsed permissions for branch restrictions endpoint"
);
assert!(
result.contains(&String::from("admin:repository:bitbucket")),
"Should require admin:repository:bitbucket permission"
);
}
}
14 changes: 13 additions & 1 deletion crates/fsrt/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ mod test;

use clap::{Parser, ValueHint};
use forge_permission_resolver::permissions_resolver::{
get_permission_resolver_confluence, get_permission_resolver_jira,
get_permission_resolver_bitbucket, get_permission_resolver_confluence,
get_permission_resolver_jira,
};

use std::{
Expand Down Expand Up @@ -277,6 +278,7 @@ pub(crate) fn scan_directory<'a>(
let (jira_permission_resolver, jira_regex_map) = get_permission_resolver_jira();
let (confluence_permission_resolver, confluence_regex_map) =
get_permission_resolver_confluence();
let (bitbucket_permission_resolver, bitbucket_regex_map) = get_permission_resolver_bitbucket();

let mut definition_analysis_interp = Interp::<DefinitionAnalysisRunner>::new(
&proj.env,
Expand All @@ -287,6 +289,8 @@ pub(crate) fn scan_directory<'a>(
&jira_regex_map,
&confluence_permission_resolver,
&confluence_regex_map,
&bitbucket_permission_resolver,
&bitbucket_regex_map,
);

let mut interp = Interp::new(
Expand All @@ -298,6 +302,8 @@ pub(crate) fn scan_directory<'a>(
&jira_regex_map,
&confluence_permission_resolver,
&confluence_regex_map,
&bitbucket_permission_resolver,
&bitbucket_regex_map,
);
let mut authn_interp = Interp::new(
&proj.env,
Expand All @@ -308,6 +314,8 @@ pub(crate) fn scan_directory<'a>(
&jira_regex_map,
&confluence_permission_resolver,
&confluence_regex_map,
&bitbucket_permission_resolver,
&bitbucket_regex_map,
);

let mut reporter = Reporter::new();
Expand All @@ -320,6 +328,8 @@ pub(crate) fn scan_directory<'a>(
&jira_regex_map,
&confluence_permission_resolver,
&confluence_regex_map,
&bitbucket_permission_resolver,
&bitbucket_regex_map,
);
reporter.add_app(opts.appkey.clone().unwrap_or_default(), name.to_owned());

Expand All @@ -332,6 +342,8 @@ pub(crate) fn scan_directory<'a>(
&jira_regex_map,
&confluence_permission_resolver,
&confluence_regex_map,
&bitbucket_permission_resolver,
&bitbucket_regex_map,
);
for func in &proj.funcs {
let mut def_checker = DefinitionAnalysisRunner::new();
Expand Down
44 changes: 44 additions & 0 deletions crates/fsrt/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -844,3 +844,47 @@ fn rovo_function_basic_authz_vuln() {
assert!(scan_result.contains_authz_vuln(1));
assert!(scan_result.contains_vulns(1));
}

// TODO: ensure that this test is testing correct items, maybe use debugger? ALSO need to check IR for with -dump-ir
#[test]
fn authz_function_called_in_object_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.asApp().requestBitbucket(route`/rest/api/3/test`);
return res;
}
}
testObject.someFunction()
return (
<Fragment>
<Text>Hello world!</Text>
</Fragment>
);
};
export const run = render(<Macro app={<App />} />);
// 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: []",
);

let scan_result = scan_directory_test(test_forge_project);
assert!(scan_result.contains_vulns(1))
}

0 comments on commit 7898f16

Please sign in to comment.