dbnexus 0.1.3

An enterprise-grade database abstraction layer for Rust with built-in permission control and connection pooling
// Copyright (c) 2026 Kirky.X
//
// Licensed under the MIT License
// See LICENSE file in the project root for full license information.

//! 简单权限提供者实现
//!
//! 基于内存的权限检查实现,适合大多数场景

use crate::permission_engine::{
    PermissionAction, PermissionContext, PermissionDecision, PermissionProvider as PermissionProviderTrait,
    PermissionResource,
};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;

/// 简单权限提供者配置
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SimplePermissionConfig {
    /// 角色到策略的映射
    pub roles: HashMap<String, SimpleRolePolicy>,
}

/// 简单角色策略
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SimpleRolePolicy {
    /// 角色允许的表权限
    pub tables: Vec<SimpleTablePermission>,
}

/// 简单表权限配置
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SimpleTablePermission {
    /// 表名(支持通配符 *)
    pub name: String,
    /// 允许的操作列表
    pub operations: Vec<String>,
}

/// 简单权限提供者实现
///
/// 基于内存的权限检查实现,适合大多数场景
#[derive(Debug, Clone)]
pub struct SimplePermissionProvider {
    /// 配置
    config: Arc<SimplePermissionConfig>,
}

impl SimplePermissionProvider {
    /// 创建新的权限提供者
    pub fn new(config: SimplePermissionConfig) -> Self {
        Self {
            config: Arc::new(config),
        }
    }

    /// 创建拒绝所有的安全默认配置
    pub fn deny_all() -> Self {
        Self::new(SimplePermissionConfig { roles: HashMap::new() })
    }

    /// 创建允许所有的配置(仅用于开发/测试环境)
    pub fn allow_all() -> Self {
        let mut admin_tables = vec![SimpleTablePermission {
            name: "*".to_string(),
            operations: vec![
                "SELECT".to_string(),
                "INSERT".to_string(),
                "UPDATE".to_string(),
                "DELETE".to_string(),
            ],
        }];
        admin_tables.extend(vec![
            SimpleTablePermission {
                name: "sqlite_master".to_string(),
                operations: vec!["SELECT".to_string()],
            },
            SimpleTablePermission {
                name: "information_schema.tables".to_string(),
                operations: vec!["SELECT".to_string()],
            },
        ]);

        let system_tables = admin_tables.clone();

        Self::new(SimplePermissionConfig {
            roles: HashMap::from([
                ("admin".to_string(), SimpleRolePolicy { tables: admin_tables }),
                ("system".to_string(), SimpleRolePolicy { tables: system_tables }),
            ]),
        })
    }

    /// 检查角色是否有权限执行操作(内部方法)
    fn check_access(&self, role: &str, table: &str, operation: &str) -> bool {
        if let Some(policy) = self.config.roles.get(role) {
            let normalized_table = table.to_lowercase();
            let is_system_table = Self::is_system_table(&normalized_table);

            for perm in &policy.tables {
                if perm.name == "*" {
                    if is_system_table {
                        continue;
                    }
                    if perm.operations.iter().any(|op| op.eq_ignore_ascii_case(operation)) {
                        return true;
                    }
                } else if perm.name.eq_ignore_ascii_case(table) {
                    if perm.operations.iter().any(|op| op.eq_ignore_ascii_case(operation)) {
                        return true;
                    }
                }
            }
        }
        false
    }

    /// 检查是否是系统表
    fn is_system_table(table: &str) -> bool {
        const SYSTEM_TABLES: &[&str] = &[
            "sqlite_master",
            "sqlite_sequence",
            "sqlite_stat1",
            "sqlite_stat2",
            "sqlite_stat3",
            "sqlite_stat4",
            "information_schema",
            "pg_catalog",
            "pg_toast",
            "mysql",
            "performance_schema",
            "sys",
        ];
        SYSTEM_TABLES.iter().any(|system_table| {
            table.eq_ignore_ascii_case(system_table) || table.starts_with(&format!("{}.", system_table))
        })
    }
}

#[async_trait]
impl PermissionProviderTrait for SimplePermissionProvider {
    async fn check_permission(&self, context: &PermissionContext) -> PermissionDecision {
        let subject_id = match &context.subject {
            crate::permission_engine::PermissionSubject { id, .. } => id.clone(),
        };

        let allowed = self.check_access(&subject_id, &context.resource.name, &context.action.to_string());
        if allowed {
            PermissionDecision::Allow
        } else {
            PermissionDecision::Deny
        }
    }

    async fn get_allowed_resources(&self, subject: &str) -> Vec<PermissionResource> {
        if let Some(policy) = self.config.roles.get(subject) {
            policy.tables.iter().map(|t| PermissionResource::new(&t.name)).collect()
        } else {
            Vec::new()
        }
    }

    async fn get_allowed_actions(&self, subject: &str, resource: &str) -> Vec<PermissionAction> {
        if let Some(policy) = self.config.roles.get(subject) {
            for table in &policy.tables {
                if table.name == "*" || table.name.eq_ignore_ascii_case(resource) {
                    return table
                        .operations
                        .iter()
                        .filter_map(|op| match op.to_uppercase().as_str() {
                            "SELECT" => Some(PermissionAction::Select),
                            "INSERT" => Some(PermissionAction::Insert),
                            "UPDATE" => Some(PermissionAction::Update),
                            "DELETE" => Some(PermissionAction::Delete),
                            _ => None,
                        })
                        .collect();
                }
            }
        }
        Vec::new()
    }

    async fn refresh(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
        Ok(())
    }

    fn name(&self) -> &str {
        "SimplePermissionProvider"
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::permission_engine::{PermissionAction, PermissionResource, PermissionSubject};

    #[test]
    fn test_simple_permission_provider_allow_all() {
        let provider = SimplePermissionProvider::allow_all();

        // admin 应该能访问所有表
        let ctx = PermissionContext::new(
            PermissionSubject::role("admin"),
            PermissionResource::new("users"),
            PermissionAction::Select,
        );
        let decision = futures::executor::block_on(provider.check_permission(&ctx));
        assert_eq!(decision, PermissionDecision::Allow);

        let ctx = PermissionContext::new(
            PermissionSubject::role("admin"),
            PermissionResource::new("orders"),
            PermissionAction::Delete,
        );
        let decision = futures::executor::block_on(provider.check_permission(&ctx));
        assert_eq!(decision, PermissionDecision::Allow);
    }

    #[test]
    fn test_simple_permission_provider_deny_all() {
        let provider = SimplePermissionProvider::deny_all();

        let ctx = PermissionContext::new(
            PermissionSubject::role("admin"),
            PermissionResource::new("users"),
            PermissionAction::Select,
        );
        let decision = futures::executor::block_on(provider.check_permission(&ctx));
        assert_eq!(decision, PermissionDecision::Deny);
    }
}