use crate::SecurityResult;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionDef {
pub name: String,
pub resource: String,
pub action: String,
pub description: String,
}
impl PermissionDef {
pub fn new(
name: impl Into<String>,
resource: impl Into<String>,
action: impl Into<String>,
description: impl Into<String>,
) -> Self {
Self {
name: name.into(),
resource: resource.into(),
action: action.into(),
description: description.into(),
}
}
}
#[derive(Debug, Clone)]
pub struct PermissionRegistry {
permissions: HashMap<String, PermissionDef>,
role_permissions: HashMap<String, HashSet<String>>,
}
impl Default for PermissionRegistry {
fn default() -> Self {
Self::new()
}
}
impl PermissionRegistry {
pub fn new() -> Self {
Self {
permissions: HashMap::new(),
role_permissions: HashMap::new(),
}
}
pub fn register(
&mut self,
name: impl Into<String>,
resource: impl Into<String>,
action: impl Into<String>,
description: impl Into<String>,
) {
let name = name.into();
let def = PermissionDef::new(name.clone(), resource, action, description);
self.permissions.insert(name, def);
}
pub fn register_def(&mut self, def: PermissionDef) {
self.permissions.insert(def.name.clone(), def);
}
pub fn grant_role_permission(
&mut self,
role: impl Into<String>,
permission: impl Into<String>,
) {
self.role_permissions
.entry(role.into())
.or_default()
.insert(permission.into());
}
pub fn grant_role_permissions(
&mut self,
role: impl Into<String>,
permissions: impl IntoIterator<Item = impl Into<String>>,
) {
let set = self.role_permissions.entry(role.into()).or_default();
for p in permissions {
set.insert(p.into());
}
}
pub fn revoke_role_permission(&mut self, role: &str, permission: &str) -> bool {
if let Some(perms) = self.role_permissions.get_mut(role) {
perms.remove(permission)
} else {
false
}
}
pub fn remove_role(&mut self, role: &str) -> bool {
self.role_permissions.remove(role).is_some()
}
pub fn has_permission(&self, permission: &str) -> bool {
self.permissions.contains_key(permission)
}
pub fn get_permission(&self, name: &str) -> Option<&PermissionDef> {
self.permissions.get(name)
}
pub fn get_role_permissions(&self, role: &str) -> HashSet<&str> {
self.role_permissions
.get(role)
.map(|s| s.iter().map(String::as_str).collect())
.unwrap_or_default()
}
pub fn all_permission_names(&self) -> Vec<&str> {
self.permissions.keys().map(String::as_str).collect()
}
pub fn all_roles(&self) -> Vec<&str> {
self.role_permissions.keys().map(String::as_str).collect()
}
pub fn effective_permissions(&self, roles: &[String]) -> HashSet<String> {
let mut perms = HashSet::new();
for role in roles {
if let Some(role_perms) = self.role_permissions.get(role) {
perms.extend(role_perms.iter().cloned());
}
}
perms
}
}
#[derive(Clone)]
pub struct PermissionEvaluator {
registry: Arc<RwLock<PermissionRegistry>>,
cache: Arc<RwLock<HashMap<String, bool>>>,
}
impl Default for PermissionEvaluator {
fn default() -> Self {
Self::new(PermissionRegistry::new())
}
}
impl fmt::Debug for PermissionEvaluator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PermissionEvaluator")
.field("registry", &"<PermissionRegistry>")
.field("cache", &"<cache>")
.finish()
}
}
impl PermissionEvaluator {
pub fn new(registry: PermissionRegistry) -> Self {
Self {
registry: Arc::new(RwLock::new(registry)),
cache: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn from_shared(registry: Arc<RwLock<PermissionRegistry>>) -> Self {
Self {
registry,
cache: Arc::new(RwLock::new(HashMap::new())),
}
}
fn cache_key(roles: &[String], permission: &str) -> String {
let mut sorted = roles.to_vec();
sorted.sort();
format!("{}|{}", sorted.join(","), permission)
}
pub async fn has_permission(&self, user_roles: &[String], permission: &str) -> bool {
let key = Self::cache_key(user_roles, permission);
{
let cache = self.cache.read().await;
if let Some(&result) = cache.get(&key) {
return result;
}
}
let registry = self.registry.read().await;
let result = registry
.effective_permissions(user_roles)
.contains(permission);
{
let mut cache = self.cache.write().await;
cache.insert(key, result);
}
result
}
pub async fn has_any_permission(&self, user_roles: &[String], permissions: &[&str]) -> bool {
for perm in permissions {
if self.has_permission(user_roles, perm).await {
return true;
}
}
false
}
pub async fn has_all_permissions(&self, user_roles: &[String], permissions: &[&str]) -> bool {
for perm in permissions {
if !self.has_permission(user_roles, perm).await {
return false;
}
}
true
}
pub async fn invalidate_cache(&self) {
self.cache.write().await.clear();
}
pub async fn registry(&self) -> Arc<RwLock<PermissionRegistry>> {
self.registry.clone()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionAuditEntry {
pub timestamp: DateTime<Utc>,
pub user: String,
pub permission: String,
pub granted: bool,
pub resource: Option<String>,
}
impl PermissionAuditEntry {
pub fn new(
user: impl Into<String>,
permission: impl Into<String>,
granted: bool,
resource: Option<String>,
) -> Self {
Self {
timestamp: Utc::now(),
user: user.into(),
permission: permission.into(),
granted,
resource,
}
}
}
#[async_trait::async_trait]
pub trait PermissionAuditLog: Send + Sync {
async fn log_access(&self, entry: PermissionAuditEntry) -> SecurityResult<()>;
}
#[derive(Debug, Clone, Default)]
pub struct PermissionAuditLogger;
impl PermissionAuditLogger {
pub fn new() -> Self {
Self
}
}
#[async_trait::async_trait]
impl PermissionAuditLog for PermissionAuditLogger {
async fn log_access(&self, entry: PermissionAuditEntry) -> SecurityResult<()> {
let status = if entry.granted { "GRANTED" } else { "DENIED" };
tracing::info!(
"[PERM-AUDIT] {} | User: {} | Permission: {} | Resource: {:?}",
status,
entry.user,
entry.permission,
entry.resource,
);
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct InMemoryPermissionAuditLogger {
entries: Arc<RwLock<Vec<PermissionAuditEntry>>>,
}
impl Default for InMemoryPermissionAuditLogger {
fn default() -> Self {
Self::new()
}
}
impl InMemoryPermissionAuditLogger {
pub fn new() -> Self {
Self {
entries: Arc::new(RwLock::new(Vec::new())),
}
}
pub async fn entries(&self) -> Vec<PermissionAuditEntry> {
self.entries.read().await.clone()
}
pub async fn clear(&self) {
self.entries.write().await.clear();
}
pub async fn len(&self) -> usize {
self.entries.read().await.len()
}
pub async fn is_empty(&self) -> bool {
self.entries.read().await.is_empty()
}
}
#[async_trait::async_trait]
impl PermissionAuditLog for InMemoryPermissionAuditLogger {
async fn log_access(&self, entry: PermissionAuditEntry) -> SecurityResult<()> {
self.entries.write().await.push(entry);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_register_permission() {
let mut reg = PermissionRegistry::new();
reg.register("user:read", "user", "read", "Read user data");
assert!(reg.has_permission("user:read"));
assert!(!reg.has_permission("user:write"));
}
#[test]
fn test_register_permission_def() {
let mut reg = PermissionRegistry::new();
let def = PermissionDef::new("doc:write", "doc", "write", "Write document");
reg.register_def(def);
assert!(reg.has_permission("doc:write"));
let fetched = reg.get_permission("doc:write").unwrap();
assert_eq!(fetched.resource, "doc");
}
#[test]
fn test_grant_and_revoke_role_permission() {
let mut reg = PermissionRegistry::new();
reg.register("user:read", "user", "read", "Read");
reg.register("user:write", "user", "write", "Write");
reg.grant_role_permission("USER", "user:read");
reg.grant_role_permission("USER", "user:write");
let perms = reg.get_role_permissions("USER");
assert_eq!(perms.len(), 2);
assert!(reg.revoke_role_permission("USER", "user:write"));
let perms = reg.get_role_permissions("USER");
assert_eq!(perms.len(), 1);
assert!(!reg.revoke_role_permission("USER", "nonexistent"));
}
#[test]
fn test_grant_role_permissions_batch() {
let mut reg = PermissionRegistry::new();
reg.register("a:1", "a", "1", "");
reg.register("a:2", "a", "2", "");
reg.grant_role_permissions("ADMIN", vec!["a:1", "a:2"]);
let perms = reg.get_role_permissions("ADMIN");
assert_eq!(perms.len(), 2);
}
#[test]
fn test_remove_role() {
let mut reg = PermissionRegistry::new();
reg.grant_role_permission("TEMP", "x:y");
assert!(reg.remove_role("TEMP"));
assert!(!reg.remove_role("TEMP"));
}
#[test]
fn test_effective_permissions() {
let mut reg = PermissionRegistry::new();
reg.register("r1", "res", "read", "");
reg.register("r2", "res", "write", "");
reg.grant_role_permission("ADMIN", "r1");
reg.grant_role_permission("USER", "r2");
let roles = vec!["ADMIN".to_string(), "USER".to_string()];
let effective = reg.effective_permissions(&roles);
assert_eq!(effective.len(), 2);
}
#[test]
fn test_effective_permissions_empty_roles() {
let reg = PermissionRegistry::new();
let effective = reg.effective_permissions(&[]);
assert!(effective.is_empty());
}
#[test]
fn test_all_permission_names() {
let mut reg = PermissionRegistry::new();
reg.register("a", "x", "y", "");
reg.register("b", "x", "z", "");
let names = reg.all_permission_names();
assert_eq!(names.len(), 2);
}
#[test]
fn test_all_roles() {
let mut reg = PermissionRegistry::new();
reg.grant_role_permission("A", "x");
reg.grant_role_permission("B", "y");
let roles = reg.all_roles();
assert_eq!(roles.len(), 2);
}
fn make_evaluator() -> PermissionEvaluator {
let mut reg = PermissionRegistry::new();
reg.register("user:read", "user", "read", "Read users");
reg.register("user:write", "user", "write", "Write users");
reg.register("admin:panel", "admin", "panel", "Admin panel");
reg.grant_role_permission("USER", "user:read");
reg.grant_role_permission("ADMIN", "user:read");
reg.grant_role_permission("ADMIN", "user:write");
reg.grant_role_permission("ADMIN", "admin:panel");
PermissionEvaluator::new(reg)
}
#[tokio::test]
async fn test_has_permission_granted() {
let ev = make_evaluator();
assert!(ev.has_permission(&["USER".to_string()], "user:read").await);
}
#[tokio::test]
async fn test_has_permission_denied() {
let ev = make_evaluator();
assert!(!ev.has_permission(&["USER".to_string()], "user:write").await);
}
#[tokio::test]
async fn test_has_permission_unknown_role() {
let ev = make_evaluator();
assert!(!ev.has_permission(&["GUEST".to_string()], "user:read").await);
}
#[tokio::test]
async fn test_has_permission_empty_roles() {
let ev = make_evaluator();
assert!(!ev.has_permission(&[], "user:read").await);
}
#[tokio::test]
async fn test_has_any_permission() {
let ev = make_evaluator();
assert!(
ev.has_any_permission(&["USER".to_string()], &["user:write", "user:read"],)
.await
);
assert!(
!ev.has_any_permission(&["GUEST".to_string()], &["admin:panel", "user:write"],)
.await
);
}
#[tokio::test]
async fn test_has_all_permissions() {
let ev = make_evaluator();
assert!(
ev.has_all_permissions(
&["ADMIN".to_string()],
&["user:read", "user:write", "admin:panel"],
)
.await
);
assert!(
!ev.has_all_permissions(&["USER".to_string()], &["user:read", "user:write"],)
.await
);
}
#[tokio::test]
async fn test_has_all_permissions_empty_list() {
let ev = make_evaluator();
assert!(ev.has_all_permissions(&[], &[]).await);
}
#[tokio::test]
async fn test_cache_is_used() {
let ev = make_evaluator();
assert!(ev.has_permission(&["USER".to_string()], "user:read").await);
{
let reg_arc = ev.registry().await;
let mut reg = reg_arc.write().await;
reg.revoke_role_permission("USER", "user:read");
}
assert!(ev.has_permission(&["USER".to_string()], "user:read").await);
ev.invalidate_cache().await;
assert!(!ev.has_permission(&["USER".to_string()], "user:read").await);
}
#[tokio::test]
async fn test_invalidate_cache() {
let ev = make_evaluator();
ev.has_permission(&["USER".to_string()], "user:read").await;
ev.invalidate_cache().await;
assert!(ev.has_permission(&["USER".to_string()], "user:read").await);
}
#[tokio::test]
async fn test_cache_key_order_independent() {
let ev = make_evaluator();
let a = ev.has_permission(&["USER".to_string()], "user:read").await;
let b = ev
.has_permission(&["ADMIN".to_string(), "USER".to_string()], "user:read")
.await;
assert!(a);
assert!(b);
}
#[test]
fn test_evaluator_debug() {
let ev = make_evaluator();
let debug = format!("{:?}", ev);
assert!(debug.contains("PermissionEvaluator"));
}
#[test]
fn test_audit_entry_new() {
let entry =
PermissionAuditEntry::new("alice", "user:read", true, Some("doc/1".to_string()));
assert_eq!(entry.user, "alice");
assert!(entry.granted);
assert_eq!(entry.resource.as_deref(), Some("doc/1"));
}
#[test]
fn test_audit_entry_serialization() {
let entry = PermissionAuditEntry::new("bob", "user:write", false, None);
let json = serde_json::to_string(&entry).unwrap();
let deserialized: PermissionAuditEntry = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.user, "bob");
assert!(!deserialized.granted);
}
#[tokio::test]
async fn test_tracing_audit_logger_does_not_error() {
let logger = PermissionAuditLogger::new();
let entry = PermissionAuditEntry::new("test", "p", true, None);
assert!(logger.log_access(entry).await.is_ok());
}
#[tokio::test]
async fn test_in_memory_audit_logger() {
let logger = InMemoryPermissionAuditLogger::new();
assert!(logger.is_empty().await);
logger
.log_access(PermissionAuditEntry::new("alice", "user:read", true, None))
.await
.unwrap();
logger
.log_access(PermissionAuditEntry::new(
"bob",
"user:write",
false,
Some("doc".to_string()),
))
.await
.unwrap();
assert_eq!(logger.len().await, 2);
let entries = logger.entries().await;
assert_eq!(entries[0].user, "alice");
assert!(entries[0].granted);
assert_eq!(entries[1].user, "bob");
assert!(!entries[1].granted);
}
#[tokio::test]
async fn test_in_memory_audit_logger_clear() {
let logger = InMemoryPermissionAuditLogger::new();
logger
.log_access(PermissionAuditEntry::new("u", "p", true, None))
.await
.unwrap();
assert_eq!(logger.len().await, 1);
logger.clear().await;
assert!(logger.is_empty().await);
}
}