use std::collections::HashMap;
use std::sync::{Arc, RwLock};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AclPermission(u32);
impl AclPermission {
pub const READ: AclPermission = AclPermission(1);
pub const WRITE: AclPermission = AclPermission(1 << 1);
pub const CREATE: AclPermission = AclPermission(1 << 2);
pub const DELETE: AclPermission = AclPermission(1 << 3);
pub const ADMIN: AclPermission = AclPermission(1 << 4);
pub fn from_bits(bits: u32) -> Self {
Self(bits)
}
pub fn includes(&self, other: AclPermission) -> bool {
(self.0 & other.0) != 0
}
pub fn union(self, other: AclPermission) -> Self {
Self(self.0 | other.0)
}
pub fn bits(&self) -> u32 {
self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum AclSid {
Principal(String),
Authority(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AclObjectIdentity {
pub object_type: String,
pub object_id: String,
}
impl AclObjectIdentity {
pub fn new(object_type: impl Into<String>, object_id: impl Into<String>) -> Self {
Self {
object_type: object_type.into(),
object_id: object_id.into(),
}
}
}
#[derive(Debug, Clone)]
pub struct AclEntry {
pub sid: AclSid,
pub permission: AclPermission,
pub granting: bool,
}
impl AclEntry {
pub fn grant(sid: AclSid, permission: AclPermission) -> Self {
Self {
sid,
permission,
granting: true,
}
}
pub fn deny(sid: AclSid, permission: AclPermission) -> Self {
Self {
sid,
permission,
granting: false,
}
}
pub fn matches(&self, sid: &AclSid, permission: AclPermission) -> bool {
&self.sid == sid && self.permission.includes(permission)
}
}
#[derive(Debug, Clone)]
pub struct Acl {
pub object_identity: AclObjectIdentity,
pub owner: AclSid,
pub entries: Vec<AclEntry>,
pub inherit: bool,
pub parent: Option<AclObjectIdentity>,
}
impl Acl {
pub fn new(object_identity: AclObjectIdentity, owner: AclSid) -> Self {
Self {
object_identity,
owner,
entries: Vec::new(),
inherit: false,
parent: None,
}
}
pub fn add_entry(&mut self, entry: AclEntry) {
self.entries.push(entry);
}
pub fn remove_entries_for_sid(&mut self, sid: &AclSid) {
self.entries.retain(|e| &e.sid != sid);
}
pub fn is_granted(&self, sid: &AclSid, permission: AclPermission) -> bool {
if &self.owner == sid {
return true;
}
let mut granted = false;
for entry in &self.entries {
if entry.matches(sid, permission) {
if !entry.granting {
return false;
}
granted = true;
}
}
granted
}
}
pub struct AclService {
acls: Arc<RwLock<HashMap<(String, String), Acl>>>,
}
impl Default for AclService {
fn default() -> Self {
Self::new()
}
}
impl AclService {
pub fn new() -> Self {
Self {
acls: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn save_acl(&self, acl: Acl) {
let key = (acl.object_identity.object_type.clone(), acl.object_identity.object_id.clone());
self.acls.write().unwrap().insert(key, acl);
}
pub fn get_acl(&self, oid: &AclObjectIdentity) -> Option<Acl> {
self.acls
.read()
.unwrap()
.get(&(oid.object_type.clone(), oid.object_id.clone()))
.cloned()
}
pub fn delete_acl(&self, oid: &AclObjectIdentity) -> bool {
self.acls
.write()
.unwrap()
.remove(&(oid.object_type.clone(), oid.object_id.clone()))
.is_some()
}
pub fn is_granted(
&self,
oid: &AclObjectIdentity,
sid: &AclSid,
permission: AclPermission,
) -> bool {
self.acls
.read()
.unwrap()
.get(&(oid.object_type.clone(), oid.object_id.clone()))
.is_some_and(|acl| acl.is_granted(sid, permission))
}
pub fn list_acls(&self) -> Vec<Acl> {
self.acls.read().unwrap().values().cloned().collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_permission_flags() {
let p = AclPermission::READ.union(AclPermission::WRITE);
assert!(p.includes(AclPermission::READ));
assert!(p.includes(AclPermission::WRITE));
assert!(!p.includes(AclPermission::DELETE));
}
#[test]
fn test_acl_owner_always_granted() {
let oid = AclObjectIdentity::new("Document", "1");
let owner = AclSid::Principal("alice".into());
let acl = Acl::new(oid, owner.clone());
assert!(acl.is_granted(&owner, AclPermission::DELETE));
}
#[test]
fn test_acl_grant_entry() {
let oid = AclObjectIdentity::new("Document", "1");
let owner = AclSid::Principal("alice".into());
let mut acl = Acl::new(oid, owner);
let bob = AclSid::Principal("bob".into());
acl.add_entry(AclEntry::grant(bob.clone(), AclPermission::READ));
assert!(acl.is_granted(&bob, AclPermission::READ));
assert!(!acl.is_granted(&bob, AclPermission::WRITE));
}
#[test]
fn test_acl_deny_entry() {
let oid = AclObjectIdentity::new("Document", "1");
let owner = AclSid::Principal("alice".into());
let mut acl = Acl::new(oid, owner);
let bob = AclSid::Principal("bob".into());
acl.add_entry(AclEntry::grant(
bob.clone(),
AclPermission::READ.union(AclPermission::WRITE),
));
acl.add_entry(AclEntry::deny(bob.clone(), AclPermission::WRITE));
assert!(acl.is_granted(&bob, AclPermission::READ));
assert!(!acl.is_granted(&bob, AclPermission::WRITE));
}
#[test]
fn test_acl_service_crud() {
let service = AclService::new();
let oid = AclObjectIdentity::new("Order", "42");
let owner = AclSid::Principal("alice".into());
service.save_acl(Acl::new(oid.clone(), owner.clone()));
assert!(service.get_acl(&oid).is_some());
assert!(service.is_granted(&oid, &owner, AclPermission::ADMIN));
assert!(service.delete_acl(&oid));
assert!(service.get_acl(&oid).is_none());
}
#[test]
fn test_acl_service_authority_sid() {
let service = AclService::new();
let oid = AclObjectIdentity::new("Report", "r1");
let owner = AclSid::Principal("admin".into());
let mut acl = Acl::new(oid.clone(), owner);
let editors = AclSid::Authority("ROLE_EDITOR".into());
acl.add_entry(AclEntry::grant(
editors.clone(),
AclPermission::READ.union(AclPermission::WRITE),
));
service.save_acl(acl);
assert!(service.is_granted(&oid, &editors, AclPermission::READ));
assert!(!service.is_granted(&oid, &editors, AclPermission::DELETE));
}
}