Skip to content

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ệmLà gìVí dụLưu trữ dưới dạng
Permissionmột tài nguyên bạn bảo vệ (chính là obj)Sale · SaleOrder · SaleOrder.refundmột dòng trong danh mục permission
Actionmột động từ trong bậc thang (chính là act)managewritereadcác cạnh bậc thang g5 - định nghĩa một lần, toàn cục
Grantgắn role → tài nguyên + hành động + domainSale : manage @ Organizermộ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ụcCâu hỏiKhớp khi…
WHOngười dùng có giữ role không?role được gán (trực tiếp hoặc kế thừa)
WHEREmerchant đ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
WHATtà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
HOWhà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ọ.

ModuleOwnerCashierEmployeeGuest
Sale (đơn hàng, POS, bếp, khách hàng)fullfulledit-
Commerce (sản phẩm, danh mục, thiết lập merchant)fullviewview-
Inventory (tồn kho, nguyên liệu, mua hàng)fullviewview-
Payment (nhận/hoàn thanh toán)fulledit--
Finance (tài khoản, giao dịch)fullview--
Invoice (phát hành hóa đơn điện tử)fullrun-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)fullviewviewview
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 / PolicyDefinition là system-only - được thực thi bằng một deny trên *:manage củ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ầngBao phủNghĩa làNgười được cấp điển hình
managemọi thứ bên dưới"toàn quyền kiểm soát khu vực này"Owner / Manager
writecreate · 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 operationBase action
find · findById · findOne · countread
create · createAggregatecreate
updateById · updateByupdate
deleteById · deleteBydelete
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 ⊂ Modulecon ⊂ 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 g4 hiện hành - seed theo từng package (97 cạnh, mỗi subject một cạnh; đã đối chiếu DB).

ModuleSubjects
CommerceMerchant · Organizer · Product · ProductOption · ProductOptionValue · ProductVariant · ProductVariantOption · Category · SaleChannel · Device · Configuration · Setting · ReceiptTemplate · DiscriminationType · AllocationLayout · AllocationUnit · AllocationZone
SaleSaleOrder · SaleOrderItem · SaleCheck · PosSession · KitchenStation · KitchenTicket · KitchenTicketItem · Reservation · PointTransaction · AllocationUsage · Customer ¹ · SalesReport · PurchaseReport
InventoryInventoryItem · InventoryStock · InventoryLocation · InventoryIdentifier · InventoryTicket · InventoryTicketItem · InventoryTracking · Material · MaterialIdentifier · MaterialRecipe · ProductionOrder · PurchaseOrder · UnitOfMeasure · Vendor · VendorItem
FinanceFinanceAccount · FinanceCategory · FinanceTransaction · FinanceVoucher · PaymentIntegration
PaymentPayment · PaymentAttempt · PaymentResult · Transaction · TransactionItem · WebhookConfig
PricingFare · FareSet · Cost · Rule · Promotion · PromotionMethod · Simulation · Tax · TaxSet · TaxType
InvoiceInvoice · InvoiceConfigMapping · InvoiceOnboarding · InvoiceProvider · InvoiceProviderConfig · InvoiceRequest · InvoiceVnAddress · MerchantInvoiceProfile · TaxInfo
TaxationTaxGroup · TaxGroupItem · VnAdministrativeUnit · VnProvince · VnWard
LedgerLedger · MerchantLedgerConfig
LicensingLicense · Activation · Policy · PolicyFeature
IdentityUser · Role · Permission · PolicyDefinition · Employee · Customer ¹ · UserConfiguration · UserIdentifier
OutreachInquiry · Subscriber
SignalWebSocketClient

