Skip to content

Commit 6580db7

Browse files
authored
Merge pull request #122 from Sapphillon/feat/add-permission-check-helpers-18362136437383131735
Add Permission Check Helper Functions
2 parents f9239b6 + baf64c7 commit 6580db7

File tree

1 file changed

+217
-0
lines changed

1 file changed

+217
-0
lines changed

src/permission.rs

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
// SPDX-License-Identifier: MPL-2.0 OR GPL-3.0-or-later
44

55
use crate::proto::sapphillon::v1 as sapphillon_v1;
6+
pub use sapphillon_v1::Permission;
7+
pub use sapphillon_v1::PermissionType;
68

79
use crate::utils::{check_path::paths_cover_by_ancestor, check_url::urls_cover_by_ancestor};
810
use std::collections::HashMap;
@@ -239,6 +241,76 @@ pub fn check_permission(
239241
}
240242
}
241243

244+
/// Finds and returns permissions from a list of allowed permissions that match
245+
/// the given function ID or a wildcard (*).
246+
///
247+
/// # Arguments
248+
/// * `allowed` - A list of allowed permissions.
249+
/// * `target_function_ids` - The function IDs to match against (supports multiple).
250+
///
251+
/// # Returns
252+
/// The matching `Permissions`. Returns empty `Permissions` if no match is found.
253+
pub fn find_allowed_permissions(
254+
allowed: &[PluginFunctionPermissions],
255+
target_function_ids: &[&str],
256+
) -> Permissions {
257+
allowed
258+
.iter()
259+
.find(|p| {
260+
target_function_ids.contains(&p.plugin_function_id.as_str())
261+
|| p.plugin_function_id == "*"
262+
})
263+
.map(|p| p.permissions.clone())
264+
.unwrap_or_else(|| Permissions {
265+
permissions: vec![],
266+
})
267+
}
268+
269+
/// Finds the permissions for a target function ID from a list of allowed
270+
/// permissions and checks them against the required permissions.
271+
///
272+
/// # Arguments
273+
/// * `allowed` - A list of allowed permissions.
274+
/// * `target_function_ids` - The function IDs to match against (supports multiple).
275+
/// * `required_permissions` - The permissions required.
276+
///
277+
/// # Returns
278+
/// A `CheckPermissionResult`.
279+
pub fn find_and_check_permission(
280+
allowed: &[PluginFunctionPermissions],
281+
target_function_ids: &[&str],
282+
required_permissions: &Permissions,
283+
) -> CheckPermissionResult {
284+
let allowed_permissions = find_allowed_permissions(allowed, target_function_ids);
285+
check_permission(&allowed_permissions, required_permissions)
286+
}
287+
288+
/// A high-level helper function to check permissions that include a resource
289+
/// (e.g., file path, URL, command).
290+
///
291+
/// # Arguments
292+
/// * `allowed` - A list of allowed permissions.
293+
/// * `target_function_ids` - The function ID to match against.
294+
/// * `base_permissions` - The base permission definition.
295+
/// * `resource` - The resource string to check.
296+
///
297+
/// # Returns
298+
/// `Ok(())` or `Err(MissingPermissions)`.
299+
pub fn check_plugin_permission_with_resource(
300+
allowed: &[PluginFunctionPermissions],
301+
target_function_ids: &[&str],
302+
mut base_permissions: Vec<Permission>,
303+
resource: String,
304+
) -> CheckPermissionResult {
305+
if let Some(perm) = base_permissions.first_mut() {
306+
perm.resource = vec![resource];
307+
}
308+
let required = Permissions {
309+
permissions: base_permissions,
310+
};
311+
find_and_check_permission(allowed, target_function_ids, &required)
312+
}
313+
242314
#[cfg(test)]
243315
mod tests {
244316
use crate::proto::sapphillon::v1 as sapphillon_v1;
@@ -498,4 +570,149 @@ mod tests {
498570
);
499571
assert!(matches!(res, CheckPermissionResult::Ok));
500572
}
573+
574+
#[test]
575+
fn test_find_allowed_permissions_exact_match() {
576+
let allowed = vec![
577+
PluginFunctionPermissions {
578+
plugin_function_id: "test.func1".to_string(),
579+
permissions: Permissions::new(vec![sapphillon_v1::Permission {
580+
permission_type: 1,
581+
..Default::default()
582+
}]),
583+
},
584+
PluginFunctionPermissions {
585+
plugin_function_id: "test.func2".to_string(),
586+
permissions: Permissions::new(vec![sapphillon_v1::Permission {
587+
permission_type: 2,
588+
..Default::default()
589+
}]),
590+
},
591+
];
592+
593+
let result = find_allowed_permissions(&allowed, &["test.func1"]);
594+
assert_eq!(result.permissions.len(), 1);
595+
assert_eq!(result.permissions[0].permission_type, 1);
596+
}
597+
598+
#[test]
599+
fn test_find_allowed_permissions_wildcard_match() {
600+
let allowed = vec![
601+
PluginFunctionPermissions {
602+
plugin_function_id: "*".to_string(),
603+
permissions: Permissions::new(vec![sapphillon_v1::Permission {
604+
permission_type: 99,
605+
..Default::default()
606+
}]),
607+
},
608+
PluginFunctionPermissions {
609+
plugin_function_id: "test.func2".to_string(),
610+
permissions: Permissions::new(vec![sapphillon_v1::Permission {
611+
permission_type: 2,
612+
..Default::default()
613+
}]),
614+
},
615+
];
616+
617+
let result = find_allowed_permissions(&allowed, &["test.func1"]);
618+
assert_eq!(result.permissions.len(), 1);
619+
assert_eq!(result.permissions[0].permission_type, 99);
620+
}
621+
622+
#[test]
623+
fn test_find_allowed_permissions_no_match() {
624+
let allowed = vec![PluginFunctionPermissions {
625+
plugin_function_id: "test.func1".to_string(),
626+
permissions: Permissions::new(vec![sapphillon_v1::Permission {
627+
permission_type: 1,
628+
..Default::default()
629+
}]),
630+
}];
631+
632+
let result = find_allowed_permissions(&allowed, &["test.func3"]);
633+
assert!(result.permissions.is_empty());
634+
}
635+
636+
#[test]
637+
fn test_find_allowed_permissions_multiple_function_ids() {
638+
let allowed = vec![PluginFunctionPermissions {
639+
plugin_function_id: "fetch.post".to_string(),
640+
permissions: Permissions::new(vec![sapphillon_v1::Permission {
641+
permission_type: 6, // NetAccess
642+
..Default::default()
643+
}]),
644+
}];
645+
646+
let result = find_allowed_permissions(&allowed, &["fetch.get", "fetch.post"]);
647+
assert_eq!(result.permissions.len(), 1);
648+
assert_eq!(result.permissions[0].permission_type, 6);
649+
}
650+
651+
#[test]
652+
fn test_find_and_check_permission_ok() {
653+
let allowed = vec![PluginFunctionPermissions {
654+
plugin_function_id: "filesystem.read".to_string(),
655+
permissions: Permissions::new(vec![sapphillon_v1::Permission {
656+
permission_type: sapphillon_v1::PermissionType::FilesystemRead as i32,
657+
resource: vec!["/home/user".to_string()],
658+
..Default::default()
659+
}]),
660+
}];
661+
let required = Permissions::new(vec![sapphillon_v1::Permission {
662+
permission_type: sapphillon_v1::PermissionType::FilesystemRead as i32,
663+
resource: vec!["/home/user/file.txt".to_string()],
664+
..Default::default()
665+
}]);
666+
667+
let result = find_and_check_permission(&allowed, &["filesystem.read"], &required);
668+
assert!(matches!(result, CheckPermissionResult::Ok));
669+
}
670+
671+
#[test]
672+
fn test_find_and_check_permission_denied() {
673+
let allowed = vec![PluginFunctionPermissions {
674+
plugin_function_id: "filesystem.read".to_string(),
675+
permissions: Permissions::new(vec![sapphillon_v1::Permission {
676+
permission_type: sapphillon_v1::PermissionType::FilesystemRead as i32,
677+
resource: vec!["/home/guest".to_string()],
678+
..Default::default()
679+
}]),
680+
}];
681+
let required = Permissions::new(vec![sapphillon_v1::Permission {
682+
permission_type: sapphillon_v1::PermissionType::FilesystemRead as i32,
683+
resource: vec!["/home/user/file.txt".to_string()],
684+
..Default::default()
685+
}]);
686+
687+
let result = find_and_check_permission(&allowed, &["filesystem.read"], &required);
688+
assert!(matches!(
689+
result,
690+
CheckPermissionResult::MissingPermission(_)
691+
));
692+
}
693+
694+
#[test]
695+
fn test_check_plugin_permission_with_resource() {
696+
let allowed = vec![PluginFunctionPermissions {
697+
plugin_function_id: "filesystem.write".to_string(),
698+
permissions: Permissions::new(vec![sapphillon_v1::Permission {
699+
permission_type: sapphillon_v1::PermissionType::FilesystemWrite as i32,
700+
resource: vec!["/data".to_string()],
701+
..Default::default()
702+
}]),
703+
}];
704+
let base_permissions = vec![sapphillon_v1::Permission {
705+
permission_type: sapphillon_v1::PermissionType::FilesystemWrite as i32,
706+
..Default::default()
707+
}];
708+
let resource = "/data/new_file.txt".to_string();
709+
710+
let result = check_plugin_permission_with_resource(
711+
&allowed,
712+
&["filesystem.write"],
713+
base_permissions,
714+
resource,
715+
);
716+
assert!(matches!(result, CheckPermissionResult::Ok));
717+
}
501718
}

0 commit comments

Comments
 (0)