Files
leaudit-platform-backend/docs/权限与地区隔离/角色硬编码与接口影响专项补充分析.md

15 KiB
Raw Permalink Blame History

角色硬编码与接口影响专项补充分析

适用范围:leaudit-platform 当前角色权限与地区隔离体系
文档定位:从主方案中拆出的专项补充稿,专门分析“硬编码角色如何改”和“哪些接口会被联动影响”。


1. 结论先行

当前项目里的角色硬编码不是零散问题,而是已经渗透到:

  • 后端服务层能力派生
  • 后端服务层动作放行
  • 前端 UI 可见性和可编辑性
  • 前端 guard、fallback、role mapping

因此,这类改造不能只改某一个 service,也不能只改权限表配置。

如果只改一部分,会出现 3 类典型问题:

  1. 后端 permission 已放行,但服务层仍按角色名拒绝
  2. 后端边界已收敛,但前端仍按旧角色逻辑展示按钮或入口
  3. 菜单、页面、接口、详情、下载这些边界继续不一致

正确做法是:

  1. 先识别所有硬编码角色位置
  2. 再区分它们的职责类型
  3. 最后按“能力抽象 -> 服务层替换 -> 前端去角色化”的顺序渐进改造

2. 当前硬编码角色的 4 种形态

2.1 服务层上下文派生型

这类代码并不直接判断某个 permission,而是把角色名先转成派生能力:

  • is_global
  • can_manage
  • is_super_admin
  • is_area_admin
  • bypass_area

典型位置:

  • documentServiceImpl.py
  • govdocServiceImpl.py
  • usageStatsServiceImpl.py
  • contractTemplateServiceImpl.py
  • rbacAdminServiceImpl.py
  • homeServiceImpl.py

典型模式:

  • role_key IN ('super_admin', 'provincial_admin') => is_global
  • role_key IN ('super_admin', 'provincial_admin', 'admin') => can_manage
  • role_key = 'super_admin' => is_super_admin / bypass_area

本质问题:

  • 角色名被直接当成能力模型
  • 一旦出现新领域管理员,所有上下文派生逻辑都要重复改

2.2 服务层动作白名单型

这类代码直接按角色名决定某个业务动作是否允许。

典型位置:

  • ragDatasetServiceImpl.py
  • ragChatServiceImpl.py

典型模式:

  • UserRole not in ("provincial_admin", "admin", "super_admin")
  • user_role == "provincial_admin"
  • UserRole == "admin"

本质问题:

  • 控制器层已经做 permission 校验
  • 服务层又加了角色白名单
  • 结果会出现“有权限但角色名不对仍被拒绝”

这是当前最需要优先清理的硬编码类型。

2.3 前端 UI 能力硬编码型

这类代码不是后端鉴权,但会直接影响用户感知边界。

典型位置:

  • components/dify-dataset-manager/index.tsx
  • components/dify-dataset-manager/area-dataset-config.tsx
  • hooks/use-area-dataset-config.ts

典型模式:

  • provincial_admin 可编辑全部
  • super_admin 可编辑全部
  • admin 仅可编辑本地区

本质问题:

  • 前端自己在解释“谁能管理”
  • 即使后端以后完成去角色化,前端仍可能展示旧权限状态

2.4 前端兼容层硬编码型

这类代码主要存在于路由、guard 和迁移兼容层。

典型位置:

  • legal-platform-frontend/lib/auth/user-routes.ts
  • legal-platform-frontend/lib/api/legacy/auth/user-routes.ts
  • legal-platform-frontend/lib/auth/guard.ts
  • legal-platform-frontend/lib/auth/cross-checking-access.ts

典型模式:

  • provincial_admin -> admin
  • super_admin -> admin
  • developer -> admin
  • provincial_admin 自动获得“省局 area”候选

本质问题:

  • 前端把真实角色体系压缩成少数桶位
  • 继续保留会让权限模型越来越难以统一

3. 为什么不能直接删掉所有角色硬编码

不是所有角色硬编码都应该同一天删除,因为它们承担职责不同。

3.1 可优先替换的

