[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
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:
@ -200,4 +200,71 @@ public class AuthorizePolicyRegressionTests
|
||||
attr.Should().NotBeNull("POST satellite CreateWorkHistory phải có action-level policy");
|
||||
attr!.Policy.Should().Be("Hrm_HoSo.Create");
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// S118 (2026-07-14) — Reviewer catch: ReportsController lỗ hổng lộ tài chính toàn công ty.
|
||||
//
|
||||
// Trước S118: cả controller chỉ [Authorize] class-level trần → MỌI user đăng nhập gọi được
|
||||
// GET /reports/dashboard (tổng giá trị HĐ, top NCC/dự án theo giá trị, xu hướng 12 tháng) +
|
||||
// GET /reports/contracts/export (xuất TOÀN BỘ HĐ ra Excel) → lộ tài chính HĐ toàn công ty.
|
||||
//
|
||||
// Fix (owner chốt, khớp S92 "HĐ chỉ Admin thấy"): 2 endpoint company-wide gate Policy
|
||||
// "Reports.Read" (hiện chỉ Admin có) — mở role khác = cấp qua ma trận, KHÔNG sửa code.
|
||||
// my-dashboard CỐ Ý giữ mở (scope theo currentUser — mỗi user chỉ thấy số của chính mình).
|
||||
//
|
||||
// Regression coverage: nếu ai gỡ Policy khỏi 2 endpoint company-wide → tái lộ tài chính;
|
||||
// hoặc thêm nhầm Policy vào my-dashboard → khóa dashboard CÁ NHÂN của role không-Admin.
|
||||
// Cả 2 hướng đều FAIL test ngay, không cần UAT reproduce.
|
||||
// ===================================================================
|
||||
|
||||
[Fact]
|
||||
public void ReportsController_ClassLevel_AuthorizeOnly_NoPolicy_NoRoles()
|
||||
{
|
||||
// Class-level [Authorize] trần (any-authenticated) — gate company-wide đặt PER-ACTION.
|
||||
// Nếu hardcode Policy/Roles ở class-level sẽ khóa luôn my-dashboard cá nhân (gotcha #44).
|
||||
var attr = GetClassLevelAuthorize(typeof(ReportsController));
|
||||
|
||||
attr.Should().NotBeNull("ReportsController phải có [Authorize] class-level chặn anonymous");
|
||||
attr!.Policy.Should().BeNull(
|
||||
"class-level KHÔNG được hardcode Policy — my-dashboard cần mở cho mọi authenticated. " +
|
||||
"Gate company-wide đặt action-level (Reports.Read).");
|
||||
attr.Roles.Should().BeNull("class-level KHÔNG được hardcode Roles");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReportsController_Dashboard_GET_RequiresReportsReadPolicy()
|
||||
{
|
||||
// GET /reports/dashboard = báo cáo TOÀN CÔNG TY → phải gate Reports.Read (chỉ Admin, S118).
|
||||
var attr = GetActionAuthorize(typeof(ReportsController), nameof(ReportsController.Dashboard));
|
||||
|
||||
attr.Should().NotBeNull("GET dashboard (báo cáo toàn công ty) phải có [Authorize(Policy = ...)]");
|
||||
attr!.Policy.Should().Be("Reports.Read",
|
||||
"dashboard company-wide (tổng giá trị HĐ, top NCC/dự án, xu hướng 12 tháng) chỉ Admin — " +
|
||||
"gate Reports.Read (S118 vá lỗ hổng lộ tài chính). Gỡ Policy = tái lộ toàn công ty.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReportsController_ExportContracts_GET_RequiresReportsReadPolicy()
|
||||
{
|
||||
// GET /reports/contracts/export = xuất TOÀN BỘ HĐ ra Excel → gate Reports.Read (chỉ Admin, S118).
|
||||
var attr = GetActionAuthorize(typeof(ReportsController), nameof(ReportsController.ExportContracts));
|
||||
|
||||
attr.Should().NotBeNull("GET contracts/export (xuất toàn bộ HĐ) phải có [Authorize(Policy = ...)]");
|
||||
attr!.Policy.Should().Be("Reports.Read",
|
||||
"export toàn bộ HĐ ra Excel = nhạy cảm → gate Reports.Read chỉ Admin (S118).");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReportsController_MyDashboard_GET_StaysOpen_NoActionPolicy()
|
||||
{
|
||||
// GET /reports/my-dashboard CỐ Ý giữ mở (chỉ inherit class-level [Authorize] any-authenticated)
|
||||
// — scope theo currentUser.UserId, mỗi user chỉ thấy số của CHÍNH MÌNH, không lộ toàn công ty.
|
||||
// Nếu ai thêm [Authorize(Policy=...)] action-level → khóa nhầm dashboard CÁ NHÂN của mọi role
|
||||
// không có Reports.Read (403 silent). Cho phép bare [Authorize] (vô hại) nhưng CHẶN mọi Policy.
|
||||
var attr = GetActionAuthorize(typeof(ReportsController), nameof(ReportsController.MyDashboard));
|
||||
|
||||
(attr?.Policy).Should().BeNull(
|
||||
"GET my-dashboard KHÔNG được gate Policy — mỗi user chỉ thấy số của chính mình. " +
|
||||
"Thêm policy sẽ khóa dashboard cá nhân của role không có Reports.Read (S118 giữ mở có chủ đích).");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user