use crate::domain::permission::{
PermissionAction, PermissionChecker, PermissionConfig, PermissionError, PermissionLifecycle, PermissionProvider,
PolicyManager, RolePolicy,
};
use async_trait::async_trait;
use std::collections::HashMap;
use std::sync::RwLock;
pub struct YamlPermissionProvider {
config: PermissionConfig,
policies: RwLock<HashMap<String, RolePolicy>>,
#[cfg(feature = "cache")]
cache: Option<std::sync::Arc<oxcache::Cache<String, RolePolicy>>>,
}
impl YamlPermissionProvider {
pub async fn new(config: PermissionConfig) -> Result<Self, PermissionError> {
let provider = Self {
config: config.clone(),
policies: RwLock::new(HashMap::new()),
#[cfg(feature = "cache")]
cache: None,
};
if config.policy_path.is_some() {
provider.load_policies().await?;
}
Ok(provider)
}
#[cfg(feature = "cache")]
pub async fn with_cache(
config: PermissionConfig,
cache: std::sync::Arc<oxcache::Cache<String, RolePolicy>>,
) -> Result<Self, PermissionError> {
let provider = Self {
config: config.clone(),
policies: RwLock::new(HashMap::new()),
cache: Some(cache),
};
if config.policy_path.is_some() {
provider.load_policies().await?;
}
Ok(provider)
}
async fn load_policies(&self) -> Result<(), PermissionError> {
if let Some(path) = &self.config.policy_path {
let content = tokio::fs::read_to_string(path)
.await
.map_err(|e| PermissionError::ParseError(format!("Failed to read {}: {}", path, e)))?;
let policies: HashMap<String, RolePolicy> =
serde_yaml_ng::from_str(&content).map_err(|e| PermissionError::ParseError(e.to_string()))?;
#[cfg(feature = "cache")]
if let Some(cache) = &self.cache {
for (role, policy) in &policies {
let _ = cache.set(role, policy).await;
}
}
let mut guard = self.policies.write().unwrap();
*guard = policies;
}
Ok(())
}
}
#[async_trait]
impl PermissionChecker for YamlPermissionProvider {
async fn check(&self, role: &str, table: &str, action: PermissionAction) -> Result<bool, PermissionError> {
if role == self.config.admin_role {
return Ok(true);
}
let guard = self.policies.read().unwrap();
if let Some(policy) = guard.get(role) {
Ok(policy.allows(table, &action))
} else {
match self.config.default_policy {
crate::domain::permission::DefaultPolicy::AllowAll => Ok(true),
crate::domain::permission::DefaultPolicy::DenyAll => Ok(false),
}
}
}
}
#[async_trait]
impl PolicyManager for YamlPermissionProvider {
async fn get_policy(&self, role: &str) -> Result<Option<RolePolicy>, PermissionError> {
let guard = self.policies.read().unwrap();
Ok(guard.get(role).cloned())
}
async fn refresh(&self) -> Result<(), PermissionError> {
self.load_policies().await
}
}
#[async_trait]
impl PermissionLifecycle for YamlPermissionProvider {
async fn health_check(&self) -> anyhow::Result<()> {
Ok(())
}
async fn shutdown(&self) {
let mut guard = self.policies.write().unwrap();
guard.clear();
}
}
impl PermissionProvider for YamlPermissionProvider {}