Files
leaudit-platform-backend/docs/权限与地区隔离/统一数据范围执行器设计.md
T

710 lines
16 KiB
Markdown
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.
# 统一数据范围执行器设计
> 适用范围:`leaudit-platform` 当前 `RBAC + 单地区隔离 + 模块特例策略` 体系
> 文档定位:把“data_scope 已建模但未统一执行”的现状,收敛为一套后端可落地的统一执行器设计。
---
## 1. 设计结论
当前系统真正缺的不是权限表,也不是权限点,而是一个统一的“数据范围执行层”。
现状已经具备:
- `roles.data_scope`
- `role_permissions.data_scope`
- `role_permissions.grant_type = GRANT / DENY`
- 用户主地区 `sso_users.area`
- 多个模块已存在真实可运行的数据边界实现
当前系统真正缺失的是:
1. 平台层没有把“功能权限 + 数据范围 + 模块特例策略”合并成一次统一决策
2. `PermissionServiceImpl` 只返回布尔值,不返回 scope 决策
3. 文档、公文、统计、合同模板、RBAC 管理域都在各自重复写 `is_global/can_manage/area/created_by`
4. RAG、交叉评查等特殊模块没有被纳入统一决策框架,只能继续靠模块内手工判断
因此,建议新增统一执行链:
1. `PermissionDecisionService`
2. `DataScopeResolver`
3. `QueryScopeBuilder`
4. `ModulePolicyRegistry`
5. `ScopeAwarePermissionFacade`
目标不是推翻当前业务 SQL,而是先把“决策入口”和“范围解释逻辑”统一,再逐模块替换已有散落实现。
---
## 2. 统一执行器要解决什么问题
## 2.1 功能准入和数据范围必须拆开
一个接口是否可调用,与一个用户调用后能看到多少数据,是两层不同问题:
- 功能准入:是否拥有 `permission_key`
- 数据范围:即便拥有该权限,可见范围到底是 `ALL / DEPT / SELF / RELATION / PUBLIC_MIXED`
当前系统多数地方只做了第一层,第二层分散在 service 内。
## 2.2 多角色用户必须有统一合并规则
当前库允许一个用户绑定多个角色:
- 一个角色可能 `GRANT documents:list:read + SELF`
- 另一个角色可能 `GRANT documents:list:read + DEPT`
- 还有角色可能 `DENY documents:list:read`
没有统一合并规则,就会出现:
- 有人按最大范围放行
- 有人按主角色放行
- 有人忽略 `DENY`
## 2.3 模块差异必须被显式建模
不是所有模块都适合硬套 `ALL/DEPT/SELF`
- 文档、公文、统计:典型数据范围模型
- RAG`地区 + 公开(public)` 混合模型
- 交叉评查:任务成员关系模型
- 规则/配置域:更偏功能权限域
所以统一执行器不能只有一个“拼 where region = ?”函数,而要允许模块策略扩展。
---
## 3. 总体架构
建议新增如下能力层。
## 3.1 核心对象
### 3.1.1 `ScopeContext`
表示一次权限决策的输入上下文。
建议字段:
```python
@dataclass
class ScopeContext:
user_id: int
permission_key: str
module: str
action: str
user_area: str | None
request_area: str | None = None
target_user_id: int | None = None
resource_id: int | None = None
extra_filters: dict[str, Any] | None = None
```
说明:
- `user_area` 来自 `sso_users.area`
- `request_area` 是接口显式传入的筛选地区
- `target_user_id` 适用于按用户过滤的查询
- `resource_id` 适用于详情、删除、下载这类单资源接口
- `extra_filters` 给统计、RAG、交叉评查等模块挂业务参数
### 3.1.2 `PermissionGrant`
表示从数据库查出的单条授权记录。
```python
@dataclass
class PermissionGrant:
role_id: int
role_key: str
permission_key: str
grant_type: str
role_scope: str | None
permission_scope: str | None
condition_filter: dict[str, Any] | None
priority: int
is_system_role: bool
```
### 3.1.3 `PermissionDecision`
表示平台最终产出的权限决策。
```python
@dataclass
class PermissionDecision:
allowed: bool
deny_reason: str | None
effective_scope: str | None
scope_mode: str
allowed_areas: list[str]
allowed_user_ids: list[int]
allow_public: bool
conditions: dict[str, Any]
matched_roles: list[str]
matched_permissions: list[str]
```
建议 `scope_mode` 取值:
- `NONE`:不做数据范围控制
- `ALL`
- `DEPT`
- `SELF`
- `RELATION`
- `PUBLIC_MIXED`
- `CUSTOM`
### 3.1.4 `ScopeClause`
表示最终用于拼接 SQL 的范围子句。
```python
@dataclass
class ScopeClause:
sql: str
params: dict[str, Any]
scope_mode: str
description: str
```
---
## 4. 执行链路
建议所有需要“权限 + 数据边界”的 service 统一走下面链路:
```text
Controller
-> ScopeAwarePermissionFacade.require()
-> PermissionDecisionService.decide()
-> PermissionGrantRepository.load_user_grants()
-> DataScopeResolver.resolve()
-> ModulePolicyRegistry.get(module).decorate()
-> QueryScopeBuilder.build()
-> Service 执行业务 SQL / ORM
```
细化如下:
1. 控制器或 service 构造 `ScopeContext`
2. `PermissionDecisionService` 先确认该用户是否拥有功能权限
3. 若被 `DENY` 命中,直接拒绝
4.`GRANT` 命中,则计算有效范围
5. 若模块存在特例策略,由 `ModulePolicy` 二次修饰决策
6.`QueryScopeBuilder` 将决策转换为统一 SQL 子句
7. 业务 service 只负责把子句注入原查询,不再自行解释角色
---
## 5. 数据来源与优先级规则
## 5.1 数据来源
统一执行器的决策数据来自 4 层:
1. `sso_users.area`
2. `roles.data_scope`
3. `role_permissions.data_scope`
4. `role_permissions.grant_type / condition_filter`
## 5.2 功能准入优先级
功能准入保持现有语义,但要扩展为“带授权明细返回”:
1. 精确 `DENY`
2. 通配符 `DENY`
3. 精确 `GRANT`
4. 通配符 `GRANT`
5. 无匹配则拒绝
这部分沿用 `PermissionServiceImpl` 现有规则,不改语义。
## 5.3 范围优先级
建议统一采用:
1. `role_permissions.data_scope`
2. `roles.data_scope`
3. 模块默认值
即:
- 权限级 scope 覆盖角色级 scope
- 角色级 scope 覆盖模块默认值
- 若都没有,读接口默认 `SELF`,配置接口默认 `NONE`
## 5.4 多角色合并规则
建议采用:
1. 先按 `DENY` 决定是否整体拒绝
2. 在所有有效 `GRANT` 中取“最大可见范围”
3. 若存在模块策略,则再由模块策略收敛边界
范围大小建议定义为:
```text
ALL > DEPT > SELF
GROUP 不再扩展,仅兼容映射
RELATION / PUBLIC_MIXED / CUSTOM 由模块策略生成
```
注意:
- `DENY` 是功能拒绝,不只是 scope 收缩
- 不能把一个 `DENY documents:list:read` 理解成“降到 SELF”
- 一旦该权限被 `DENY` 命中,应直接 `allowed = False`
## 5.5 `GROUP` 的处理
库里仍保留 `GROUP`,但当前系统没有成熟“组织组”模型。
建议:
- 现阶段统一映射为 `DEPT`
- 文档和代码中明确标注“仅兼容,不新增使用”
- 后续如果真要启用组织组,再单独扩展
---
## 6. 范围语义规范
## 6.1 `ALL`
含义:
- 可见该模块全部数据
- 可按任意地区或任意用户进一步筛选
适用:
- 省级管理员
- 超级管理员
- 某些总部配置权限
## 6.2 `DEPT`
本项目中 `DEPT` 语义应明确定义为:
- 同主地区 `area`
不是传统部门树。
含义:
- 只能看与自身 `user_area` 相同地区的数据
- 如接口显式传入 `request_area`,则必须等于 `user_area`
## 6.3 `SELF`
含义:
- 仅可看与自己直接归属的数据
优先字段通常是:
- `created_by`
- `owner_id`
- `user_id`
必须按模块字段映射规范执行,不能每个模块再自行猜测。
## 6.4 `RELATION`
适用于交叉评查等成员关系模型。
含义:
- 可访问与当前用户存在显式关系绑定的数据
例如:
- `task_member.user_id = current_user_id`
- `proposal.created_by = current_user_id`
- `document` 属于当前参与任务
## 6.5 `PUBLIC_MIXED`
适用于 RAG。
含义:
- 本地区资源可见
- 公共资源可见
- 省级公共资源可见
本质条件通常是:
```sql
resource.area IN (:user_area, '省级', '')
OR resource.is_public = TRUE
```
---
## 7. 通用字段映射规范
统一执行器要避免“模块自己猜字段名”,必须先定义字段映射表。
## 7.1 推荐映射对象
```python
@dataclass
class ScopeFieldMapping:
area_field: str | None = None
creator_field: str | None = None
owner_field: str | None = None
user_field: str | None = None
```
## 7.2 模块字段规范
### 文档模块
- area 字段:`d.region`
- self 字段:`f.created_by` 或主文档归属用户
说明:
- 当前文档真实归属更多落在文件记录 `created_by`
- 统一接入时要固定“列表/详情/附件/结果”的归属判断口径
### 公文模块
- area 字段:`d.region`
- self 字段:`f.created_by`
### 使用统计模块
按口径不同分两类:
- 用户口径 area`u.area`
- 文档口径 area`d.region`
- self 字段:`f.created_by``u.id`
### 合同模板模块
- area 字段:`t.region`
- self 字段:`t.created_by`
### RBAC 用户管理
- area 字段:`u.area`
- self 字段:通常不用
### RAG 数据集/应用
- area 字段:`dataset.area` / `app.area`
- self 字段:按创建者字段补充
- public 字段:`is_public`
### 交叉评查
- 不优先走 `area_field`
- 主入口走关系字段:
- `task_member.user_id`
- `proposal.created_by`
- `vote.user_id`
---
## 8. 模块策略设计
统一执行器不能只靠 `ALL/DEPT/SELF`,必须允许模块策略。
## 8.1 `ModulePolicy` 接口
```python
class ModulePolicy(Protocol):
def decorate(self, ctx: ScopeContext, decision: PermissionDecision) -> PermissionDecision: ...
def build_clause(self, ctx: ScopeContext, decision: PermissionDecision) -> ScopeClause | None: ...
```
说明:
- `decorate()` 负责把通用 scope 转成模块实际策略
- `build_clause()` 负责拼接模块专属条件
## 8.2 DocumentPolicy
适用模块:
- 文档列表
- 文档详情
- 文档状态
- 评查点聚合
- 附件追加
- 文档确认
建议策略:
- `ALL`:不加地区限制,但允许 `request_area`
- `DEPT`:固定 `d.region = user_area`
- `SELF`:固定 `f.created_by = current_user_id`
- 单资源接口必须先验证资源是否在 scope 内,再做更新/删除
## 8.3 GovdocPolicy
建议策略:
- 与文档模块同一套 area/self 语义
- `run/result/report/download` 等派生接口必须继承 document scope
- 不能只校验 `run_id` 是否存在,必须回溯到所属 `document_id`
## 8.4 UsageStatsPolicy
建议策略:
- `overview/trends/by-areas`:支持 `ALL/DEPT`
- `by-users/by-departments/details`:支持 `ALL/DEPT/SELF`
- `SELF` 时只允许看到自己的登录、上传、评查明细
- `areaScope=document``areaScope=user` 由模块策略切换字段映射
## 8.5 RagPolicy
建议策略:
- 读取类接口:`PUBLIC_MIXED`
- 管理类接口:`ALL/DEPT/SELF` + 资源属地校验
拆分为两类:
1. `rag:app:read` / `rag:chat:use` / `rag:dataset:read`
- 允许 `本地区 + 省级 + 公共`
2. `rag:dataset:manage/create/update/delete`
- 不再按 `UserRole` 白名单
- 统一按 `permission + scope` 决定
## 8.6 CrossReviewPolicy
建议策略:
- 主模型使用 `RELATION`
- `task:read` 不是 area 过滤,而是成员关系过滤
- `document:complete` 不是角色白名单,而是“拥有权限 + 是任务参与者/负责方”
- `proposal:read/export` 必须通过文档所属任务关系回溯校验
结论:
- 交叉评查接入统一执行器
- 但不强制落到普通 `ALL/DEPT/SELF`
- 它是统一框架下的特例策略,不是例外代码
## 8.7 RbacAdminPolicy
建议策略:
- 角色、用户、组织树、路由、授权查询等接口优先按 `u.area`
- 全局管理员看全部
- 地区管理员仅管理本地区用户
- 角色对象本身通常不按地区分表,但“角色绑定到哪些用户”仍受用户 area 边界约束
---
## 9. QueryScopeBuilder 设计
## 9.1 目标
`PermissionDecision` 转成业务可注入的条件,而不是让业务自己重新解释。
## 9.2 建议接口
```python
class QueryScopeBuilder:
def build_by_mapping(
self,
decision: PermissionDecision,
mapping: ScopeFieldMapping,
current_user_id: int,
user_area: str | None,
request_area: str | None = None,
) -> ScopeClause: ...
```
## 9.3 通用构造规则
### `ALL`
```sql
1 = 1
```
若带 `request_area`
```sql
COALESCE(area_field, '') = :request_area
```
### `DEPT`
```sql
COALESCE(area_field, '') = :scope_area
```
并且:
-`request_area` 非空且不等于 `user_area`,直接拒绝
### `SELF`
优先级:
1. `creator_field`
2. `owner_field`
3. `user_field`
生成:
```sql
creator_field = :current_user_id
```
### `PUBLIC_MIXED`
```sql
(
COALESCE(area_field, '') IN :visible_areas
OR public_field = TRUE
)
```
### `RELATION`
由模块策略返回完整 SQL 子句,不走通用拼接。
---
## 10. 权限决策缓存建议
当前 `PermissionServiceImpl` 已有 60 秒权限集合缓存。
建议统一执行器增加两层缓存:
1. 用户授权明细缓存
2. 用户能力快照缓存
建议缓存内容:
- grant / deny 结果
- 每个 `permission_key` 对应的有效 scope
- 用户 `area`
- 用户角色列表
注意:
- 缓存里只存决策原料,不缓存最终 SQL
- 角色变更、权限变更、用户地区变更后,应主动失效
---
## 11. 建议代码落点
建议新增目录:
```text
fastapi_modules/fastapi_leaudit/services/permission_scope/
decision_models.py
permissionDecisionService.py
dataScopeResolver.py
queryScopeBuilder.py
modulePolicies/
base.py
documentPolicy.py
govdocPolicy.py
usageStatsPolicy.py
ragPolicy.py
crossReviewPolicy.py
rbacAdminPolicy.py
```
建议改造现有:
- `services/impl/permissionServiceImpl.py`
- 保留布尔接口
- 新增返回授权明细的方法
- `services/permissionService.py`
- 增加 `GetPermissionDecision`
- `services/impl/documentServiceImpl.py`
- `services/impl/govdocServiceImpl.py`
- `services/impl/usageStatsServiceImpl.py`
- `services/impl/ragDatasetServiceImpl.py`
- `services/impl/ragChatServiceImpl.py`
- `services/impl/rbacAdminServiceImpl.py`
- `services/impl/contractTemplateServiceImpl.py`
---
## 12. 推荐接入顺序
## 12.1 第一批
- RAG
- 文档
- 公文
- 使用统计
原因:
- 这几块最明显存在范围逻辑分散或双轨冲突
- 风险最高,收益也最大
## 12.2 第二批
- RBAC 管理域
- 合同模板
- 首页入口模块
## 12.3 第三批
- 交叉评查接入统一决策框架
- 前端联动改为只认权限与服务端返回能力
---
## 13. 与现有代码的映射关系
当前散落逻辑,建议按下述替换:
- `documentServiceImpl._getCurrentUserContext`
- 替换为 `PermissionDecisionService + ScopeContext`
- `documentServiceImpl._buildDocumentScopeFilters`
- 替换为 `QueryScopeBuilder + DocumentPolicy`
- `govdocServiceImpl` 内 area/created_by 过滤
- 替换为 `GovdocPolicy`
- `usageStatsServiceImpl._build_user_scope_condition`
- 替换为 `UsageStatsPolicy`
- `contractTemplateServiceImpl``is_global/can_manage`
- 替换为 `ScopeDecision`
- `rbacAdminServiceImpl``bool_or(role_key IN (...))`
- 替换为统一管理域能力决策
- `ragDatasetServiceImpl` / `ragChatServiceImpl``UserRole` 白名单
- 替换为 `RagPolicy`
---
## 14. 验收标准
统一执行器设计落地后,应满足以下标准:
1. 新增一个 permission 时,可以同时配置 data_scope,并被平台自动执行
2. 文档、公文、统计、RAG 不再自行硬编码角色解释范围
3. `DENY`、多角色合并、地区限制有统一规则
4. 详情、删除、下载、导出接口与列表接口共享同一 scope 决策
5. 前端不再需要猜测“省级管理员/市级管理员能看到哪些数据”
---
## 15. 最终建议
这次改造不应该继续扩写更多 `is_global/can_manage` 工具函数,而应直接建立统一执行器。
如果继续在各 service 内复制现有模式,后续问题会持续扩大:
- 数据范围无法统一验收
- 新角色接入成本持续上升
- 前后端边界会长期不一致
- RAG、交叉评查这类特例模块会越来越难治理
统一数据范围执行器是本轮权限架构改造的核心平台能力,优先级应为最高。