为行级数据权限做准备,TenantContext 新增 department_ids 字段 存储用户所属部门 ID 列表。当前阶段 JWT 中间件填充为空列表, 待 user_positions 关联表建立后补充查询逻辑。
92 lines
2.9 KiB
Rust
92 lines
2.9 KiB
Rust
use crate::error::AppError;
|
|
use crate::types::TenantContext;
|
|
|
|
/// Check whether the `TenantContext` includes the specified permission code.
|
|
///
|
|
/// Returns `Ok(())` if the permission is present, or `AppError::Forbidden` otherwise.
|
|
pub fn require_permission(ctx: &TenantContext, permission: &str) -> Result<(), AppError> {
|
|
if ctx.permissions.iter().any(|p| p == permission) {
|
|
Ok(())
|
|
} else {
|
|
Err(AppError::Forbidden("权限不足".to_string()))
|
|
}
|
|
}
|
|
|
|
/// Check whether the `TenantContext` includes at least one of the specified permission codes.
|
|
///
|
|
/// Useful when multiple permissions can grant access to the same resource.
|
|
pub fn require_any_permission(ctx: &TenantContext, permissions: &[&str]) -> Result<(), AppError> {
|
|
let has_any = permissions
|
|
.iter()
|
|
.any(|p| ctx.permissions.iter().any(|up| up == *p));
|
|
|
|
if has_any {
|
|
Ok(())
|
|
} else {
|
|
Err(AppError::Forbidden("权限不足".to_string()))
|
|
}
|
|
}
|
|
|
|
/// Check whether the `TenantContext` includes the specified role code.
|
|
///
|
|
/// Returns `Ok(())` if the role is present, or `AppError::Forbidden` otherwise.
|
|
pub fn require_role(ctx: &TenantContext, role: &str) -> Result<(), AppError> {
|
|
if ctx.roles.iter().any(|r| r == role) {
|
|
Ok(())
|
|
} else {
|
|
Err(AppError::Forbidden("权限不足".to_string()))
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use uuid::Uuid;
|
|
|
|
fn test_ctx(roles: Vec<&str>, permissions: Vec<&str>) -> TenantContext {
|
|
TenantContext {
|
|
tenant_id: Uuid::now_v7(),
|
|
user_id: Uuid::now_v7(),
|
|
roles: roles.into_iter().map(String::from).collect(),
|
|
permissions: permissions.into_iter().map(String::from).collect(),
|
|
department_ids: vec![],
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn require_permission_succeeds_when_present() {
|
|
let ctx = test_ctx(vec![], vec!["user.read", "user.write"]);
|
|
assert!(require_permission(&ctx, "user.read").is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn require_permission_fails_when_missing() {
|
|
let ctx = test_ctx(vec![], vec!["user.read"]);
|
|
assert!(require_permission(&ctx, "user.delete").is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn require_any_permission_succeeds_with_match() {
|
|
let ctx = test_ctx(vec![], vec!["user.read"]);
|
|
assert!(require_any_permission(&ctx, &["user.delete", "user.read"]).is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn require_any_permission_fails_with_no_match() {
|
|
let ctx = test_ctx(vec![], vec!["user.read"]);
|
|
assert!(require_any_permission(&ctx, &["user.delete", "user.admin"]).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn require_role_succeeds_when_present() {
|
|
let ctx = test_ctx(vec!["admin", "user"], vec![]);
|
|
assert!(require_role(&ctx, "admin").is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn require_role_fails_when_missing() {
|
|
let ctx = test_ctx(vec!["user"], vec![]);
|
|
assert!(require_role(&ctx, "admin").is_err());
|
|
}
|
|
}
|