feat(auth,plugin): Q3 行级数据权限 — user_departments 表 + JWT 注入 department_ids + data_scope 接线
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

- 新增 user_departments 关联表(migration + entity)
- JWT 中间件查询用户部门并注入 TenantContext.department_ids
- role_permission entity 添加 data_scope 字段
- data_handler 接线行级数据权限过滤(list/count/aggregate)
- DataScopeParams + build_scope_sql + merge_scope_condition 实现全链路
This commit is contained in:
iven
2026-04-17 21:42:40 +08:00
parent 9d18b7e079
commit 62eea3d20d
11 changed files with 326 additions and 17 deletions

View File

@@ -12,7 +12,7 @@ use crate::data_dto::{
PatchPluginDataReq, PluginDataListParams, PluginDataResp, TimeseriesItem, TimeseriesParams,
UpdatePluginDataReq,
};
use crate::data_service::{PluginDataService, resolve_manifest_id};
use crate::data_service::{DataScopeParams, PluginDataService, resolve_manifest_id};
use crate::state::PluginState;
/// 获取当前用户对指定权限的 data_scope 等级
@@ -105,12 +105,10 @@ where
let fine_perm = compute_permission_code(&manifest_id, &entity, "list");
require_permission(&ctx, &fine_perm)?;
// TODO(data_scope): 此处注入行级数据权限过滤
// 1. 解析 entity 定义检查 data_scope == Some(true)
// 2. 调用 get_data_scope(&ctx, &fine_perm, &state.db) 获取当前用户的 scope 等级
// 3. 若 scope != "all",调用 get_dept_members 获取部门成员列表
// 4. 将 scope 条件合并到 filter 中传给 PluginDataService::list
// 参考: crates/erp-plugin/src/dynamic_table.rs build_data_scope_condition()
// 解析数据权限范围
let scope = resolve_data_scope(
&ctx, &manifest_id, &entity, &fine_perm, &state.db,
).await?;
let page = params.page.unwrap_or(1);
let page_size = params.page_size.unwrap_or(20);
@@ -133,6 +131,7 @@ where
params.sort_by,
params.sort_order,
&state.entity_cache,
scope,
)
.await?;
@@ -498,3 +497,54 @@ where
Ok(Json(ApiResponse::ok(result)))
}
/// 解析数据权限范围 — 检查 entity 是否启用 data_scope
/// 若启用则查询用户对该权限的 scope 等级,返回 DataScopeParams。
async fn resolve_data_scope(
ctx: &TenantContext,
manifest_id: &str,
entity: &str,
fine_perm: &str,
db: &sea_orm::DatabaseConnection,
) -> Result<Option<DataScopeParams>, AppError> {
let entity_has_scope = check_entity_data_scope(manifest_id, entity, db).await?;
if !entity_has_scope {
return Ok(None);
}
let scope_level = get_data_scope(ctx, fine_perm, db).await?;
if scope_level == "all" {
return Ok(None);
}
let dept_members = get_dept_members(ctx, false).await;
Ok(Some(DataScopeParams {
scope_level,
user_id: ctx.user_id,
dept_member_ids: dept_members,
owner_field: "owner_id".to_string(),
}))
}
/// 查询 entity 定义是否启用了 data_scope
async fn check_entity_data_scope(
_manifest_id: &str,
entity_name: &str,
db: &sea_orm::DatabaseConnection,
) -> Result<bool, AppError> {
use crate::entity::plugin_entity;
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
let entity = plugin_entity::Entity::find()
.filter(plugin_entity::Column::EntityName.eq(entity_name))
.filter(plugin_entity::Column::DeletedAt.is_null())
.one(db)
.await
.map_err(|e| AppError::Internal(e.to_string()))?;
let Some(e) = entity else { return Ok(false) };
let schema: crate::manifest::PluginEntity =
serde_json::from_value(e.schema_json)
.map_err(|e| AppError::Internal(format!("解析 entity schema 失败: {}", e)))?;
Ok(schema.data_scope.unwrap_or(false))
}