Files
Neo-ZQYY/docs/audit/changes/2026-03-23__tenant-admin-site-access-root-fix.md
Neo 14a12342b5 chore(audit): 补追 96 份未入仓审计孤本 — 覆盖 2026-02-26 ~ 2026-04-08
这些审计记录原本堆积在 docs/audit/changes/changes/ 嵌套误产物目录下(由开发机迁移
79d3c2e 前后的不明批量操作产生)。由于同期 .gitignore 屏蔽了 docs/audit/ 全目录,
它们从未入过 git 任何分支 history。删除即永久丢失。

按 docs/specs/audit-gap-recovery/tasks.md 阶段 1 执行,将全部 96 份 D 类孤本
(主目录无同名、git history 亦无记录)复制到 docs/audit/changes/ 主目录入仓。

涵盖主题: P1-P18 全栈集成 / 多模块累积变更 / ETL bug 修复 / 业务日切 /
   召回与任务引擎改造 / 租户管理与审批 / 董事会财务 / 客户与助教详情 /
   DDL 基线合并 / Kiro 到 Claude Code 迁移

阶段 2(B 类内容漂移 1 份)和阶段 4(嵌套目录删除)独立推进。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 06:35:42 +08:00

87 lines
7.0 KiB
Markdown
Raw Permalink 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.
# 变更审计记录:根治 tenant_admin 的 managed_site_ids 限制(跨模块权限验证改造)
| 字段 | 值 |
|------|-----|
| 日期 | 2026-03-23 21:00:00 |
| Prompt-ID | P20260323-210000 |
| Session-ID | f0a03585 |
| Session 路径 | docs/audit/session_logs/2026-03/23/21_28b7ab84_173223 |
## 操作摘要
根治 tenant_admin 的 JWT `managed_site_ids` 静态签发问题。tenant_admin 新建店铺后 JWT 中不含新店铺 ID导致所有使用 `verify_site_access``site_filter_clause` 的端点无法访问新店铺。方案tenant_admin 按 `tenant_id` 实时查 `biz.sites` 获取有效 site_idssite_admin 仍用 JWT 中的 `managed_site_ids`。改造涉及 5 个文件、跨 4 个路由模块。
## 核心方案
`tenant_admins.py` 新增两个函数:
- `get_tenant_site_ids(tenant_id)` — 通过 `biz.tenants.tenant_id``biz.tenants.id``biz.sites.tenant_id` 关联查询
- `get_effective_site_ids(admin)` — 统一入口,自动区分 admin_typetenant_admin 查库 / site_admin 用 JWT
改造 `site_filter_clause``verify_site_access` 支持 `admin=` keyword-only 参数(向后兼容旧的 positional `managed_site_ids` 签名)。
## 受影响文件
| 文件 | 改动内容 |
|------|----------|
| `apps/backend/app/auth/tenant_admins.py` | 新增 `get_tenant_site_ids``get_effective_site_ids`;改造 `site_filter_clause``verify_site_access` |
| `apps/backend/app/routers/tenant_users.py` | 所有调用点改用 `admin=admin``list_my_sites` 简化为 `get_effective_site_ids``list_applications` 回退头痛医头代码 |
| `apps/backend/app/routers/tenant_clues.py` | `_get_clue_with_site_check` 签名改为 `admin: CurrentTenantAdmin``search_customers``get_effective_site_ids``list_customer_clues``site_filter_clause(admin=admin)` |
| `apps/backend/app/routers/tenant_excel.py` | 两个 `verify_site_access` 改用 `admin=admin``list_upload_logs``site_filter_clause` 改用 `admin=admin` |
| `apps/backend/app/routers/tenant_site_admins.py` | `create_site_admin``edit_site_admin` 的权限子集校验改用 `get_effective_site_ids(admin)` |
## 影响范围
- 所有 `/api/tenant/*` 端点的门店权限验证
- tenant_admin 新建店铺后无需重新登录即可访问
- site_admin 行为不变(仍用 JWT managed_site_ids
## 改动注解
### `apps/backend/app/auth/tenant_admins.py`
- 变更类型:修改
- 原始原因JWT `managed_site_ids` 是登录时静态签发的,新建店铺后不在列表中,导致 tenant_admin 无法访问新店铺的任何端点。需要一个统一的动态获取机制。
- 思路分析:新增 `get_tenant_site_ids()` 通过 `biz.tenants.tenant_id`(外部标识)→ `biz.tenants.id`(内部 PK`biz.sites.tenant_id` 三表关联查询活跃店铺。`get_effective_site_ids()` 作为统一入口,按 `admin_type` 分流tenant_admin 走查库路径site_admin 仍用 JWT。`site_filter_clause``verify_site_access` 新增 `admin=` keyword-only 参数,优先使用 admin 参数,也兼容旧的 positional `managed_site_ids` 直传方式,实现向后兼容。
- 修改结果:所有下游路由只需传 `admin=admin` 即可自动获得正确的 site_ids无需关心 admin_type 差异。biz.sites 数据量极小(几条),无需缓存。
### `apps/backend/app/routers/tenant_users.py`
- 变更类型:修改
- 原始原因该路由的所有端点my-sites、applications、users、approve、reject、edit、binding都直接使用 `admin.managed_site_ids`,新建店铺后全部受限。此前 `list_my_sites``list_applications` 已有针对 tenant_admin 的特殊处理(头痛医头),需要统一回退。
- 思路分析:所有 `verify_site_access(site_id, admin.managed_site_ids)` 改为 `verify_site_access(site_id, admin=admin)`;所有 `site_filter_clause(admin.managed_site_ids)` 改为 `site_filter_clause(admin=admin)``list_my_sites` 简化为直接调用 `get_effective_site_ids(admin)` 查库,删除之前针对 tenant_admin 的特殊 SQL 分支。`list_applications` 回退之前的 tenant_admin 特殊处理代码,统一走 `site_filter_clause(admin=admin)`
- 修改结果10 个端点的权限验证统一收口代码更简洁。tenant_admin 新建店铺后所有用户管理功能立即可用。
### `apps/backend/app/routers/tenant_clues.py`
- 变更类型:修改
- 原始原因:维客线索管理的 `_get_clue_with_site_check``search_customers``list_customer_clues` 都直接使用 `admin.managed_site_ids`,新建店铺的线索无法查看和管理。
- 思路分析:`_get_clue_with_site_check` 签名从接受 `managed_site_ids: list[int]` 改为接受 `admin: CurrentTenantAdmin`,内部调用 `verify_site_access(site_id, admin=admin)``search_customers``get_effective_site_ids(admin)` 构建 IN 子句。`list_customer_clues``site_filter_clause(admin=admin)`。三个调用点edit_clue、delete_clue、toggle_visibility改传 admin 对象。
- 修改结果:维客线索管理覆盖新建店铺,签名更语义化。
### `apps/backend/app/routers/tenant_excel.py`
- 变更类型:修改
- 原始原因Excel 上传/确认/日志三个端点的权限验证使用静态 `managed_site_ids`,新建店铺的 Excel 数据无法上传和查看。
- 思路分析:`upload_excel``confirm_excel` 中的 `verify_site_access(site_id, admin.managed_site_ids)` 改为 `verify_site_access(site_id, admin=admin)``list_upload_logs``site_filter_clause(admin.managed_site_ids)` 改为 `site_filter_clause(admin=admin)`
- 修改结果Excel 上传/确认/日志覆盖新建店铺。
### `apps/backend/app/routers/tenant_site_admins.py`
- 变更类型:修改
- 原始原因创建和编辑店铺管理员时权限子集校验site_admin 的 managed_site_ids 必须是 tenant_admin 管辖范围的子集)使用静态 `admin.managed_site_ids`,导致无法为新建店铺分配管理员。
- 思路分析:`create_site_admin``edit_site_admin` 中的子集校验从 `set(body.managed_site_ids) <= set(admin.managed_site_ids)` 改为 `set(body.managed_site_ids) <= set(get_effective_site_ids(admin))`
- 修改结果tenant_admin 可以为新建店铺创建和编辑店铺管理员。
## 合规检查
| 检查项 | 状态 |
|--------|------|
| 新增迁移 SQL | 无 |
| DDL 基线 | 不涉及 |
| OpenAPI spec | ✅ 已重新导出137 paths, 194 schemas。附带修复`tenant_admins.py``AI_CHANGELOG` 文档字符串移至 `from __future__` 之后(消除 SyntaxError |
| BD 手册 | 不涉及 |
## 回滚方案
将 5 个文件的 `admin=admin` 参数改回 `admin.managed_site_ids`positional删除 `tenant_admins.py` 中新增的三个函数。旧签名向后兼容,无需修改函数定义。
## 验证 SQL
```sql
-- 验证 tenant_admin 的 tenant_id 能查到所有店铺(含新建)
SELECT s.site_id, s.site_name, s.site_code
FROM biz.sites s
JOIN biz.tenants t ON t.id = s.tenant_id
WHERE t.tenant_id = 'LLZQ' AND t.is_active = true AND s.is_active = true;
```