Hướng dẫn phân quyền
Mô hình phân quyền đầy đủ - đọc từ trên xuống dưới. §1 là bản đồ trên một màn hình; §2-8 là phần chi tiết chính xác. Để tra cứu một mã quyền cụ thể hoặc tập quyền hiệu lực đầy đủ của một role, dùng Ma trận quyền được sinh tự động.
Ba thứ - đừng bao giờ nhầm lẫn chúng
| Khái niệm | Là gì | Ví dụ | Lưu trữ dưới dạng |
|---|---|---|---|
| Permission | một tài nguyên bạn bảo vệ (chính là obj) | Sale · SaleOrder · SaleOrder.refund | một dòng trong danh mục permission |
| Action | một động từ trong bậc thang (chính là act) | manage ⊃ write ⊃ read | các cạnh bậc thang g5 - định nghĩa một lần, toàn cục |
| Grant | gắn role → tài nguyên + hành động + domain | Sale : manage @ Organizer | một dòng PolicyDefinition |
Bạn định nghĩa các permission (tài nguyên) + bậc thang hành động một lần; bạn gán các grant cho từng role. Sale:manage là một grant, không phải permission - đó là lý do một role chỉ cần một nhúm grant thay vì hàng trăm permission.
1. Mô hình trên một màn hình
Cách một request được quyết định
Mỗi request là (who · where · what · how) và chỉ được cho phép khi cả bốn trục khớp (một deny tường minh luôn thắng):
| Trục | Câu hỏi | Khớp khi… |
|---|---|---|
| WHO | người dùng có giữ role không? | role được gán (trực tiếp hoặc kế thừa) |
| WHERE | merchant đang hoạt động có nằm trong phạm vi không? | grant là system-wide, một merchant đã tham gia, hoặc organizer của merchant |
| WHAT | tài nguyên có được bao phủ không? | mã bằng hoặc nằm dưới tài nguyên của grant |
| HOW | hành động có được bao phủ không? | hành động được cấp bằng hoặc rộng hơn |
Ba trục - một grant bao phủ mọi thứ bên dưới nó
HOW - bậc thang hành động
WHAT - cây tài nguyên
WHERE - cây domain
Cấp grant cao trên một trục = rộng (
Sale : manage @ Organizer= mọi thứ, ở khắp nơi trong brand); thấp = chính xác (SaleOrder.refund : execute @ Merchant_7).
Ai được làm gì - nhìn tổng quan
full = manage · edit = create/update/delete · view = read · run = execute · - = không. Phạm vi: Owner = toàn bộ tổ chức (mọi merchant); Cashier/Employee = (các) merchant được gán cho họ.
| Module | Owner | Cashier | Employee | Guest |
|---|---|---|---|---|
| Sale (đơn hàng, POS, bếp, khách hàng) | full | full | edit | - |
| Commerce (sản phẩm, danh mục, thiết lập merchant) | full | view | view | - |
| Inventory (tồn kho, nguyên liệu, mua hàng) | full | view | view | - |
| Payment (nhận/hoàn thanh toán) | full | edit | - | - |
| Finance (tài khoản, giao dịch) | full | view | - | - |
| Invoice (phát hành hóa đơn điện tử) | full | run | - | view ¹ |
| Pricing (bộ máy fare/quy tắc/thuế) | full | - | - | - |
| Taxation (nhóm thuế) | full | - | - | - |
| Ledger (tờ khai thuế nhà nước) | full | - | - | - |
| Licensing (gói, kích hoạt) | full | - | - | view |
| Identity (người dùng, role, quyền) | full ² | - | - | - |
Taxation Vn* tham chiếu (tỉnh/phường) | full | view | view | view |
| Outreach (lead, bản tin) | full | - | - | - |
| Signal (realtime) | full | - | - | - |
¹ Guest chỉ thấy phần onboarding hóa đơn (công khai). ² Owner quản lý Users / Employees / Roles nhưng không quản lý chính danh mục RBAC (
Permission/PolicyDefinitionlà system-only - được thực thi bằng một deny trên*:managecủa Owner).Super Admin · Admin · Operator vượt qua mọi kiểm tra (full ở khắp nơi). Customer không giữ quyền backend nào (chỉ dữ liệu của chính mình).
2. Hành động chi tiết
Một hành động rộng hơn tự động thỏa mãn bất kỳ request hẹp hơn. Các route luôn yêu cầu một base action; các grant có thể dùng bất kỳ tầng nào.
| Tầng | Bao phủ | Nghĩa là | Người được cấp điển hình |
|---|---|---|---|
manage | mọi thứ bên dưới | "toàn quyền kiểm soát khu vực này" | Owner / Manager |
write | create · update · delete | "chỉnh sửa, không nhất thiết chạy hành động" | Editor |
read | - (lá) | "chỉ xem" | Viewer / Cashier (dữ liệu tham chiếu) |
execute | - (lá) | chạy một op không phải CRUD (refund, issue, close…) | theo từng operation |
create·update·delete | - (lá) | các động từ write riêng lẻ | chi tiết |
Route → base action (cách một route khai báo nó cần gì):
| Loại operation | Base action |
|---|---|
find · findById · findOne · count | read |
create · createAggregate | create |
updateById · updateBy | update |
deleteById · deleteBy | delete |
op tùy chỉnh (refund, issue, close, launchpad…) | execute |
3. Cây tài nguyên chi tiết
* ← chỉ super-admin
└── <Module> vd. Sale, Commerce, Inventory (gộp module: cạnh tường minh)
└── <Subject> vd. SaleOrder (subject)
└── <Subject>.<op> vd. SaleOrder.refund (operation: tự động, dotted)- Operation ⊂ Subject - miễn phí, theo mã dotted. Subject ⊂ Module và con ⊂ cha (
SaleOrderItem ⊂ SaleOrder) - mỗi cái một cạnh khai báo.
Cây tài nguyên phản chiếu các module backend. Operation nằm dưới subject của chúng (không liệt kê ở đây - xem Ma trận). Các gộp module bên dưới là cạnh
g4hiện hành - seed theo từng package (97 cạnh, mỗi subject một cạnh; đã đối chiếu DB).
| Module | Subjects |
|---|---|
| Commerce | Merchant · Organizer · Product · ProductOption · ProductOptionValue · ProductVariant · ProductVariantOption · Category · SaleChannel · Device · Configuration · Setting · ReceiptTemplate · DiscriminationType · AllocationLayout · AllocationUnit · AllocationZone |
| Sale | SaleOrder · SaleOrderItem · SaleCheck · PosSession · KitchenStation · KitchenTicket · KitchenTicketItem · Reservation · PointTransaction · AllocationUsage · Customer ¹ · SalesReport · PurchaseReport |
| Inventory | InventoryItem · InventoryStock · InventoryLocation · InventoryIdentifier · InventoryTicket · InventoryTicketItem · InventoryTracking · Material · MaterialIdentifier · MaterialRecipe · ProductionOrder · PurchaseOrder · UnitOfMeasure · Vendor · VendorItem |
| Finance | FinanceAccount · FinanceCategory · FinanceTransaction · FinanceVoucher · PaymentIntegration |
| Payment | Payment · PaymentAttempt · PaymentResult · Transaction · TransactionItem · WebhookConfig |
| Pricing | Fare · FareSet · Cost · Rule · Promotion · PromotionMethod · Simulation · Tax · TaxSet · TaxType |
| Invoice | Invoice · InvoiceConfigMapping · InvoiceOnboarding · InvoiceProvider · InvoiceProviderConfig · InvoiceRequest · InvoiceVnAddress · MerchantInvoiceProfile · TaxInfo |
| Taxation | TaxGroup · TaxGroupItem · VnAdministrativeUnit · VnProvince · VnWard |
| Ledger | Ledger · MerchantLedgerConfig |
| Licensing | License · Activation · Policy · PolicyFeature |
| Identity | User · Role · Permission · PolicyDefinition · Employee · Customer ¹ · UserConfiguration · UserIdentifier |
| Outreach | Inquiry · Subscriber |
| Signal | WebSocketClient |
¹
Customerđược tham chiếu bởi cả Sale và Identity - nó vẫn là một subject duy nhất, gộp dưới Sale (đã quyết định), nênSale:managecủa một cashier bao phủ các thao tác customer/loyalty.
4. Phạm vi domain chi tiết
Một grant gắn vào domain cha áp dụng cho mọi con (rộng nhất trước):
| Domain grant | Áp dụng trong | Cần membership? | Dùng bởi |
|---|---|---|---|
SYSTEM_WIDE | khắp nơi | không | Super Admin / Admin / Operator |
Organizer_<id> | mọi merchant dưới organizer đó | không (cascade theo cây) | Owner / quản lý HQ |
ANY_MEMBER | mọi merchant người dùng đã tham gia | có (membership) | nhân viên đa merchant |
Merchant_<id> | đúng một merchant đó | (member, thường là vậy) | nhân viên đơn merchant |
- HQ thấy tất cả merchant → các role quản lý được cấp ở phạm vi
Organizer; cây domain cascade chúng tới mọi merchant - thay thế việc backfill membership theo từng merchant hiện nay.
5. Ma trận role → grant
Grant thô - vài dòng mỗi role thay vì hàng trăm.
*= tất cả module. Xem góc nhìn per-operation đã giải quyết trong Ma trận quyền.
| Role | Grants (resource : action @ domain) |
|---|---|
| Super Admin / Admin / Operator | * : manage @ SYSTEM_WIDE (hiện được bỏ qua thực thi) |
Owner (500_organizer-owner) | * : manage @ Organizer · trừ Permission & PolicyDefinition (deny - system-only) |
Cashier (110_cashier) | Sale : manage · Customer : manage · Commerce : read · Inventory : read · Finance : read · Payment : write · Invoice : execute - tất cả @ ANY_MEMBER |
Employee (100_employee) | Sale : write · Customer : read · Commerce : read · Inventory : read - tất cả @ ANY_MEMBER |
| Customer | chỉ đọc đơn hàng của chính mình |
| Guest | Licensing : read · InvoiceOnboarding : read @ SYSTEM_WIDE |
Dữ liệu tham chiếu
Vn*(tỉnh / phường) mọi role đều đọc được (tham chiếu công khai, không có cổng RBAC).
6. Mô hình dữ liệu: Permission vs PolicyDefinition
Tất cả nằm trong hai bảng. Permission là catalog tài nguyên (không bao giờ co lại - mỗi endpoint có một dòng). PolicyDefinition chứa grant, gán role/merchant, và các cạnh phân cấp - đây là nơi phân cấp rút gọn hàng trăm grant phẳng xuống còn vài dòng.
Permission - catalog tài nguyên
Cột: code (unique, <subject>.<method>) · subject · method · action · scope · parentId · name/description (i18n). Mỗi route đăng ký một dòng (qua crudPermissions + custom def). Thiết kế mới giữ nguyên tất cả và thêm node cha subject và module để grant thô có đích nhắm tới:
| code | subject | method | action | loại |
|---|---|---|---|---|
Sale | Sale | - | - | node module |
SaleOrder | SaleOrder | - | - | node subject |
SaleOrder.find | SaleOrder | find | read | operation |
SaleOrder.count | SaleOrder | count | read | operation |
SaleOrder.create | SaleOrder | create | create | operation |
SaleOrder.updateById | SaleOrder | updateById | update | operation |
SaleOrder.deleteById | SaleOrder | deleteById | delete | operation |
SaleOrder.refund | SaleOrder | refund | execute | operation |
SaleOrder.find,SaleOrder.count, … luôn tồn tại - đó là cách guard route và cách một grant chi tiết nhắm vào một op duy nhất. Thiết kế mới không xoá chúng; chỉ ngừng tạo một grant cho mỗi op.
Parent không phải một cột đơn.
parentIdchỉ là gợi ý một parent duy nhất (và hiện lànull). Quan hệ module→subject mà enforcer thực sự dùng là cạnhresource_inherits(g4) trongPolicyDefinition, vàg4là many-to-many - một subject có thể roll-up vào nhiều module. VdCustomernằm dưới cảSalelẫnIdentitybằng cách thêm hai cạnh, nên grant trên bất kỳ module nào cũng phủ nó. (Ta chọn rollCustomerdướiSalecho grant mặc định, nhưng mô hình cho phép nhiều hơn.)
PolicyDefinition - grant, assignment, hierarchy
Cột: variant · subjectType/subjectId · targetType/targetId · action · effect · domain.
Trước đây (phẳng) - Cashier từng nhận một grant cho mỗi op (nguyên nhân nổ dòng, nay đã gỡ):
| variant | subject (type:id) | target (type:id) | action | effect |
|---|---|---|---|---|
grant | Role : cashier | Permission : SaleOrder.find | read | allow |
grant | Role : cashier | Permission : SaleOrder.count | read | allow |
grant | Role : cashier | Permission : SaleOrder.create | create | allow |
| … | … | … (× mỗi op × mỗi subject) | … | … |
Hiện hành (thô) - Cashier chỉ nhận một grant thô cho mỗi subject:
| variant | subject (type:id) | target (type:id) | action | effect | domain |
|---|---|---|---|---|---|
grant | Role : cashier | Permission : SaleOrder | manage | allow | null → ANY_MEMBER |
…cộng các cạnh dùng chung, khai báo một lần (không nhân theo role):
| variant | subject → target | ý nghĩa |
|---|---|---|
action_inherits (g5) | manage→read/write · write→create/update/delete | bậc thang action |
resource_inherits (g4) | Sale → SaleOrder · Sale → Customer | module ⊃ subject - có khả năng many-to-many; dữ liệu hiện hành gộp mỗi subject dưới đúng một module (Customer → Sale) |
domain_inherits (g3) | Merchant_7 → Organizer_9 | HQ thấy mọi chi nhánh |
assign_role | User_1 → Role cashier | ai giữ role |
join_domain | User_1 → Merchant_7 | thành viên merchant |
Walkthrough - enforce(User_1, Merchant_7, "SaleOrder.find", read)
assign_role: User_1 giữ cashier ✓- cashier có grant
SaleOrder : manage✓ objectMatch("SaleOrder.find", "SaleOrder")✓ - operation ⊂ subject là tự động (dotted prefix)g5:manage ⊃ read✓join_domain: User_1 ∈ Merchant_7 ✓- → ALLOW - mà không cần dòng grant
SaleOrder.find.
Operation ⊂ subject là miễn phí (dotted match). Module ⊃ subject (
Sale ⊃ SaleOrder) không tự động - cần cạnhresource_inherits. Nên grant thô rẻ nhất đặt ở mức subject (SaleOrder); grant ở mức module (Sale) chỉ khi bạn thêm cạnhg4.
7. Ví dụ thực tế
| # | Request | Grant đang giữ | Quyết định | Tại sao |
|---|---|---|---|---|
| 1 | Cashier đọc SaleOrder.refund trong Merchant_7 | Sale : manage @ ANY_MEMBER | ✅ | manage ⊃ read; SaleOrder.refund ⊂ Sale; là member của Merchant_7 |
| 2 | Owner cập nhật Product trong Merchant_8 | * : manage @ Organizer_9 | ✅ | manage ⊃ update; Product ⊂ *; Merchant_8 ⊂ Organizer_9 |
| 3 | Employee xóa một SaleOrder | Sale : write @ ANY_MEMBER | ✅ | write ⊃ delete; SaleOrder ⊂ Sale |
| 4 | Employee xóa một Product | Commerce : read @ ANY_MEMBER | ❌ | read không bao phủ delete |
| 5 | Cashier của org A thao tác trong một merchant của org B | Sale : manage @ ANY_MEMBER | ❌ | không phải member của merchant thuộc org B |
8. API tài nguyên có thể cấp (role picker)
Màn hình Tạo / Sửa Role cần catalog dưới dạng cây kèm các tier mỗi node được phép cấp. API đọc này biến hai bảng (§6) thành module → subject → permissions, mỗi node mang theo tier khả dụng (§2) - và đã giới hạn theo đúng những gì bạn được cấp.
GET /v1/api/identity/permissions/grantable-resources
Header x-merchant-id: <merchantId> ← bắt buộc cho caller non-bypass (Owner / Manager); thiếu → 403| Query | Kiểu | Mặc định | Tác dụng |
|---|---|---|---|
q | string | - | substring không phân biệt hoa thường trên code (= subject.method), name, description - refund ra Payment.refund. Giữ node nếu chính nó hoặc một op con khớp; với withPermissions, node khớp theo tên thì hiện đủ op, còn lại chỉ hiện op khớp |
modules | CSV | tất cả | giới hạn theo module code, vd Sale,Commerce |
withPermissions | 'true'|'false' | false | trả thêm các Permission row thao tác bên dưới (panel Advanced) |
Hai hành vi luôn bật, không tắt được:
| Quy tắc | Ý nghĩa |
|---|---|
| Giới hạn theo trần quyền | bạn chỉ thấy tier mình thực sự được cấp - role bypass (§1) thấy tất; còn lại bị cap theo grant của chính mình, nên picker không bao giờ đề xuất tier sẽ 403 lúc lưu |
| Ẩn system subject | Permission / PolicyDefinition không bao giờ xuất hiện (catalog RBAC là system-only - §1 ²) |
Response - { data, count }, lồng nhau ở mọi tầng; name là object i18n đầy đủ; permissions.data chỉ có dữ liệu khi withPermissions=true:
{ "count": 13, "data": [ {
"code": "Payment",
"tiers": ["read","write","execute","manage"], // các nút segmented cần bật (+ None)
"permissions": { "data": [ /* full Permission rows, vd Payment.refund */ ], "count": 5 },
"subjects": { "data": [
{ "code": "Transaction", "tiers": ["read","write","manage"], "permissions": { "data": [], "count": 8 } }
], "count": 5 }
} ] }
tiers= thao tác thật của node (§2) ∩ trần quyền của bạn -executechỉ xuất hiện ở node có op execute (vd Payment); subject report là[read, manage]. Catalog nhỏ & bounded nên trả full list (không phân trang) vàqlọc in-process.
Lưu các tier đã chọn qua grant facade (
…/roles/{id}/targets/permissions) - grant thô, đúng ma trận §5. Xem PRD: Policy-Definition Grants.
Quản lý membership cấp được (Merchant / Organizer targets)
Quản lý ai được gán vào một Merchant / Organizer là facade trên PolicyDefinition, nhưng không nằm ở subject system-only PolicyDefinition - nó ở subject Merchant / Organizer (module Commerce), nên hiện trong catalog này và cấp & ủy quyền được.
| Thao tác | Subject | Tier (action) | Làm gì |
|---|---|---|---|
findMerchantTargets · countMerchantTargets | Merchant | read | liệt kê ai (user/role) được gán vào merchant |
manageMerchantTargets | Merchant | execute | gán / gỡ membership merchant |
findOrganizerTargets · countOrganizerTargets | Organizer | read | liệt kê assignee của organizer |
manageOrganizerTargets | Organizer | execute | gán / gỡ membership organizer |
- Owner tự có -
Commerce : manage(§5) phủMerchant/Organizer(roll-up lên Commerce quag4). - Ủy quyền: cấp role tùy biến tier
Merchant/Organizerở mứcread(xem membership) hoặcexecute/manage(sửa) - đó là lý do 2 subject này giờ hiện tierexecutetrong picker. - Raw
PolicyDefinitionvẫn hoàn toàn system-only; chỉ facade membership này là cấp được.
Lưu role - op id phẳng tự gom thành coarse
Picker vẫn có thể gửi danh sách permissionIds phẳng (1 id mỗi leaf tick). Backend gom thành bộ grant coarse tối thiểu trước khi lưu - nên role không bao giờ tích lũy 1 grant mỗi op (line explosion ở §6 đã bỏ). FE không phải đổi: cứ gửi permissionIds.
| Bước | Diễn ra |
|---|---|
| nhóm | op đã chọn → theo subject |
| tier mỗi subject | 1 leaf → tier đó (read/write/execute); ≥2 leaf → manage |
| rollup module | mọi code grantable dưới 1 module đều chọn cùng tier → 1 grant module : tier |
| lưu | set-replace reconcile với grant hiện có của role - thêm mới, cập nhật tier đổi, revoke cái bỏ, skip cái trùng → không dòng trùng/dư; cap theo ceiling của caller (không grant lố) |
POST /roles · PATCH /roles/{id} body: { …, permissionIds: [ …op ids… ] } → lưu coarsevd 165 op ids (toàn bộ Inventory + vài read Commerce + Payment.refund + hai op SaleOrder) → 5 grant: Inventory:manage (rollup), SaleOrder:manage, Payment:execute, Product:read, Category:read.
Preview - quy op ids ra coarse targets mà chưa lưu (FE hiện "sẽ lưu là…"):
POST /v1/api/identity/permissions/resolve-grants (Permission:read + x-merchant-id)
body { "permissionIds": [ … ] }
→ { "data": [ { "id": "<node id>", "tier": "manage" }, … ], "count": n }Trang liên quan
- Ma trận quyền - mọi quyền (kèm mô tả) + ma trận truy cập hiệu lực theo từng role (sinh tự động)
- URD: Permissions - yêu cầu & tiêu chí chấp nhận của tính năng
HIER - PRD: Resource, Action & Domain Hierarchy
- Developer: RBAC · Casbin Authorization