优先替换:

  • 服务层动作白名单
  • RAG 管理动作角色白名单

原因:

  • 这些逻辑已经和 permission 决策重复
  • 删除后收益最大,副作用相对可控

3.2 需要先抽象再替换的

需要先抽象:

  • is_global
  • can_manage
  • is_super_admin
  • is_area_admin

原因:

  • 它们已经被多个模块用于拼接 SQL 过滤条件
  • 直接删会导致大量数据边界逻辑断裂

正确做法:

  • 先统一沉淀为能力派生层
  • 再逐步把“角色名判断”替换为“能力决策”

3.3 可以保留为兼容层但必须收缩边界的

允许短期保留:

  • route fallback
  • role mapping
  • 某些旧前端 guard

但必须满足:

  1. 只存在于适配层
  2. 不再扩散到新业务代码
  3. 后续有明确移除计划

4. 推荐的替换目标

当前很多角色语义,其实应该改写成能力语义。

建议的替换关系如下:

  • super_admin 全局绕过 替换为:is_super_admin
  • provincial_admin => 全省可见 替换为:effective_scope == ALL
  • admin => 本地区管理 替换为:effective_scope == DEPT + domain manage permission
  • common => 自己的数据 替换为:effective_scope == SELF
  • provincial_admin/admin/super_admin 共用白名单 替换为:显式 create/update/delete/manage permission

这一步的本质,是把“角色名”换成“能力决策”。


5. 建议新增的统一能力层

建议新增统一能力派生对象,例如:

  • is_super_admin
  • has_global_scope
  • has_area_scope
  • can_manage_rbac
  • can_manage_rag_dataset
  • can_manage_contract_templates
  • can_view_usage_stats
  • can_bypass_home_area

这些能力不应直接写死在角色名上,而应由以下信息共同决策:

  • roles
  • permissions
  • data_scope
  • 可选 condition_filter

建议新增两层统一能力:

  1. ScopeContextProvider
  2. AdminCapabilityResolver

前者负责“当前用户的通用数据边界”,后者负责“某个业务域是否具备管理能力”。


6. 当前高风险冲突区

6.1 RAG 是最典型的双轨冲突区

当前链路是:

  1. 控制器层按 permission 校验
  2. 服务层又按 UserRole 白名单校验
  3. 最终管理能力同时受 permission 和角色名双重控制

风险:

  • 未来新增 rag_manager 这类角色时,即使给了 rag:dataset:manage/create/update/delete,服务层仍会拒绝

优先级:

  • 最高

6.2 RBAC 管理域存在“角色管理能力”和“权限管理能力”双重耦合

当前链路是:

  1. _assertManagePermission 依赖 can_manage
  2. _assertPermission 再校验具体权限点

风险:

  • 如果未来某用户拥有 rbac:* 权限,但主角色不是 admin/provincial_admin/super_admin,仍可能被挡在第一层

优先级:

6.3 文档 / 公文 / 统计存在共性上下文派生复制

这些模块并没有直接角色白名单放行,但都重复实现了:

  • is_global
  • can_manage
  • is_super_admin

风险:

  • 改一个模块不改另一个,边界会漂移

优先级:

6.4 前端知识库管理仍在自行解释角色

风险:

  • 后端已经 permission 化后,前端仍会显示不该显示的按钮
  • 或相反,后端已允许,前端仍不展示入口

优先级:


7. 受影响接口分析

下面重点回答“其他接口会不会影响到”。

答案是:会,而且影响面不小。

7.1 文档模块

受影响接口包括:

  • POST /api/upload
  • GET /api/documents/list
  • GET /api/documents/status
  • GET /api/documents/{DocumentId}
  • GET /api/v3/review-points/{DocumentId}
  • PATCH /api/v3/review-points/{ReviewPointResultId}/audit
  • PATCH /api/v3/documents/{DocumentId}/confirm
  • POST /api/documents/{DocumentId}/attachments
  • PUT /api/documents/{DocumentId}
  • DELETE /api/documents/{DocumentId}

