341 lines
9.6 KiB
Markdown
341 lines
9.6 KiB
Markdown
# 新平台主链路租户边界扫描报告
|
|
|
|
> 扫描日期:2026-05-21
|
|
> 范围:新平台主链路
|
|
> 不含:旧 `evaluation_points` 兼容链路、旧 `rules/list`、`rules/new`
|
|
|
|
---
|
|
|
|
## 1. 总结论
|
|
|
|
当前新平台主链路已经不是“完全没租户化”,而是进入了一个更危险的阶段:
|
|
|
|
- 文档主表已经开始写 `tenant_code`
|
|
- 首页入口模块已经开始按租户过滤
|
|
- 但评查组/规则集/前端上传作用域仍没有完整切到 `tenant_code`
|
|
|
|
这意味着系统现在处于一种“半租户化”状态:
|
|
|
|
1. 一部分链路按租户工作
|
|
2. 一部分链路仍按 `region/area/document_type/entry_module_id`
|
|
3. 还有一部分链路默认假设主数据是全局共享
|
|
|
|
真正的风险不是“功能不能用”,而是:
|
|
|
|
- 会把数据落错边界
|
|
- 会把旧脏数据和新租户数据串到同一条版本链
|
|
- 会在多租户复用同入口模块/同文档类型时,把用户带到错误的业务树或规则树
|
|
|
|
---
|
|
|
|
## 2. 三个最危险的核心问题
|
|
|
|
## 2.1 评查组/规则集主数据仍然是全局共享模型
|
|
|
|
当前:
|
|
|
|
- `leaudit_evaluation_point_groups` 没有 `tenant_code`
|
|
- `leaudit_rule_group_bindings` 没有 `tenant_code`
|
|
- `leaudit_rule_sets` / `rule_type` 仍按全局唯一思路工作
|
|
|
|
结果:
|
|
|
|
- 不同租户可能共享同一套分组树
|
|
- 不同租户可能共享同一个 `rule_type -> rule_set`
|
|
- 一个租户新建规则版本,可能覆盖另一个租户当前使用的规则资产
|
|
|
|
这不是“显示错了”,而是规则资产归属模型本身还没分租户。
|
|
|
|
## 2.2 文档链路仍存在 `tenant_code + region` 混查
|
|
|
|
当前:
|
|
|
|
- 文档上传虽然开始写 `tenant_code`
|
|
- 但历史版本命中、列表过滤、详情过滤仍允许 `tenant_code` 不命中时回退 `region`
|
|
|
|
结果:
|
|
|
|
- 同地区旧脏数据可能被续成新租户文档的新版本
|
|
- 列表和详情在历史数据上可能出现租户边界漂移
|
|
- 只要旧数据 `tenant_code` 为空,系统就会继续依赖中文展示名做范围匹配
|
|
|
|
这是当前最容易造成“查对了表面、查错了真实归属”的问题。
|
|
|
|
## 2.3 前端上传和页面作用域仍未显式传递 `tenant_code`
|
|
|
|
当前:
|
|
|
|
- 上传页提交时主要传 `region`
|
|
- 首页跳转主要传 `entryModuleId + documentTypeIds`
|
|
- 子类型分组查询只按 `document_type_id`
|
|
- 文档列表读范围也主要依赖 URL/sessionStorage 中的模块/文档类型
|
|
|
|
结果:
|
|
|
|
- 当前租户上下文在前端不是一等主语义
|
|
- 很多页面只能“假设后端已经帮你隔离好了”
|
|
- 一旦后端返回混合数据,前端没有第二道确认防线
|
|
|
|
---
|
|
|
|
## 3. 高风险问题清单
|
|
|
|
## 3.1 评查组/规则集后端高风险
|
|
|
|
### 3.1.1 `RuleController` 基本是裸接口
|
|
|
|
文件:
|
|
|
|
- [ruleController.py](/home/wren-dev/Porject/leaudit-platform/fastapi_modules/fastapi_leaudit/controllers/ruleController.py)
|
|
|
|
问题:
|
|
|
|
- 缺少统一 `verify_access_token`
|
|
- 缺少功能权限校验
|
|
- 也没有数据范围/租户上下文透传
|
|
|
|
影响:
|
|
|
|
- 规则集列表、版本内容、发布、回滚、绑定增删改都可能绕过租户边界
|
|
|
|
### 3.1.2 `CreateVersion` 仍按全局 `rule_type` 工作
|
|
|
|
文件:
|
|
|
|
- [ruleServiceImpl.py](/home/wren-dev/Porject/leaudit-platform/fastapi_modules/fastapi_leaudit/services/impl/ruleServiceImpl.py)
|
|
|
|
问题:
|
|
|
|
- 规则版本创建仍按全局 `rule_type` 命中 `rule_set`
|
|
- 没有 `tenant_code`
|
|
|
|
影响:
|
|
|
|
- 不同租户只要规则类型编码一致,就可能共用同一个规则集和版本链
|
|
|
|
### 3.1.3 评查组树所有读写都没有租户谓词
|
|
|
|
文件:
|
|
|
|
- [evaluationPointGroupServiceImpl.py](/home/wren-dev/Porject/leaudit-platform/fastapi_modules/fastapi_leaudit/services/impl/evaluationPointGroupServiceImpl.py)
|
|
|
|
问题:
|
|
|
|
- `ListGroups`
|
|
- `ListAllGroups`
|
|
- `GetGroup`
|
|
- `GetChildren`
|
|
- `ListGroupsByDocumentTypes`
|
|
- `CreateGroup`
|
|
- `UpdateGroup`
|
|
- `DeleteGroup`
|
|
- `BatchDelete`
|
|
- `RebindGroup`
|
|
- `CreateBinding`
|
|
- `UpdateBinding`
|
|
- `DeleteBinding`
|
|
|
|
以上全部没有显式 `tenant_code` 约束。
|
|
|
|
影响:
|
|
|
|
- 管理页看到的可能是跨租户混合分组
|
|
- 修改和删除可能直接作用到其他租户的业务树
|
|
|
|
### 3.1.4 `RebindGroup` 是最高危错挂点
|
|
|
|
问题:
|
|
|
|
- 直接按 `pid` 批量迁移子分组
|
|
- 只校验层级,不校验租户一致性
|
|
|
|
影响:
|
|
|
|
- 一次操作可能把多个二级子类型整体迁入错误根分组
|
|
|
|
### 3.1.5 分组/绑定表结构本身没有 `tenant_code`
|
|
|
|
文件:
|
|
|
|
- [ruleGroupSupport.py](/home/wren-dev/Porject/leaudit-platform/fastapi_modules/fastapi_leaudit/services/impl/ruleGroupSupport.py)
|
|
|
|
问题:
|
|
|
|
- `ensure_rule_group_schema` 建表时就没有租户字段
|
|
|
|
影响:
|
|
|
|
- 即使服务层开始传租户,也没有稳定持久化载体
|
|
|
|
## 3.2 文档后端主链路高风险
|
|
|
|
### 3.2.1 历史版本命中仍会 `tenant_code` 回退 `region`
|
|
|
|
文件:
|
|
|
|
- [documentServiceImpl.py](/home/wren-dev/Porject/leaudit-platform/fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py)
|
|
|
|
问题:
|
|
|
|
- `_find_latest_version_candidate`
|
|
- `_document_tenant_filter_sql`
|
|
- `_buildDocumentScopeFilters`
|
|
|
|
都还允许 `tenant_code` 不足时回退 `region`
|
|
|
|
影响:
|
|
|
|
- 新租户文档可能接到旧地区文档的历史版本链
|
|
|
|
### 3.2.2 `group_id / entry_module_id` 仍在查询时反推
|
|
|
|
问题:
|
|
|
|
- 列表和详情展示并不完全依赖文档上传时的稳定快照
|
|
- 而是会根据当前分组树状态推断
|
|
|
|
影响:
|
|
|
|
- 配置一变,历史文档显示的业务树就变了
|
|
- 容易造成“文档看起来属于当前子类型,但上传时并不属于”
|
|
|
|
### 3.2.3 上传仅校验 `group_id` 属于 `document_type_id`
|
|
|
|
问题:
|
|
|
|
- 没有进一步校验:
|
|
- 是否属于当前入口模块
|
|
- 是否属于当前租户允许范围
|
|
- 是否属于当前业务树
|
|
|
|
影响:
|
|
|
|
- 只要知道一个合法分组 ID,就可能落错业务边界
|
|
|
|
### 3.2.4 交叉评查链路会绕过原租户范围
|
|
|
|
问题:
|
|
|
|
- `GetReviewPoints`
|
|
- `AuditReviewPoint`
|
|
- `ConfirmReviewResults`
|
|
|
|
在交叉评查任务成员命中时会 bypass scope
|
|
|
|
影响:
|
|
|
|
- 如果业务上不允许跨租户交叉评查,这就是直接越界读取/更新
|
|
|
|
## 3.3 前端主链路高风险
|
|
|
|
### 3.3.1 上传页没有显式提交 `tenant_code`
|
|
|
|
文件:
|
|
|
|
- [FilesUploadClient.tsx](/home/wren-dev/Porject/leaudit-platform/legal-platform-frontend/app/(audit)/files/upload/FilesUploadClient.tsx)
|
|
- [files-upload.ts](/home/wren-dev/Porject/leaudit-platform/legal-platform-frontend/lib/api/legacy/files/files-upload.ts)
|
|
|
|
问题:
|
|
|
|
- 当前前端主要把 `tenant_name/area` 当 `region` 传
|
|
- 没有直接传 `tenant_code`
|
|
|
|
影响:
|
|
|
|
- 后端仍要猜
|
|
- 租户显示名不唯一时最容易落错数据
|
|
|
|
### 3.3.2 子类型分组下拉没有按 `tenant_code` 拉取
|
|
|
|
问题:
|
|
|
|
- `/api/v3/evaluation-point-groups/by-document-types`
|
|
- 只按 `document_type_ids`
|
|
- 最多本地再按 `entryModuleId` 过滤
|
|
|
|
影响:
|
|
|
|
- 不同租户复用同文档类型时,前端会把别的租户二级组混到下拉框
|
|
|
|
### 3.3.3 首页跳转/文档列表作用域没有显式携带租户
|
|
|
|
问题:
|
|
|
|
- 首页只传 `selectedModuleId/documentTypeIds`
|
|
- 文档列表和上传页继续从 sessionStorage/URL 恢复范围
|
|
|
|
影响:
|
|
|
|
- 模块是对的,不代表租户也是对的
|
|
- 最容易在跨页、刷新、缓存残留时串范围
|
|
|
|
---
|
|
|
|
## 4. 中风险问题清单
|
|
|
|
## 4.1 后端中风险
|
|
|
|
1. `_ensure_entry_module_valid` 只校验入口模块存在,不校验租户可用性
|
|
2. `_ensure_document_type_valid` 仍是全局文种唯一/存在校验
|
|
3. `_ensure_code_unique` 仍是全局唯一
|
|
4. `bootstrap_rule_groups`、`ensure_group_for_doc_type` 仍是假设全局分组树
|
|
5. 文档当前用户上下文仍依赖 `area` 兼容与硬编码管理角色
|
|
6. `review_point_audits` 没有租户/入口/分组快照
|
|
|
|
## 4.2 前端中风险
|
|
|
|
1. `rule-groups` 页面没有当前租户确认层
|
|
2. `document-types` 页面只围绕入口模块,不围绕租户
|
|
3. 首页系统概览仍用硬编码 `documentTypeIds` 推断业务
|
|
|
|
---
|
|
|
|
## 5. 低风险问题清单
|
|
|
|
1. 首页仍把 `user_area` 写入本地缓存
|
|
2. `region` 仍是多个接口 VO 的正式字段
|
|
3. 上传接口同时暴露 `typeId/typeCode/groupId/region/tenant_code` 多套口径
|
|
4. 多个管理页缺少“当前租户/当前作用域”显式展示
|
|
|
|
---
|
|
|
|
## 6. 正确的改造顺序
|
|
|
|
## 第一阶段:堵住最容易落错/查错数据的链路
|
|
|
|
1. 上传前端显式提交 `tenant_code`
|
|
2. 后端上传与列表停止 `tenant_code -> region` 混查
|
|
3. 上传时强校验 `group_id + document_type_id + entry_module_id + tenant_code`
|
|
4. 文档持久化真实 `group_id/root_group_id/entry_module_id/tenant_code`
|
|
|
|
## 第二阶段:补评查组/规则集的入口防线与读写边界
|
|
|
|
1. `RuleController` 全面加鉴权与权限
|
|
2. 评查组 controller/service 全链路透传租户
|
|
3. 分组树和绑定查询全部加租户过滤
|
|
4. `RebindGroup`、`CreateBinding`、`CreateRuleDraft` 改成租户内操作
|
|
|
|
## 第三阶段:补主数据模型
|
|
|
|
1. 给 `leaudit_evaluation_point_groups` 增加租户归属
|
|
2. 给 `leaudit_rule_group_bindings` 增加租户归属
|
|
3. 重新定义 `rule_set` 是否按租户隔离
|
|
4. 重做 `bootstrap_rule_groups / ensure_group_for_doc_type`
|
|
|
|
## 第四阶段:补界面确认层
|
|
|
|
1. 首页跳转带上租户上下文
|
|
2. 上传页/文档列表/评查组页显示当前租户
|
|
3. 子类型、规则集、文档列表全部按租户查询
|
|
|
|
---
|
|
|
|
## 7. 建议接下来直接执行的第一批任务
|
|
|
|
1. 后端:收紧文档主链路,去掉危险的 `tenant_code + region` 混查
|
|
2. 前端:上传显式传 `tenant_code`,文档列表显式带 `tenant_code`
|
|
3. 后端:给 `RuleController` 全量补 token 和权限
|
|
4. 后端:评查组接口开始透传 `tenant_code`
|
|
5. 文档:补“新平台主链路租户改造实施任务单”
|
|
|