[CLAUDE] Auth: PRO role giu quyen Master/NCC qua deploy (seeder chay-sau-revoke) + scope-isolation test
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m33s

Van de (UAT anh Kiet FDC, S118): user tra.bui (Bui Le Thuy Tra, Procurement TP) dang nhap khong
thay menu quan ly NCC/Suppliers. Nguyen nhan: seeder S92 RevokeTemporarilyHiddenModulesAsync (chay
moi startup) set CanRead=false cho Master/Suppliers/Projects/Departments/Catalog* voi moi role
non-Admin ("chi Admin thay").

Fix (owner chot mo ngoai-le CHI role Procurement, ben qua deploy):
- Them SeedProcurementMasterAccessAsync chay NGAY SAU revoke de THANG (mirror pattern Hrm S65 /
  Office S69). Chi dung role Procurement -- S92 "chi Admin thay" GIU NGUYEN cho moi role khac.
- Grant 9 key: Suppliers (R+C+U -- quan ly + cong bo NCC, S113 publish can Suppliers.Update);
  Master/Projects/Departments/Catalogs + 4 Catalog leaf (Read-only). KHONG Delete.
- Dashboard/Reports CO Y KHONG cap (reviewer bat: Reports lo tong gia tri HD + top NCC/du an theo
  gia tri, nguoc S92; owner chot bo).
- Upgrade-only idempotent: nang co desired len true, khong ha co, khong dung role khac.

Test: +4 regression scope-isolation (ProcurementMasterAccessSeedTests) -- Procurement duoc nang
dung ma tran, role khac (DeptManager rieng) VAN bi revoke. 501 -> 505.

Reviewer PASS (scope cach-ly sach, no over-grant). Grant tam da ap prod (tra.bui verified 20 menu);
deploy nay = ban ben. Follow-up: gate ReportsController thieu policy (task rieng).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-07-14 15:29:44 +07:00
parent b56659b6b7
commit c2135bae2b
2 changed files with 338 additions and 0 deletions

View File

@ -2007,6 +2007,12 @@ public static class DbInitializer
// Mở lại sau golive: gỡ prefix khỏi revoke + thêm lại vào InReviewScope.
await RevokeTemporarilyHiddenModulesAsync(db, roleManager, logger);
// [S118 2026-07-14 — anh Kiệt FDC] CHẠY SAU revoke để THẮNG: NGOẠI-LỆ role Procurement
// (Phòng Cung ứng) — mở lại Danh mục (Master/Suppliers/Projects/Departments/Catalog*) RIÊNG
// cho PRO (cần quản lý + CÔNG BỐ NCC — Suppliers R+C+U). S92 "chỉ Admin thấy" GIỮ NGUYÊN cho
// MỌI role khác (seeder chỉ đụng role Procurement). Dashboard/Reports KHÔNG cấp (anh chốt bỏ).
await SeedProcurementMasterAccessAsync(db, roleManager, logger);
// [S65 2026-06-16] CHẠY SAU revoke để THẮNG: mở lại quyền XEM "Hồ sơ Nhân sự"
// (+ root nhóm "Nhân sự") cho MỌI role — anh chốt public module Nhân sự cho
// user thường tra cứu hồ sơ. Dashboard NS (Hrm_Dashboard) + 6 catalog
@ -2180,6 +2186,83 @@ public static class DbInitializer
}
}
// [S118 2026-07-14 — anh Kiệt FDC] NGOẠI-LỆ role Procurement (Phòng Cung ứng): mở lại quyền
// Danh mục (Master + Suppliers + Projects + Departments + Catalog*) RIÊNG cho role Procurement
// — anh chốt PRO cần quản lý + CÔNG BỐ NCC (Suppliers R+C+U; S113 publish cần Suppliers.Update).
// S92 "chỉ Admin thấy" GIỮ NGUYÊN cho MỌI role khác. CHẠY SAU RevokeTemporarilyHiddenModulesAsync
// để THẮNG revoke (mirror pattern S65/S69). Dashboard/Reports CỐ Ý KHÔNG cấp (anh chốt bỏ —
// Reports lộ tổng giá trị HĐ + top NCC/dự án theo giá trị, ngược S92; reviewer S118 bắt).
// - Suppliers: Read+Create+Update (quản lý + công bố NCC). KHÔNG Delete.
// - Master + Catalogs (group) + Projects + Departments + 4 Catalog leaf: Read-only.
// - UPGRADE-ONLY (mirror Hrm S65 / Office S69): row đã tồn tại (revoke vừa set false) → NÂNG
// cờ desired lên true. Row chưa có (DB mới) → tạo. KHÔNG hạ cờ + KHÔNG đụng role khác.
// Thu hồi khi cần: xóa call ở SeedAsync — revoke sẽ tự che Master/Suppliers lại lần seed kế.
private static async Task SeedProcurementMasterAccessAsync(
ApplicationDbContext db, RoleManager<Role> roleManager, ILogger logger)
{
var role = await roleManager.FindByNameAsync(AppRoles.Procurement);
if (role is null)
{
logger.LogWarning("SeedProcurementMasterAccessAsync: skip — Procurement role chưa seed.");
return;
}
// (MenuKey, CanRead, CanCreate, CanUpdate) — CanDelete luôn giữ nguyên (KHÔNG cấp Delete).
var grants = new (string Key, bool R, bool C, bool U)[]
{
(MenuKeys.Master, true, false, false),
(MenuKeys.Suppliers, true, true, true), // quản lý + CÔNG BỐ NCC (S113 cần Update)
(MenuKeys.Projects, true, false, false),
(MenuKeys.Departments, true, false, false),
(MenuKeys.Catalogs, true, false, false),
(MenuKeys.CatalogUnits, true, false, false),
(MenuKeys.CatalogMaterials, true, false, false),
(MenuKeys.CatalogServices, true, false, false),
(MenuKeys.CatalogWorkItems, true, false, false),
};
var keys = grants.Select(g => g.Key).ToArray();
var existing = (await db.Permissions
.Where(p => p.RoleId == role.Id && keys.Contains(p.MenuKey))
.ToListAsync())
.ToDictionary(p => p.MenuKey);
var added = 0;
var upgraded = 0;
foreach (var g in grants)
{
if (existing.TryGetValue(g.Key, out var row))
{
var changed = false;
if (g.R && !row.CanRead) { row.CanRead = true; changed = true; }
if (g.C && !row.CanCreate) { row.CanCreate = true; changed = true; }
if (g.U && !row.CanUpdate) { row.CanUpdate = true; changed = true; }
if (changed) upgraded++;
continue;
}
db.Permissions.Add(new Permission
{
RoleId = role.Id,
MenuKey = g.Key,
CanRead = g.R,
CanCreate = g.C,
CanUpdate = g.U,
CanDelete = false,
});
added++;
}
if (added > 0 || upgraded > 0)
{
await db.SaveChangesAsync();
logger.LogInformation(
"Seeded Procurement master access: {Added} added + {Upgraded} upgraded " +
"(Master/Suppliers/Projects/Departments/Catalog* — PRO exception to S92, S118)",
added, upgraded);
}
}
// [S65 2026-06-16] Mở quyền XEM (Read-only) "Hồ sơ Nhân sự" cho MỌI role: anh chốt
// public module Nhân sự (trọng tâm Hồ sơ NS) để user thường tra cứu hồ sơ nhân sự.
// CHẠY SAU RevokeTemporarilyHiddenModulesAsync (SeedAsync) để THẮNG revoke: revoke