Skip to main content

sz_rust_capability/
permission.rs

1use async_trait::async_trait;
2use serde_json::Value;
3
4use crate::error::{CapError, CapResult};
5
6/// 权限检查器 trait,在能力调用前执行授权决策。
7///
8/// 实现方根据 `cap_name`、`args`、`tenant_id` 判断是否放行。
9/// 所有实现必须 `Send + Sync + 'static` 以支持并发调用。
10#[async_trait]
11pub trait PermissionChecker: Send + Sync + 'static {
12    /// 检查指定租户是否有权调用该能力。
13    ///
14    /// 返回 `Ok(())` 表示放行,返回 `Err(CapError::PermissionDenied(_))` 表示拒绝。
15    async fn check(&self, cap_name: &str, args: &Value, tenant_id: i64) -> CapResult<()>;
16}
17
18/// 默认放行检查器,用于测试和未配置权限的场景。
19pub struct AllowAll;
20
21#[async_trait]
22impl PermissionChecker for AllowAll {
23    async fn check(&self, _cap_name: &str, _args: &Value, _tenant_id: i64) -> CapResult<()> {
24        Ok(())
25    }
26}
27
28/// 基于租户范围的权限检查器。
29///
30/// 维护一张"能力名 → 允许的租户 ID 集合"映射,
31/// 仅当调用方 `tenant_id` 在允许集合中时放行。
32pub struct TenantScopeChecker {
33    allowed: parking_lot::RwLock<std::collections::HashMap<String, std::collections::HashSet<i64>>>,
34}
35
36impl TenantScopeChecker {
37    pub fn new() -> Self {
38        Self {
39            allowed: parking_lot::RwLock::new(std::collections::HashMap::new()),
40        }
41    }
42
43    /// 授权指定租户调用指定能力。
44    pub fn grant(&self, cap_name: &str, tenant_id: i64) {
45        let mut map = self.allowed.write();
46        map.entry(cap_name.to_string())
47            .or_default()
48            .insert(tenant_id);
49    }
50
51    /// 撤销指定租户对指定能力的调用权限。
52    pub fn revoke(&self, cap_name: &str, tenant_id: i64) {
53        let mut map = self.allowed.write();
54        if let Some(set) = map.get_mut(cap_name) {
55            set.remove(&tenant_id);
56        }
57    }
58}
59
60impl Default for TenantScopeChecker {
61    fn default() -> Self {
62        Self::new()
63    }
64}
65
66#[async_trait]
67impl PermissionChecker for TenantScopeChecker {
68    async fn check(&self, cap_name: &str, _args: &Value, tenant_id: i64) -> CapResult<()> {
69        let map = self.allowed.read();
70        match map.get(cap_name) {
71            Some(set) if set.contains(&tenant_id) => Ok(()),
72            _ => Err(CapError::PermissionDenied(format!(
73                "租户 {tenant_id} 无权调用能力 {cap_name}"
74            ))),
75        }
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[tokio::test]
84    async fn test_allow_all() {
85        let checker = AllowAll;
86        assert!(checker.check("any.cap", &Value::Null, 1).await.is_ok());
87    }
88
89    #[tokio::test]
90    async fn test_tenant_scope_grant_revoke() {
91        let checker = TenantScopeChecker::new();
92        checker.grant("cap.a", 100);
93        assert!(checker.check("cap.a", &Value::Null, 100).await.is_ok());
94        assert!(checker.check("cap.a", &Value::Null, 200).await.is_err());
95        checker.revoke("cap.a", 100);
96        assert!(checker.check("cap.a", &Value::Null, 100).await.is_err());
97    }
98}