Files
solution-erp/docs/changelog/sessions/2026-07-14-S118-perm-pro-tra-bui-reports-authz.md
pqhuy1987 3fde8afb93 [CLAUDE] Docs: S118 closeout - STATUS/HANDOFF/session-log + memory reconcile (509 test / gotcha 82)
Session S118: phan quyen PRO tra.bui (Procurement exception to S92 revoke) + va lo hong
ReportsController authz. 2 prod deploy #388/#389 verified; 3 code/docs commit da push
(e88e75d docs-reconcile + c2135ba PRO-perm-seeder + c808b9a Reports-authz).

Closeout (docs/memory-only -> CI-skip per paths-ignore):
- STATUS: S118 mega-line blurb + canonical rows Tests 501->509 + Gotchas 81->82
  (governance-detector [OK] canonical matches disk 509/82).
- HANDOFF: S118 brief + NEXT anh (tra.bui re-login · binh.le decision · account-trung Le-Thanh-Binh)
  + lessons (wipe-durability / menu-flag!=API / verify-lane-garble-recover / Serilog-durability-proof).
- Session log 2026-07-14-S118-perm-pro-tra-bui-reports-authz.md.
- User memory: +feedback_permission_grant_two_layers (menu-flag grant != API-power, 2-layer verify);
  garble-recover x18->x20 (cicd VERIFY-lane x2 -> em-main-solo curl+sqlcmd).
- Agent-memory: cicd fidelity-fix (harvest-curator F1: false "CLEAN-return" label -> garble+recover,
  substance disk-verified TRUE) + tooling-auditor/harvest-curator @end diaries (H1/H2 gate PASS 5-truc).
- H22 §5.0: fold 2 trailing wal: flush commits (670c888 + e5f655f) into this closeout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 16:29:55 +07:00

6.3 KiB
Raw Blame History

S118 — 2026-07-14 — Phân quyền PRO tra.bui + vá lỗ hổng ReportsController

Nguồn: UAT anh Kiệt FDC (chat Zalo) — "phân quyền tra.bui đi nhé" (ảnh chat: bạn PRO login không thấy menu). Mode: HMW-ON · lead Opus 4.8 (owner-choice per-session) · em-main-led + 7 spawn + 3 script. Commit: e88e75d (docs-reconcile bootstrap) + c2135ba (PRO-perm seeder + test) + c808b9a (Reports-authz + test + gotcha) — 3 pushed, 2 deploy #388/#389 verified.

Bối cảnh

Anh Kiệt báo tra.bui (Bùi Lê Thủy Trà, TP Cung ứng) đăng nhập không thấy menu quản lý NCC/Suppliers, dù "em phân quyền hình như đủ". Chat xoay quanh công bố NCC (S113: phải có Mã NCC mới cho công bố).

Task 1 — Phân quyền tra.bui (Procurement)

Điều tra (read-only, không đoán)

  • Skill permission-matrixGetMyMenuTreeQuery gate menu thuần theo Permission CanRead (KHÔNG Admin-bypass ở menu-tree).
  • DbInitializer.cs: tra.bui seed sẵn roles [Procurement, DeptManager].
  • Prod read-only (SSH vietreport-vps + sqlcmd đọc password từ appsettings.Production.json, cơ chế plan-ca): tra.bui active OK, roles đúng, nhưng chỉ đọc 11 menu — toàn Pe_* (Duyệt NCC), KHÔNG Master/Suppliers.
  • Root-cause: seeder RevokeTemporarilyHiddenModulesAsync (S92 owner-decision, dòng ~2140-2181) chạy mỗi startup set CanRead=0 cho Master/Suppliers/Projects/Departments/Catalog*/Contracts/Hrm*/Off*/Personal với MỌI role non-admin ("chỉ Admin thấy"). Pe_* được chừa → PE giữ cho user.

Owner 3-decision (AskUser)

  1. Scope: Master đầy đủ (Suppliers + Projects + Departments + Catalogs + 4 leaf) + Dashboard + Reports.
  2. Durability: làm bền qua code (không để grant tay bị revoke reset next-deploy).
  3. Edit-NCC: chỉ công-bố/import/thêm-mới (KHÔNG sửa/xóa).

Fix 2 tầng

  1. Grant tạm prod — MERGE idempotent vào Permissions cho RoleId Procurement (5B21CDC1-...): tra.bui 11→22 (verify LIVE). Row Master/Suppliers đã tồn tại all-0 (placeholder) → UPDATE; Dashboard/Reports chưa có → INSERT.
  2. Code-hóa bền — method mới SeedProcurementMasterAccessAsync chạy NGAY SAU RevokeTemporarilyHiddenModulesAsync (mirror pattern re-grant-thắng-revoke S65 Hrm / S69 Office). CHỈ role Procurement, upgrade-only (nâng cờ desired lên true, không hạ, không đụng role khác).

