|
3 | 3 | // SPDX-License-Identifier: MPL-2.0 OR GPL-3.0-or-later |
4 | 4 |
|
5 | 5 | use crate::proto::sapphillon::v1 as sapphillon_v1; |
| 6 | +pub use sapphillon_v1::Permission; |
| 7 | +pub use sapphillon_v1::PermissionType; |
6 | 8 |
|
7 | 9 | use crate::utils::{check_path::paths_cover_by_ancestor, check_url::urls_cover_by_ancestor}; |
8 | 10 | use std::collections::HashMap; |
@@ -239,6 +241,76 @@ pub fn check_permission( |
239 | 241 | } |
240 | 242 | } |
241 | 243 |
|
| 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 | + |
242 | 314 | #[cfg(test)] |
243 | 315 | mod tests { |
244 | 316 | use crate::proto::sapphillon::v1 as sapphillon_v1; |
@@ -498,4 +570,149 @@ mod tests { |
498 | 570 | ); |
499 | 571 | assert!(matches!(res, CheckPermissionResult::Ok)); |
500 | 572 | } |
| 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 | + } |
501 | 718 | } |
0 commit comments