use async_trait::async_trait;
use serde_json::Value;
use crate::error::{CapError, CapResult};
#[async_trait]
pub trait PermissionChecker: Send + Sync + 'static {
async fn check(&self, cap_name: &str, args: &Value, tenant_id: i64) -> CapResult<()>;
}
pub struct AllowAll;
#[async_trait]
impl PermissionChecker for AllowAll {
async fn check(&self, _cap_name: &str, _args: &Value, _tenant_id: i64) -> CapResult<()> {
Ok(())
}
}
pub struct TenantScopeChecker {
allowed: parking_lot::RwLock<std::collections::HashMap<String, std::collections::HashSet<i64>>>,
}
impl TenantScopeChecker {
pub fn new() -> Self {
Self {
allowed: parking_lot::RwLock::new(std::collections::HashMap::new()),
}
}
pub fn grant(&self, cap_name: &str, tenant_id: i64) {
let mut map = self.allowed.write();
map.entry(cap_name.to_string())
.or_default()
.insert(tenant_id);
}
pub fn revoke(&self, cap_name: &str, tenant_id: i64) {
let mut map = self.allowed.write();
if let Some(set) = map.get_mut(cap_name) {
set.remove(&tenant_id);
}
}
}
impl Default for TenantScopeChecker {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl PermissionChecker for TenantScopeChecker {
async fn check(&self, cap_name: &str, _args: &Value, tenant_id: i64) -> CapResult<()> {
let map = self.allowed.read();
match map.get(cap_name) {
Some(set) if set.contains(&tenant_id) => Ok(()),
_ => Err(CapError::PermissionDenied(format!(
"租户 {tenant_id} 无权调用能力 {cap_name}"
))),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_allow_all() {
let checker = AllowAll;
assert!(checker.check("any.cap", &Value::Null, 1).await.is_ok());
}
#[tokio::test]
async fn test_tenant_scope_grant_revoke() {
let checker = TenantScopeChecker::new();
checker.grant("cap.a", 100);
assert!(checker.check("cap.a", &Value::Null, 100).await.is_ok());
assert!(checker.check("cap.a", &Value::Null, 200).await.is_err());
checker.revoke("cap.a", 100);
assert!(checker.check("cap.a", &Value::Null, 100).await.is_err());
}
}