影响原因:

  • 这些接口都依赖文档服务里的用户上下文派生和数据范围过滤
  • 一旦 is_global/can_manage 的计算逻辑变化,全部都会联动

7.2 公文模块

受影响接口包括:

  • POST /api/govdoc/documents
  • GET /api/govdoc/documents
  • GET /api/govdoc/documents/{documentId}
  • PATCH /api/govdoc/documents/{documentId}
  • DELETE /api/govdoc/documents/{documentId}
  • POST /api/govdoc/runs
  • GET /api/govdoc/runs/{runId}
  • GET /api/govdoc/runs/{runId}/result
  • GET /api/govdoc/runs/{runId}/findings
  • GET /api/govdoc/runs/{runId}/entities
  • GET /api/govdoc/runs/{runId}/structure
  • GET /api/govdoc/runs/{runId}/outline
  • GET /api/govdoc/runs/{runId}/paragraphs
  • GET /api/govdoc/runs/{runId}/report/html
  • GET /api/govdoc/runs/{runId}/report/docx
  • GET /api/govdoc/documents/{documentId}/original

影响原因:

  • 公文模块也使用了同构的上下文派生
  • 尤其结果、报告、下载类接口最容易出现“资源详情边界未同步”的问题

7.3 统计模块

受影响接口包括:

  • GET /api/v3/usage-stats/overview
  • GET /api/v3/usage-stats/trends
  • GET /api/v3/usage-stats/by-users
  • GET /api/v3/usage-stats/by-departments
  • GET /api/v3/usage-stats/by-areas
  • GET /api/v3/usage-stats/details

影响原因:

  • 当前统计接口对管理员可见性有明显上下文派生依赖
  • 去角色化后,这些接口需要统一切到“统计域 permission + scope”模型

7.4 RAG 模块

受影响接口包括:

  • GET /api/v3/rag/apps
  • GET /api/v3/rag/apps/default
  • GET /api/v3/rag/datasets/my
  • GET /api/v3/rag/datasets/admin
  • POST /api/v3/rag/datasets/admin
  • PUT /api/v3/rag/datasets/admin/{DatasetId}
  • DELETE /api/v3/rag/datasets/admin/{DatasetId}
  • GET /api/v3/rag/datasets/{DatasetId}
  • PATCH /api/v3/rag/datasets/{DatasetId}
  • 各类 /datasets/{DatasetId}/documents
  • 各类 /datasets/{DatasetId}/segments
  • 各类检索测试接口
  • POST /api/v3/rag/chat/messages
  • 会话、消息反馈、会话重命名、删除等接口

影响原因:

  • 控制器层和服务层当前存在双轨权限逻辑
  • 这是最典型的“permission 改了,接口仍会被角色名卡住”的模块

7.5 RBAC 管理模块

受影响接口包括:

  • GET /api/v3/rbac/roles
  • POST /api/v3/rbac/roles
  • PUT /api/v3/rbac/roles/{RoleId}
  • DELETE /api/v3/rbac/roles/{RoleId}
  • GET /api/v3/rbac/users
  • GET /api/admin/users/organizations/tree
  • GET /api/v3/rbac/roles/{RoleId}/users
  • POST /api/v3/rbac/users/{UserId}/roles
  • DELETE /api/v3/rbac/users/{UserId}/roles/{RoleId}
  • GET /api/v3/rbac/users/{UserId}/roles
  • GET /api/v3/routes
  • GET/PUT /api/rbac/roles/{RoleId}/routes
  • GET/POST /api/v3/rbac/role-permissions
  • POST /api/v3/rbac/roles/{RoleId}/access
  • GET /api/v3/routes/{RouteId}/permissions

影响原因:

  • 当前同时依赖 _assertManagePermission_assertPermission
  • 第一层还是角色派生管理能力

7.6 首页入口模块

受影响接口:

  • GET /api/home/entry-modules

影响原因:

  • 当前首页入口存在 super_admin 的 area bypass 语义
  • 这类入口可见性也属于权限边界的一部分

7.7 合同模板模块

