FR-048 Phase 3 交接 prompt(主體層 decorator・軸①②,可與 2b 並行)

下面整段是給執行 session 的 prompt。前置:Phase 1 已 merge、Phase 2a 矩陣已產出且 user 已定調。與 Phase 2b 並行安全(不同軸、不同檔案面)。


【接手主題】FR-048 統一授權守門 Phase 3 — 建主體層 route decorator(platform-admin / super-admin),照矩陣掛上軸①②端點

必讀(照順序):

  1. docs/features/FR-048-2607-unified-auth-guard/endpoint-authz-matrix.md(工作清單=軸①②且 Phase=3 的列)
  2. docs/analysis/2026-07-07-unified-auth-guard-design.md §2.0 兩層半模型、§3.1 雙軌定調(P1 已拍板)、§7 Phase 3 注意事項
  3. common/authz/ 現況(Phase 1 產物:platform.py / admin.py)

【branch】fix/v1.8.0-bugs,禁止切 branch。不動 jedi- 套件*(decorator 掛主專案 route 即可守住套件 service)。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 【第一步:建 decorator(新檔 common/authz/decorators.py)】

def require_platform_admin_route(fn):
    """Route decorator:非平台(root tenant)管理員 403。委派 common.authz.platform.require_platform_admin()。
    只判主體、不碰資源、不開 session(純 context 判定)。掛在 @jwt_required() 之後(需 user context 已建)。"""

def require_super_admin_route(fn):
    """Route decorator:非帳號層 super admin 403。委派 common.authz.admin.require_super_admin()。
    【過渡性質】Phase 4 capability 軸就緒後逐端點換 @require_capability——掛此 decorator 的端點必須登記過渡名單。"""
  • functools.wraps 保留 metadata(flask-apispec/swagger 靠它)。
  • 例外直接讓 guard 拋 ForbiddenError(jedi_common error handler 會接成標準 envelope 403);確認 route 層拋出後 handler 有接到(decorator 在 MethodResource dispatch 內,錯誤處理路徑要實測一支)。
  • 順序慣例定死並寫進 docstring:@jwt_required() 在外、authz decorator 在內。

【第二步:照矩陣掛端點】

  • 插隊件(最優先,矩陣 D14/D15 已定調)
    • GET /schedule-report/previewapi/report/無 JWT 且 query 拼路徑=任意檔讀取——補 @jwt_required() + @require_super_admin_route + path 消毒(禁 ../絕對路徑,鎖定合法報表目錄)。改前查 FE 呼叫方式(若 window.open 需一併處理,比照 A 族前科)。
    • POST /send-user-test-mailapi/auth/__init__.py:86user_route.py:159直接移除 route(debug 殘留,hardcoded email)。
  • 首發:SEC-002api/log export/list 掛 @require_platform_admin_route(LOG 能力 2026-06-29 migration 已設 platform-only,正解現成;SEC-001 的 JWT 已由 182bb2f7 恢復)。
  • 其餘照矩陣:全域資源寫入 → platform;租戶級系統設定/功能(SMTP/LDAP/通知/選單/設備/資訊系統/公告/問卷管理…)→ super-admin 過渡
  • 角色要求一律照矩陣列,不自行判斷;矩陣標「需 user 定調」的跳過。

【順手項(矩陣 §4 C 類)】全站 22 處 inline ctx.is_admin / getattr(ctx,'is_admin')(cloud_integration / notify_config / flow_engine stage 等)換成 common.authzrequire_super_admin() / viewer_is_super_admin()——同為帳號層語意、零行為變更,消滅兩個 is_admin 的語意混用面。逐處換前確認原判定確實是帳號層語意(讀 UserContextDTO 的都是)。

【第三步:過渡名單登記(不做 Phase 4 會變永久過渡)】

掛了 @require_super_admin_route 的端點,逐支登記到矩陣「Phase 4 待換能力點」欄(或矩陣檔尾新開一段清單),註明建議的能力點名(<resource>.<action> 命名照權限 SOP)。

【驗證】

  • 每掛一支補測試:無權帳號 403(含 error code 斷言)、有權帳號照舊。
  • 覆蓋盤點:矩陣軸①②列全數掛完後,grep -rn "require_platform_admin_route\|require_super_admin_route" api/ 對帳矩陣,數量要合。
  • pytest test/ 全綠;請 user 重啟 BE 手測 2-3 支(含一支 403 案例)。
  • FE 影響確認:403 對 FE 是新行為——確認 FE 對這些頁本來就藏按鈕(route_capabilities 可見性),一般使用者實際上打不到;若有頁面 FE 沒藏(矩陣備註欄查)→ 列出來回報 user,別默默上。

【commit】按模組分批(顯式 add、禁 -am、不 push):fix(FR-048): <模組> 掛主體層守門(軸①/②);decorator 本體單獨一個 commit。收尾等 user 下令。