use super::{AccessHierarchy, AccessScope};
use crate::permissions::{PermissionId, Permissions};
#[derive(Debug, Clone)]
pub struct AccessPolicy<R, G>
where
R: AccessHierarchy + Eq + std::fmt::Display,
G: Eq,
{
role_requirements: Vec<AccessScope<R>>,
group_requirements: Vec<G>,
permission_requirements: Permissions,
}
impl<R, G> AccessPolicy<R, G>
where
R: AccessHierarchy + Eq + std::fmt::Display,
G: Eq,
{
pub fn deny_all() -> Self {
Self {
role_requirements: vec![],
group_requirements: vec![],
permission_requirements: Permissions::new(),
}
}
pub fn require_role(role: R) -> Self {
Self {
role_requirements: vec![AccessScope::new(role)],
group_requirements: vec![],
permission_requirements: Permissions::new(),
}
}
pub fn require_role_or_supervisor(role: R) -> Self {
Self {
role_requirements: vec![AccessScope::new(role).allow_supervisor()],
group_requirements: vec![],
permission_requirements: Permissions::new(),
}
}
pub fn require_group(group: G) -> Self {
Self {
role_requirements: vec![],
group_requirements: vec![group],
permission_requirements: Permissions::new(),
}
}
pub fn require_permission<P: Into<PermissionId>>(permission: P) -> Self {
let mut permissions = Permissions::new();
let id: PermissionId = permission.into();
permissions.bitmap_mut().insert(id.as_u64());
Self {
role_requirements: vec![],
group_requirements: vec![],
permission_requirements: permissions,
}
}
pub fn or_require_role(mut self, role: R) -> Self {
self.role_requirements.push(AccessScope::new(role));
self
}
pub fn or_require_role_or_supervisor(mut self, role: R) -> Self {
self.role_requirements
.push(AccessScope::new(role).allow_supervisor());
self
}
pub fn or_require_group(mut self, group: G) -> Self {
self.group_requirements.push(group);
self
}
pub fn or_require_permission<P: Into<PermissionId>>(mut self, permission: P) -> Self {
let id: PermissionId = permission.into();
self.permission_requirements
.bitmap_mut()
.insert(id.as_u64());
self
}
pub fn or_require_permissions<P: Into<PermissionId>>(mut self, permissions: Vec<P>) -> Self {
permissions.into_iter().for_each(|p| {
let id: PermissionId = p.into();
self.permission_requirements
.bitmap_mut()
.insert(id.as_u64());
});
self
}
pub fn role_requirements(&self) -> &[AccessScope<R>] {
&self.role_requirements
}
pub fn group_requirements(&self) -> &[G] {
&self.group_requirements
}
pub fn permission_requirements(&self) -> &Permissions {
&self.permission_requirements
}
pub fn denies_all(&self) -> bool {
self.role_requirements.is_empty()
&& self.group_requirements.is_empty()
&& self.permission_requirements.is_empty()
}
pub fn has_requirements(&self) -> bool {
!self.denies_all()
}
pub fn into_components(self) -> (Vec<AccessScope<R>>, Vec<G>, Permissions) {
(
self.role_requirements,
self.group_requirements,
self.permission_requirements,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::groups::Group;
use crate::permissions::PermissionId;
use crate::roles::Role;
#[test]
fn deny_all_creates_empty_policy() {
let policy: AccessPolicy<Role, Group> = AccessPolicy::deny_all();
assert!(policy.denies_all());
assert!(!policy.has_requirements());
assert!(policy.role_requirements().is_empty());
assert!(policy.group_requirements().is_empty());
assert!(policy.permission_requirements().is_empty());
}
#[test]
fn require_role_creates_role_policy() {
let policy: AccessPolicy<Role, Group> = AccessPolicy::require_role(Role::Admin);
assert!(!policy.denies_all());
assert!(policy.has_requirements());
assert_eq!(policy.role_requirements().len(), 1);
assert!(policy.group_requirements().is_empty());
assert!(policy.permission_requirements().is_empty());
}
#[test]
fn require_role_or_supervisor_creates_supervisor_policy() {
let policy: AccessPolicy<Role, Group> =
AccessPolicy::require_role_or_supervisor(Role::Moderator);
assert!(!policy.denies_all());
assert!(policy.has_requirements());
assert_eq!(policy.role_requirements().len(), 1);
assert!(policy.role_requirements()[0].allow_supervisor_access);
}
#[test]
fn require_group_creates_group_policy() {
let policy: AccessPolicy<Role, Group> =
AccessPolicy::require_group(Group::new("engineering"));
assert!(!policy.denies_all());
assert!(policy.has_requirements());
assert!(policy.role_requirements().is_empty());
assert_eq!(policy.group_requirements().len(), 1);
assert!(policy.permission_requirements().is_empty());
}
#[test]
fn require_permission_creates_permission_policy() {
let permission_name = "read:api";
let expected_id = PermissionId::from(permission_name).as_u64();
let policy: AccessPolicy<Role, Group> = AccessPolicy::require_permission(permission_name);
assert!(!policy.denies_all());
assert!(policy.has_requirements());
assert!(policy.role_requirements().is_empty());
assert!(policy.group_requirements().is_empty());
assert!(
policy
.permission_requirements()
.iter()
.any(|id| id == expected_id)
);
}
#[test]
fn builder_methods_add_requirements() {
let base_perms = vec!["read:api", "write:api", "admin:panel"];
let policy: AccessPolicy<Role, Group> = AccessPolicy::require_role(Role::Admin)
.or_require_role_or_supervisor(Role::Moderator)
.or_require_group(Group::new("engineering"))
.or_require_permission(base_perms[0])
.or_require_permissions(vec![base_perms[1], base_perms[2]]);
assert!(!policy.denies_all());
assert!(policy.has_requirements());
assert_eq!(policy.role_requirements().len(), 2);
assert_eq!(policy.group_requirements().len(), 1);
let collected: Vec<u64> = policy.permission_requirements().iter().collect();
for name in &base_perms {
let id = PermissionId::from(*name).as_u64();
assert!(collected.contains(&id), "missing permission {}", name);
}
}
#[test]
fn into_components_returns_all_requirements() {
let permission_name = "system:health";
let expected = PermissionId::from(permission_name).as_u64();
let policy: AccessPolicy<Role, Group> = AccessPolicy::require_role(Role::Admin)
.or_require_group(Group::new("test"))
.or_require_permission(permission_name);
let (roles, groups, permissions) = policy.into_components();
assert_eq!(roles.len(), 1);
assert_eq!(groups.len(), 1);
assert!(permissions.iter().any(|id| id == expected));
}
}