Files
iam-service/src/services/authorization.rs
2026-01-31 11:11:55 +08:00

122 lines
3.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<Vec<String>, AppError> {
let enabled_apps: Vec<String> =
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<Vec<String>, 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()))
}
}
}