[CLAUDE] Auth: gate ReportsController tai-chinh HD behind Reports.Read policy (reviewer S118)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m34s

Lo hong (reviewer S118 phat hien khi phan quyen PRO tra.bui): ReportsController chi co
[Authorize] tran (khong policy) -> MOI user dang nhap goi duoc GET /reports/dashboard (tong gia
tri HD dang chay, top NCC/du an theo gia tri, xu huong 12 thang) + GET /reports/contracts/export
(xuat toan bo HD ra Excel). Nguoc quyet dinh S92 "HD chi Admin thay" (menu an nhung API van mo,
go URL/API truc tiep lay duoc).

Fix (owner chot: chi Admin qua Reports.Read policy -- linh hoat mo role khac qua ma tran sau):
- Dashboard() + ExportContracts() -> them [Authorize(Policy = "Reports.Read")] per-method. Hien
  chi Admin co Reports.Read -> non-admin 403. Admin bypass moi policy nhu cu.
- MyDashboard() GIU MO (chi [Authorize] class-level) -- scope theo currentUser, moi user chi thay
  so cua CHINH MINH (draft/pending/SLA/gia tri draft cua minh), khong lo toan cong ty.

Test: +4 reflection authz regression (AuthorizePolicyRegressionTests, mirror gotcha #44 S45):
dashboard/export co Reports.Read; my-dashboard KHONG co action-policy (guard chong ai khoa nham
dashboard ca nhan). 505 -> 509. Gotcha #82 (menu-flag grant != quyen API that).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-07-14 15:55:18 +07:00
parent 1491d29b10
commit c808b9ad81
3 changed files with 84 additions and 0 deletions

View File

@ -1348,6 +1348,14 @@ for h in resp.points: # ← .points không phải iterable trực tiếp
**Bài học:** derived-invariant phải re-establish **MỌI mutation đổi tập-nguồn — kể cả DELETE + cascade-delete**, không chỉ write/select. **Enumeration writer trong spec KHÔNG đáng tin** grep độc-lập MỌI `.Remove(` chạm entity chứa field-nguồn (S87/S88 cardinality class mở rộng lên tầng mutation-path: writer-miss read-site-miss). Chỉ **verify adversarial-refute** bắt (invest/review/implement đều sót cùng theo enumeration). Liên-quan #72/#73 (cardinality) + `feedback_cardinality_change_grep_consumers`.
### 82. Menu-flag permission grant ≠ quyền API thật — controller gate GET bằng `[Authorize]` trần (Session 118)
**Triệu chứng:** S118 phân quyền role Procurement grant menu `Suppliers` (R+C+U) tưởng "menu hiện = làm được mọi thứ". THỰC TẾ 2 chiều lệch: (1) `PUT/DELETE /suppliers` gate `[Authorize(Roles="Admin,CatalogManager")]` PRO vẫn **403 khi Sửa/Xóa NCC** CanUpdate=1 (chỉ Publish/Import theo policy `Suppliers.Update` ăn); nút Sửa hiện nhưng bấm lỗi. (2) Ngược lại `ReportsController` chỉ `[Authorize]` **trần (KHÔNG policy)** menu bị S92 ẩn nhưng **MỌI user đăng nhập** vẫn gọi `GET /reports/dashboard` lấy tổng giá trị + top NCC/dự án theo giá trị ( URL/API trực tiếp, bỏ qua menu).
**Cơ chế:** Menu-tree (`GetMyMenuTree`) filter theo Permission `CanRead` CHỈ đổi HIỂN THỊ menu FE. Hiệu-lực API THẬT chỉ endpoint mang `[Authorize(Policy="X.Y")]` (map qua `MenuPermissionHandler`). Nhiều controller master gate GET bằng `[Authorize]` trần (mở mọi authed user, chủ ý S59) + gate write bằng `[Authorize(Roles=...)]` (KHÔNG theo menu-key). Nên grant "R/C/U" phần lớn = FE-button-visibility; delta API thật mong đợi.
**Fix:** (1) Endpoint lộ data nhạy gate `[Authorize(Policy="Reports.Read")]` per-method (S118 `dashboard` + `contracts/export`; `my-dashboard` giữ mở scope theo `currentUser`). (2) Quy tắc: TRƯỚC khi tin hiệu-lực 1 grant **grep authz-attribute của CHÍNH controller đích** (`[Authorize(Policy=...)]` vs `[Authorize(Roles=...)]` vs `[Authorize]` trần). **Bài học:** grant permission = 2 tầng ĐỘC-LẬP: menu-display (Permission table) API-authz (controller attribute) verify CẢ HAI. reviewer (adversarial) bắt được grep authz thật; em-main-solo tin "menu-flag=quyền" thì sót. Liên-quan #44 (silent 403).
---
## Checklist debug bug mới