受影响接口包括:

  • GET /api/v3/contract-templates/categories
  • GET /api/v3/contract-templates
  • POST /api/v3/contract-templates
  • GET /api/v3/contract-templates/search
  • GET /api/v3/contract-templates/{TemplateId}
  • DELETE /api/v3/contract-templates/{TemplateId}

影响原因:

  • 当前业务语义明确写着“地区管理员才能上传”
  • 如果只去角色化、不补合同模板域显式权限,这类能力会失焦

7.8 中影响模块

中影响但必须纳入联调范围的还有:

  • 交叉评查模块
  • 评查点模块
  • 评查点分组模块
  • 规则配置模块

原因:

  • 它们虽然不一定都直接依赖角色名白名单
  • 但仍依赖 permission、route、入口和关系访问逻辑
  • 一旦整体权限能力模型调整,也必须回归验证

8. 前端联动影响

不能只看后端接口,前端也会同步受影响。

8.1 菜单与路由

受影响位置:

  • Sidebar.tsx
  • user-routes.ts
  • check-route-permission.ts
  • fallback route mapping

风险:

  • 菜单可见性与真实接口权限继续分叉
  • 某些角色被映射压扁后,真实权限无法完整反映

8.2 RAG 管理页面

受影响位置:

  • components/dify-dataset-manager/*
  • hooks/use-area-dataset-config.ts

风险:

  • 后端能力已收敛,前端仍按旧角色名展示编辑入口

8.3 页面 guard

受影响位置:

  • lib/auth/guard.ts
  • lib/auth/cross-checking-access.ts
  • lib/auth/session-user.ts
  • lib/auth/jwt.ts

风险:

  • user_role 被继续当成完整权限模型使用

9. 推荐改造顺序

建议按下面顺序推进,避免同时炸开所有联动面。

9.1 第一阶段

先做平台能力层:

  • PermissionDecisionService
  • ScopeContextProvider
  • AdminCapabilityResolver

目标:

  • 不先改业务接口行为,只先统一决策能力

9.2 第二阶段

优先改 RAG

  • 清理服务层角色白名单
  • 改为 permission + scope/policy 决策

原因:

  • 这是最典型、收益也最大的双轨冲突区

9.3 第三阶段

接入共用上下文模块:

  • 文档
  • 公文
  • 统计

原因:

  • 它们共享大量上下文派生逻辑
  • 最适合沉淀统一 QueryScopeBuilder

9.4 第四阶段

处理管理域:

  • RBAC 管理
  • 首页入口
  • 合同模板

目标:

  • 把“管理能力”从角色名迁移到领域 permission

9.5 第五阶段

清理前端角色解释层:

  • role mapping
  • fallback route
  • guard
  • UI 编辑能力判断

目标:

  • 前端不再自行解释“谁是管理员”

10. 必做回归清单

建议单独维护一份“角色去硬编码回归清单”,至少覆盖:

  1. 用户拥有 permission,但主角色不是 admin/provincial_admin 时,接口是否仍能正确访问
  2. 用户获得某领域管理权限后,是否无需改代码即可生效
  3. 菜单、按钮、接口、详情、下载、导出边界是否一致
  4. 前端是否仍存在基于 user_role 的旧判断放大或缩小能力
  5. RAG 管理接口是否已完全摆脱角色白名单
  6. RBAC 管理接口是否已从“角色管理能力”切换到“权限管理能力”
  7. 文档、公文、统计是否仍存在模块间边界不一致

11. 最终建议

这次专项分析的核心结论只有一句话:

角色硬编码改造,本质上不是“替换几个 if”,而是把整套权限系统从“角色名驱动”升级为“能力决策驱动”。

如果只做局部替换,问题会更隐蔽。

如果按下面顺序推进,风险最低:

  1. 先抽象能力层
  2. 先处理 RAG 双轨冲突
  3. 再统一文档/公文/统计上下文派生
  4. 再处理 RBAC 管理域和首页入口
  5. 最后清理前端角色解释和 fallback

这样改完之后,项目才能真正从:

  • “角色名决定能力”

走向:

  • “权限点 + 数据范围 + 模块 policy 决定能力”

这才是后续权限平台可持续演进的正确方向。