use crate::authz::access_hierarchy::AccessHierarchy;
use crate::authz::access_scope::AccessScope;
use crate::permissions::Permissions;
use crate::permissions::permission_id::PermissionId;
#[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::new(),
group_requirements: Vec::new(),
permission_requirements: Permissions::new(),
}
}
pub fn require_role(role: R) -> Self {
Self {
role_requirements: vec![AccessScope::new(role)],
group_requirements: Vec::new(),
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::new(),
permission_requirements: Permissions::new(),
}
}
pub fn require_group(group: G) -> Self {
Self {
role_requirements: Vec::new(),
group_requirements: vec![group],
permission_requirements: Permissions::new(),
}
}
pub fn require_permission<P: Into<PermissionId>>(permission: P) -> Self {
let mut permission_requirements = Permissions::new();
permission_requirements.grant(permission);
Self {
role_requirements: Vec::new(),
group_requirements: Vec::new(),
permission_requirements,
}
}
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 {
self.permission_requirements.grant(permission);
self
}
pub fn or_require_permissions<I, P>(mut self, permissions: I) -> Self
where
I: IntoIterator<Item = P>,
P: Into<PermissionId>,
{
for permission in permissions {
self.permission_requirements.grant(permission);
}
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::AccessPolicy;
use crate::groups::Group;
use crate::permissions::permission_id::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_exact_role_scope() {
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.role_requirements()[0].allows_supervisor_access());
assert!(policy.group_requirements().is_empty());
assert!(policy.permission_requirements().is_empty());
}
#[test]
fn require_role_or_supervisor_creates_hierarchical_scope() {
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].allows_supervisor_access());
}
#[test]
fn require_group_creates_group_requirement() {
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(), &[Group::new("engineering")]);
assert!(policy.permission_requirements().is_empty());
}
#[test]
fn require_permission_creates_permission_requirement() {
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_accumulate_all_requirement_types() {
let base_permissions = ["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_permissions[0])
.or_require_permissions([base_permissions[1], base_permissions[2]]);
assert!(!policy.denies_all());
assert!(policy.has_requirements());
assert_eq!(policy.role_requirements().len(), 2);
assert_eq!(policy.group_requirements(), &[Group::new("engineering")]);
for permission_name in base_permissions {
let id = PermissionId::from(permission_name).as_u64();
assert!(
policy
.permission_requirements()
.iter()
.any(|value| value == id),
"missing permission {}",
permission_name
);
}
}
#[test]
fn into_components_returns_owned_policy_parts() {
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, vec![Group::new("test")]);
assert!(permissions.iter().any(|id| id == expected));
}
}