use super::{AccessHierarchy, AccessPolicy};
use crate::accounts::Account;
use tracing::debug;
#[derive(Debug, Clone)]
pub struct AuthorizationService<R, G>
where
R: AccessHierarchy + Eq + std::fmt::Display,
G: Eq + Clone,
{
policy: AccessPolicy<R, G>,
}
impl<R, G> AuthorizationService<R, G>
where
R: AccessHierarchy + Eq + std::fmt::Display,
G: Eq + Clone,
{
pub fn new(policy: AccessPolicy<R, G>) -> Self {
Self { policy }
}
pub fn is_authorized(&self, account: &Account<R, G>) -> bool {
self.meets_permission_requirement(account)
|| self.meets_group_requirement(account)
|| self.meets_role_requirement(account)
|| self.meets_role_hierarchy_requirement(account)
}
pub fn meets_role_requirement(&self, account: &Account<R, G>) -> bool {
account.roles.iter().any(|r| {
self.policy
.role_requirements()
.iter()
.any(|scope| scope.grants_role(r))
})
}
pub fn meets_role_hierarchy_requirement(&self, account: &Account<R, G>) -> bool {
debug!("Checking role hierarchy (same-or-supervisor) against required roles.");
account.roles.iter().any(|user_role| {
self.policy
.role_requirements()
.iter()
.any(|scope| scope.grants_supervisor(user_role))
})
}
pub fn meets_group_requirement(&self, account: &Account<R, G>) -> bool {
account.groups.iter().any(|r| {
self.policy
.group_requirements()
.iter()
.any(|g_scope| g_scope.eq(r))
})
}
pub fn meets_permission_requirement(&self, account: &Account<R, G>) -> bool {
let account_permissions: std::collections::HashSet<u64> =
account.permissions.iter().collect();
let required_permissions: std::collections::HashSet<u64> =
self.policy.permission_requirements().iter().collect();
!account_permissions.is_disjoint(&required_permissions)
}
pub fn policy_denies_all_access(&self) -> bool {
self.policy.denies_all()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::groups::Group;
use crate::roles::Role;
fn create_test_account() -> Account<Role, Group> {
use crate::permissions::Permissions;
use uuid::Uuid;
let mut permissions = Permissions::new();
permissions.bitmap_mut().insert(1u64);
permissions.bitmap_mut().insert(5u64);
Account {
account_id: Uuid::new_v4(),
user_id: "test_user".to_string(),
roles: vec![Role::Admin],
groups: vec![Group::new("engineering")],
permissions,
}
}
#[test]
fn authorization_service_empty_criteria() {
let service: AuthorizationService<Role, Group> =
AuthorizationService::new(AccessPolicy::deny_all());
assert!(service.policy_denies_all_access());
}
#[test]
fn authorization_service_non_empty_criteria() {
let policy = AccessPolicy::require_role(Role::Admin);
let service: AuthorizationService<Role, Group> = AuthorizationService::new(policy);
assert!(!service.policy_denies_all_access());
}
#[test]
fn authorized_by_role_matching() {
let account = create_test_account();
let policy = AccessPolicy::require_role(Role::Admin);
let service = AuthorizationService::new(policy);
assert!(service.meets_role_requirement(&account));
}
#[test]
fn authorized_by_role_not_matching() {
let account = create_test_account();
let policy = AccessPolicy::require_role(Role::User);
let service = AuthorizationService::new(policy);
assert!(!service.meets_role_requirement(&account));
}
#[test]
fn authorized_by_group_matching() {
let account = create_test_account();
let policy = AccessPolicy::require_group(Group::new("engineering"));
let service = AuthorizationService::new(policy);
assert!(service.meets_group_requirement(&account));
}
#[test]
fn authorized_by_group_not_matching() {
let account = create_test_account();
let policy = AccessPolicy::require_group(Group::new("sales"));
let service = AuthorizationService::new(policy);
assert!(!service.meets_group_requirement(&account));
}
#[test]
fn authorized_by_permission_matching() {
let account = create_test_account();
let policy =
AccessPolicy::require_permission(crate::permissions::PermissionId::from_u64(1)); let service = AuthorizationService::new(policy);
assert!(service.meets_permission_requirement(&account));
}
#[test]
fn authorized_by_permission_not_matching() {
let account = create_test_account();
let policy =
AccessPolicy::require_permission(crate::permissions::PermissionId::from_u64(10)); let service = AuthorizationService::new(policy);
assert!(!service.meets_permission_requirement(&account));
}
#[test]
fn is_authorized_returns_true_when_any_criteria_match() {
let account = create_test_account();
let policy = AccessPolicy::require_role(Role::User) .or_require_group(Group::new("engineering")); let service = AuthorizationService::new(policy);
assert!(service.is_authorized(&account));
}
#[test]
fn is_authorized_returns_false_when_no_criteria_match() {
let account = create_test_account();
let policy = AccessPolicy::require_role(Role::User) .or_require_group(Group::new("sales")); let service = AuthorizationService::new(policy);
assert!(!service.is_authorized(&account));
}
}