use crate::cache::{InMemoryPermissionCache, PermissionCacheBackend};
use crate::traits::*;
use crate::types::*;
use async_trait::async_trait;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::{debug, info};
pub struct AuthorizationService {
role_permissions: Arc<RwLock<HashMap<String, HashSet<Permission>>>>,
user_roles: Arc<RwLock<HashMap<String, HashSet<String>>>>,
cache: Arc<dyn PermissionCacheBackend>,
config: AuthorizationConfig,
}
impl Default for AuthorizationService {
fn default() -> Self {
Self::new()
}
}
impl AuthorizationService {
pub fn new() -> Self {
Self::with_cache(Arc::new(InMemoryPermissionCache::new()))
}
pub fn with_cache(cache: Arc<dyn PermissionCacheBackend>) -> Self {
Self::with_config(AuthorizationConfig::default(), cache)
}
pub fn with_config(config: AuthorizationConfig, cache: Arc<dyn PermissionCacheBackend>) -> Self {
let service = Self {
role_permissions: Arc::new(RwLock::new(HashMap::new())),
user_roles: Arc::new(RwLock::new(HashMap::new())),
cache,
config,
};
let role_perms = service.role_permissions.clone();
tokio::spawn(async move {
Self::initialize_default_roles_static(role_perms).await;
});
service
}
async fn initialize_default_roles_static(
role_permissions: Arc<RwLock<HashMap<String, HashSet<Permission>>>>,
) {
info!("Initializing default RBAC roles and permissions");
let mut role_perms = role_permissions.write().await;
let super_admin_perms: HashSet<Permission> = Permission::all_permissions();
role_perms.insert("super_admin".to_string(), super_admin_perms);
let admin_perms: HashSet<Permission> = Permission::all_permissions()
.into_iter()
.filter(|p| !matches!(p, Permission::User(_)))
.collect();
role_perms.insert("admin".to_string(), admin_perms);
let user_perms: HashSet<Permission> = [
Permission::User(UserAction::Read),
Permission::Settings(SettingsAction::Read),
Permission::Role(RoleAction::Read),
Permission::Permission(PermissionAction::Read),
]
.into_iter()
.collect();
role_perms.insert("user".to_string(), user_perms);
let guest_perms: HashSet<Permission> = [
Permission::User(UserAction::Read),
Permission::Settings(SettingsAction::Read),
]
.into_iter()
.collect();
role_perms.insert("guest".to_string(), guest_perms);
info!("Initialized {} default roles", role_perms.len());
}
async fn get_cached_permissions(&self, user_id: &str) -> HashSet<Permission> {
if self.config.enable_permission_caching {
if let Ok(Some(permissions)) = self.cache.get(user_id).await {
debug!("Using cached permissions for user '{}'", user_id);
return permissions;
}
}
let permissions = self.fetch_user_permissions(user_id).await;
if self.config.enable_permission_caching {
if let Err(e) = self.cache.set(user_id, &permissions, self.config.cache_ttl_seconds).await {
tracing::warn!("Failed to cache permissions for user '{}': {}", user_id, e);
}
}
permissions
}
async fn fetch_user_permissions(&self, user_id: &str) -> HashSet<Permission> {
let roles = self.user_roles.read().await;
let user_roles = roles.get(user_id).cloned().unwrap_or_default();
let role_perms = self.role_permissions.read().await;
let mut permissions = HashSet::new();
for role in user_roles {
if let Some(role_perms) = role_perms.get(&role) {
permissions.extend(role_perms.clone());
}
}
if permissions.is_empty() {
if let Some(guest_perms) = role_perms.get("guest") {
permissions.extend(guest_perms.clone());
}
}
permissions
}
pub async fn clear_user_cache(&self, user_id: &str) {
if let Err(e) = self.cache.delete(user_id).await {
tracing::warn!("Failed to clear cache for user '{}': {}", user_id, e);
}
debug!("Cleared permission cache for user '{}'", user_id);
}
pub async fn clear_all_cache(&self) {
if let Err(e) = self.cache.clear().await {
tracing::warn!("Failed to clear all permission caches: {}", e);
}
info!("Cleared all permission caches");
}
}
#[async_trait]
impl AuthorizationServiceTrait for AuthorizationService {
async fn check_authorization(
&self,
request: AuthorizationRequest,
) -> Result<AuthorizationResponse, AuthorizationError> {
let user_id = &request.user.user_id;
let permissions = self.get_cached_permissions(user_id).await;
let required_permission = Permission::from_action_resource(
request.action.clone(),
request.resource.clone(),
);
let has_permission = permissions.contains(&required_permission);
let response = AuthorizationResponse {
allowed: has_permission,
checks: vec![PermissionCheck {
allowed: has_permission,
permission: required_permission.clone(),
reason: if has_permission {
"Permission granted".to_string()
} else {
format!("Missing permission: {:?}", required_permission)
},
}],
};
debug!(
"Authorization check for user '{}': {}",
user_id,
if has_permission { "ALLOWED" } else { "DENIED" }
);
Ok(response)
}
async fn grant_permission_to_role(
&self,
role: &str,
permission: Permission,
) -> Result<(), AuthorizationError> {
info!("Granted permission {:?} to role '{}'", permission, role);
let mut role_perms = self.role_permissions.write().await;
role_perms
.entry(role.to_string())
.or_insert_with(HashSet::new)
.insert(permission);
Ok(())
}
async fn revoke_permission_from_role(
&self,
role: &str,
permission: &Permission,
) -> Result<(), AuthorizationError> {
let mut role_perms = self.role_permissions.write().await;
if let Some(perms) = role_perms.get_mut(role) {
perms.remove(permission);
info!("Revoked permission {:?} from role '{}'", permission, role);
}
Ok(())
}
async fn assign_role_to_user(
&self,
user_id: &str,
role: &str,
) -> Result<(), AuthorizationError> {
let mut user_roles = self.user_roles.write().await;
user_roles
.entry(user_id.to_string())
.or_insert_with(HashSet::new)
.insert(role.to_string());
self.clear_user_cache(user_id).await;
info!("Assigned role '{}' to user '{}'", role, user_id);
Ok(())
}
async fn remove_role_from_user(
&self,
user_id: &str,
role: &str,
) -> Result<(), AuthorizationError> {
let mut user_roles = self.user_roles.write().await;
if let Some(roles) = user_roles.get_mut(user_id) {
roles.remove(role);
}
self.clear_user_cache(user_id).await;
info!("Removed role '{}' from user '{}'", role, user_id);
Ok(())
}
async fn get_user_permissions(
&self,
user_id: &str,
) -> Result<Vec<Permission>, AuthorizationError> {
let permissions = self.get_cached_permissions(user_id).await;
Ok(permissions.into_iter().collect())
}
async fn user_has_permission(
&self,
user_id: &str,
permission: &Permission,
) -> Result<bool, AuthorizationError> {
let permissions = self.get_cached_permissions(user_id).await;
Ok(permissions.contains(permission))
}
}
pub type InMemoryAuthService = AuthorizationService;