¹ 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ên Sale:manage củ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 trongCần membership?Dùng bởi
SYSTEM_WIDEkhắp nơikhôngSuper Admin / Admin / Operator
Organizer_<id>mọi merchant dưới organizer đókhông (cascade theo cây)Owner / quản lý HQ
ANY_MEMBERmọi merchant người dùng đã tham giacó (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.

RoleGrants (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
Customerchỉ đọc đơn hàng của chính mình
GuestLicensing : 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. Permissioncatalog 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 subjectmodule để grant thô có đích nhắm tới:

codesubjectmethodactionloại
SaleSale--node module
SaleOrderSaleOrder--node subject
SaleOrder.findSaleOrderfindreadoperation
SaleOrder.countSaleOrdercountreadoperation
SaleOrder.createSaleOrdercreatecreateoperation
SaleOrder.updateByIdSaleOrderupdateByIdupdateoperation
SaleOrder.deleteByIdSaleOrderdeleteByIddeleteoperation
SaleOrder.refundSaleOrderrefundexecuteoperation

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. parentId chỉ 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ạnh resource_inherits (g4) trong PolicyDefinition, và g4many-to-many - một subject có thể roll-up vào nhiều module. Vd Customer nằm dưới cả Sale lẫn Identity bằ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 roll Customer dưới Sale cho 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ỡ):

variantsubject (type:id)target (type:id)actioneffect
grantRole : cashierPermission : SaleOrder.findreadallow
grantRole : cashierPermission : SaleOrder.countreadallow
grantRole : cashierPermission : SaleOrder.createcreateallow
… (× mỗi op × mỗi subject)

Hiện hành (thô) - Cashier chỉ nhận một grant thô cho mỗi subject:

variantsubject (type:id)target (type:id)actioneffectdomain
grantRole : cashierPermission : SaleOrdermanageallownullANY_MEMBER

…cộng các cạnh dùng chung, khai báo một lần (không nhân theo role):

variantsubject → targetý nghĩa
action_inherits (g5)manageread/write · writecreate/update/deletebậc thang action
resource_inherits (g4)Sale → SaleOrder · Sale → Customermodule ⊃ 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 (CustomerSale)
domain_inherits (g3)Merchant_7Organizer_9HQ thấy mọi chi nhánh
assign_roleUser_1 → Role cashierai giữ role
join_domainUser_1Merchant_7thành viên merchant

Walkthrough - enforce(User_1, Merchant_7, "SaleOrder.find", read)

  1. assign_role: User_1 giữ cashier
  2. cashier có grant SaleOrder : manage
  3. objectMatch("SaleOrder.find", "SaleOrder") ✓ - operation ⊂ subject là tự động (dotted prefix)
  4. g5: manage ⊃ read
  5. join_domain: User_1 ∈ Merchant_7 ✓
  6. 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ạnh resource_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ạnh g4.

7. Ví dụ thực tế

#RequestGrant đang giữQuyết địnhTại sao
1Cashier đọc SaleOrder.refund trong Merchant_7Sale : manage @ ANY_MEMBERmanage ⊃ read; SaleOrder.refund ⊂ Sale; là member của Merchant_7
2Owner cập nhật Product trong Merchant_8* : manage @ Organizer_9manage ⊃ update; Product ⊂ *; Merchant_8 ⊂ Organizer_9
3Employee xóa một SaleOrderSale : write @ ANY_MEMBERwrite ⊃ delete; SaleOrder ⊂ Sale
4Employee xóa một ProductCommerce : read @ ANY_MEMBERread không bao phủ delete
5Cashier của org A thao tác trong một merchant của org BSale : manage @ ANY_MEMBERkhô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
QueryKiểuMặc địnhTác dụng
qstring-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
modulesCSVtất cảgiới hạn theo module code, vd Sale,Commerce
withPermissions'true'|'false'falsetrả 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ềnbạ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 subjectPermission / 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:

jsonc
{ "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 - execute chỉ 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à q lọ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ácSubjectTier (action)Làm gì
findMerchantTargets · countMerchantTargetsMerchantreadliệt kê ai (user/role) được gán vào merchant
manageMerchantTargetsMerchantexecutegán / gỡ membership merchant
findOrganizerTargets · countOrganizerTargetsOrganizerreadliệt kê assignee của organizer
manageOrganizerTargetsOrganizerexecutegán / gỡ membership organizer
  • Owner tự có - Commerce : manage (§5) phủ Merchant/Organizer (roll-up lên Commerce qua g4).
  • Ủy quyền: cấp role tùy biến tier Merchant/Organizer ở mức read (xem membership) hoặc execute/manage (sửa) - đó là lý do 2 subject này giờ hiện tier execute trong picker.
  • Raw PolicyDefinition vẫ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ướcDiễn ra
nhómop đã chọn → theo subject
tier mỗi subject1 leaf → tier đó (read/write/execute); ≥2 leaf → manage
rollup modulemọi code grantable dưới 1 module đều chọn cùng tier → 1 grant module : tier
lưuset-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 coarse

vd 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

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.