Reviewer catch (PASS-có-điều-kiện) → owner refine

  • M1: menu Reports gọi GET /reports/dashboard lộ TotalValueActive + top NCC/dự án theo giá trị + xu hướng 12 tháng → ngược S92 ("HĐ chỉ Admin thấy"). Owner chốt bỏ cả Reports + Dashboard → seeder còn 9 key, tra.bui còn 20 menu. Revert Dashboard/Reports trên prod DB.
  • M2: Suppliers.Update chỉ mở Publish/Import; PUT/DELETE /suppliers gate [Authorize(Roles="Admin,CatalogManager")] → PRO vẫn 403 khi Sửa/Xóa NCC. Owner chốt chỉ cần công-bố/import/thêm → grant hiện đủ.

Verify durability (bằng chứng vàng)

Serilog prod sau deploy restart:

15:35:47.115 [INF] Revoked 9 permission rows ... (chỉ Admin thấy - S58 + S92)
15:35:47.159 [INF] Seeded Procurement master access: 0 added + 9 upgraded ... (PRO exception to S92, S118)

→ revoke set 9 row false, seeder lật lại true 44ms sau ⇒ seeder THẮNG revoke trên startup thật. Verify ×2 deploy (tra.bui giữ 20). Scope-isolation prod: binh.le (CostControl, no-Procurement)=11 · DeptManager-standalone Master/Suppliers=0 (S92 nguyên vẹn role khác).

Test

+4 ProcurementMasterAccessSeedTests (reflection gọi private seeder, không đụng prod code): upgrade-path + scope-isolation (DeptManager riêng vẫn revoke) + insert-path + idempotent. 501→505.

Task 2 — Vá lỗ hổng ReportsController (task anh mở từ chip reviewer flag)

  • Lỗ hổng: ReportsController chỉ [Authorize] trần (không policy) → MỌI user đăng nhập gọi được GET /reports/dashboard (tài chính HĐ toàn công ty) + GET /reports/contracts/export (dump toàn bộ HĐ). Menu bị S92 ẩn nhưng API mở.
  • Phân loại DTO: dashboard + contracts/export = company-wide nhạy cảm; my-dashboard = per-user (scope currentUser, an toàn).
  • Owner chốt: chỉ Admin (gate Reports.Read policy — linh hoạt mở role khác qua ma trận).
  • Fix: [Authorize(Policy="Reports.Read")] per-method cho Dashboard + ExportContracts; MyDashboard giữ [Authorize] trần.
  • Test: +4 reflection authz (AuthorizePolicyRegressionTests, mirror gotcha #44 S45). 505→509.
  • Prod verify: Reports.Read=chỉ Admin (DB), dashboard no-token=401, health 200×3. Non-admin→403 by-construction.
  • gotcha #82 ghi: menu-flag grant ≠ quyền API thật (display-layer ⟂ API-authz-layer).

Lessons

  • wipe-durability-check-reseed (#75/#76): grant tay prod sẽ bị seeder revoke reset next-deploy → phải code-hóa; bắt kịp TRƯỚC khi báo "done".
  • menu-flag ≠ API-power (#82): grep authz-attribute controller đích trước khi tin hiệu-lực grant.
  • #53 cicd-garble ×2 (verify-lane): cả 2 cicd-monitor trả memory-curation thay verdict → em-main-solo recover (curl 401/health + sqlcmd DB, 2 nguồn khớp). verify-lane garble ≠ mất-việc.
  • prod-config-inspect-before-change: điều tra prod-DB THẬT (SSH sqlcmd) trước khi kết luận — lật ngược giả thuyết ban đầu ("thiếu quyền" → thực ra "scope gap + cache").
  • Serilog-as-durability-proof: log-line startup = bằng chứng seeder thắng revoke (mạnh hơn end-state DB đơn thuần).

Carry (chờ anh)

  • binh.le (CostControl+Drafter, 11 menu, khác phòng CCM) — có cần thấy NCC?
  • 2 account trùng "Lê Thanh Bình": binh.lethanh vs thanh.lethanh (cùng roles, cùng 20 menu) — dùng cái nào, dọn không?
  • Audit authz-vs-menu toàn bộ (nhiều controller master [Authorize] trần — gotcha #82) — non-blocking, có thể task riêng.

State

Mig 66 · 89 bảng · 509 test (45 Domain + 464 Infra) · gotcha 82 · menu 54 · bundle admin cFI7ih4a/user MxgooVZw (FE FROZEN — session BE/permission-only) · prod 37 NCC/17 user.