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

7.0 KiB
Raw Blame History

变更审计记录:根治 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_accesssite_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_idbiz.tenants.idbiz.sites.tenant_id 关联查询
  • get_effective_site_ids(admin) — 统一入口,自动区分 admin_typetenant_admin 查库 / site_admin 用 JWT

改造 site_filter_clauseverify_site_access 支持 admin= keyword-only 参数(向后兼容旧的 positional managed_site_ids 签名)。

受影响文件

文件 改动内容
apps/backend/app/auth/tenant_admins.py 新增 get_tenant_site_idsget_effective_site_ids;改造 site_filter_clauseverify_site_access
apps/backend/app/routers/tenant_users.py 所有调用点改用 admin=adminlist_my_sites 简化为 get_effective_site_idslist_applications 回退头痛医头代码
apps/backend/app/routers/tenant_clues.py _get_clue_with_site_check 签名改为 admin: CurrentTenantAdminsearch_customersget_effective_site_idslist_customer_cluessite_filter_clause(admin=admin)
apps/backend/app/routers/tenant_excel.py 两个 verify_site_access 改用 admin=adminlist_upload_logssite_filter_clause 改用 admin=admin
apps/backend/app/routers/tenant_site_admins.py create_site_adminedit_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(内部 PKbiz.sites.tenant_id 三表关联查询活跃店铺。get_effective_site_ids() 作为统一入口,按 admin_type 分流tenant_admin 走查库路径site_admin 仍用 JWT。site_filter_clauseverify_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_siteslist_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_checksearch_customerslist_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_customersget_effective_site_ids(admin) 构建 IN 子句。list_customer_cluessite_filter_clause(admin=admin)。三个调用点edit_clue、delete_clue、toggle_visibility改传 admin 对象。
  • 修改结果:维客线索管理覆盖新建店铺,签名更语义化。

apps/backend/app/routers/tenant_excel.py

  • 变更类型:修改
  • 原始原因Excel 上传/确认/日志三个端点的权限验证使用静态 managed_site_ids,新建店铺的 Excel 数据无法上传和查看。
  • 思路分析:upload_excelconfirm_excel 中的 verify_site_access(site_id, admin.managed_site_ids) 改为 verify_site_access(site_id, admin=admin)list_upload_logssite_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_adminedit_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.pyAI_CHANGELOG 文档字符串移至 from __future__ 之后(消除 SyntaxError
BD 手册 不涉及

回滚方案

将 5 个文件的 admin=admin 参数改回 admin.managed_site_idspositional删除 tenant_admins.py 中新增的三个函数。旧签名向后兼容,无需修改函数定义。

验证 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;