use crate::utils::authz::filter_permissions_by_enabled_apps; use common_telemetry::AppError; use sqlx::PgPool; use tracing::instrument; use uuid::Uuid; #[derive(Clone)] pub struct AuthorizationService { pool: PgPool, } impl AuthorizationService { /// 创建权限服务实例。 pub fn new(pool: PgPool) -> Self { Self { pool } } #[instrument(skip(self))] /// 获取用户在指定租户下的权限编码集合(去重)。 /// /// 说明: /// - 权限来源于用户所属角色(user_roles → roles)及角色绑定权限(role_permissions → permissions)。 /// /// 输入: /// - `tenant_id`:租户 ID /// - `user_id`:用户 ID /// /// 输出: /// - 权限编码数组(如 `tenant:read` / `user:write`) /// /// 异常: /// - 数据库查询失败 pub async fn list_permissions_for_user( &self, tenant_id: Uuid, user_id: Uuid, ) -> Result, AppError> { let enabled_apps: Vec = sqlx::query_scalar("SELECT enabled_apps FROM tenant_entitlements WHERE tenant_id = $1") .bind(tenant_id) .fetch_optional(&self.pool) .await? .unwrap_or_default(); let query = r#" SELECT DISTINCT p.code FROM permissions p JOIN role_permissions rp ON rp.permission_id = p.id JOIN user_roles ur ON ur.role_id = rp.role_id JOIN roles r ON r.id = ur.role_id WHERE r.tenant_id = $1 AND ur.user_id = $2 "#; let rows = sqlx::query_scalar::<_, String>(query) .bind(tenant_id) .bind(user_id) .fetch_all(&self.pool) .await?; Ok(filter_permissions_by_enabled_apps(rows, &enabled_apps)) } #[instrument(skip(self))] /// 校验用户是否具备指定权限,不满足则直接返回权限拒绝错误。 /// /// 业务规则: /// - 若用户权限集合中不包含 `permission_code`,返回 `PermissionDenied(permission_code)`。 /// /// 输入: /// - `tenant_id`:租户 ID /// - `user_id`:用户 ID /// - `permission_code`:权限编码 /// /// 输出: /// - 成功返回 `()`;失败返回权限拒绝错误 pub async fn require_permission( &self, tenant_id: Uuid, user_id: Uuid, permission_code: &str, ) -> Result<(), AppError> { let permissions = self.list_permissions_for_user(tenant_id, user_id).await?; if permissions.iter().any(|p| p == permission_code) { Ok(()) } else { Err(AppError::PermissionDenied(permission_code.to_string())) } } #[instrument(skip(self))] pub async fn list_platform_permissions_for_user( &self, user_id: Uuid, ) -> Result, AppError> { let query = r#" SELECT DISTINCT p.code FROM permissions p JOIN role_permissions rp ON rp.permission_id = p.id JOIN user_roles ur ON ur.role_id = rp.role_id JOIN roles r ON r.id = ur.role_id WHERE ur.user_id = $1 AND r.is_system = TRUE "#; let rows = sqlx::query_scalar::<_, String>(query) .bind(user_id) .fetch_all(&self.pool) .await?; Ok(rows) } #[instrument(skip(self))] pub async fn require_platform_permission( &self, user_id: Uuid, permission_code: &str, ) -> Result<(), AppError> { let permissions = self.list_platform_permissions_for_user(user_id).await?; if permissions.iter().any(|p| p == permission_code) { Ok(()) } else { Err(AppError::PermissionDenied(permission_code.to_string())) } } }