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 (e88e75ddocs-reconcile +c2135baPRO-perm-seeder +c808b9aReports-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>
6.3 KiB
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-matrix→GetMyMenuTreeQuerygate menu thuần theo PermissionCanRead(KHÔNG Admin-bypass ở menu-tree). DbInitializer.cs:tra.buiseed 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ànPe_*(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 choMaster/Suppliers/Projects/Departments/Catalog*/Contracts/Hrm*/Off*/Personalvới MỌI role non-admin ("chỉ Admin thấy").Pe_*được chừa → PE giữ cho user.
Owner 3-decision (AskUser)
- Scope: Master đầy đủ (Suppliers + Projects + Departments + Catalogs + 4 leaf) + Dashboard + Reports.
- Durability: làm bền qua code (không để grant tay bị revoke reset next-deploy).
- Edit-NCC: chỉ công-bố/import/thêm-mới (KHÔNG sửa/xóa).
Fix 2 tầng
- Grant tạm prod — MERGE idempotent vào
Permissionscho 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. - Code-hóa bền — method mới
SeedProcurementMasterAccessAsyncchạy NGAY SAURevokeTemporarilyHiddenModulesAsync(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
ReportsgọiGET /reports/dashboardlộ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.Updatechỉ mở Publish/Import;PUT/DELETE /suppliersgate[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:
ReportsControllerchỉ[Authorize]trần (không policy) → MỌI user đăng nhập gọi đượcGET /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 (scopecurrentUser, an toàn). - Owner chốt: chỉ Admin (gate
Reports.Readpolicy — linh hoạt mở role khác qua ma trận). - Fix:
[Authorize(Policy="Reports.Read")]per-method choDashboard+ExportContracts;MyDashboardgiữ[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.lethanhvsthanh.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.