From 04811ff4369413bcdcc3212d36a9f092b7c449a1 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sun, 31 May 2026 03:20:28 +0200 Subject: [PATCH] domain: add PermissionChecker service with additive role evaluation --- .../domain/src/services/permission_service.rs | 22 +++++++++- crates/domain/tests/services/mod.rs | 1 + .../tests/services/permission_service.rs | 42 +++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 crates/domain/tests/services/mod.rs create mode 100644 crates/domain/tests/services/permission_service.rs diff --git a/crates/domain/src/services/permission_service.rs b/crates/domain/src/services/permission_service.rs index 8ecaa70..c19486f 100644 --- a/crates/domain/src/services/permission_service.rs +++ b/crates/domain/src/services/permission_service.rs @@ -1 +1,21 @@ -// Permission service — will be implemented in Task 5 +use std::collections::HashSet; +use crate::entities::{Permission, PermissionAction, ResourceType, Role}; + +pub struct PermissionChecker; + +impl PermissionChecker { + pub fn has_permission( + roles: &[Role], + action: PermissionAction, + resource_type: ResourceType, + ) -> bool { + roles.iter().any(|role| { + role.has_permission(action, resource_type) + || role.has_permission(action, ResourceType::Global) + }) + } + + pub fn effective_permissions(roles: &[Role]) -> HashSet { + roles.iter().flat_map(|r| r.permissions.iter().copied()).collect() + } +} diff --git a/crates/domain/tests/services/mod.rs b/crates/domain/tests/services/mod.rs new file mode 100644 index 0000000..b9bf335 --- /dev/null +++ b/crates/domain/tests/services/mod.rs @@ -0,0 +1 @@ +mod permission_service; diff --git a/crates/domain/tests/services/permission_service.rs b/crates/domain/tests/services/permission_service.rs new file mode 100644 index 0000000..ad8b2de --- /dev/null +++ b/crates/domain/tests/services/permission_service.rs @@ -0,0 +1,42 @@ +use domain::entities::{Permission, PermissionAction, ResourceType, Role}; +use domain::entities::permission::{admin_permissions, viewer_permissions}; +use domain::services::permission_service::PermissionChecker; + +#[test] +fn viewer_can_read() { + let role = Role::new("viewer", viewer_permissions(), true); + assert!(PermissionChecker::has_permission( + &[role], + PermissionAction::ReadAsset, + ResourceType::Asset, + )); +} + +#[test] +fn viewer_cannot_delete() { + let role = Role::new("viewer", viewer_permissions(), true); + assert!(!PermissionChecker::has_permission( + &[role], + PermissionAction::DeleteAsset, + ResourceType::Asset, + )); +} + +#[test] +fn roles_additive() { + let r1 = Role::new("r1", [Permission::new(PermissionAction::ReadAsset, ResourceType::Global)].into(), false); + let r2 = Role::new("r2", [Permission::new(PermissionAction::WriteMetadata, ResourceType::Global)].into(), false); + let eff = PermissionChecker::effective_permissions(&[r1, r2]); + assert_eq!(eff.len(), 2); +} + +#[test] +fn global_covers_specific() { + let role = Role::new("admin", admin_permissions(), true); + // Global ReadAsset should cover Asset-scoped check + assert!(PermissionChecker::has_permission( + &[role], + PermissionAction::ReadAsset, + ResourceType::Album, + )); +}