Skip to content

PolicyDefinition cookbook - lưu grant theo Role / Merchant / direct Permission

Mẫu dữ liệu PolicyDefinition cụ thể cho mọi trường hợp (không chỉ happy case), kèm casbin line ScopedCasbinAdapter sinh ra và kết quả enforce. Mô hình runtime xem Casbin Authorization.

Enforcer đọc gì

Các cột quan trọng: variant, subject_type, subject_id, target_type, target_id, domain, action, effect, deleted_at.

Variantsubject → targetÝ nghĩa domain
grantRole|User → Permissiongrant scope: SYSTEM_WIDE / ANY_MEMBER / <Type>_<id> (null ⇒ ANY_MEMBER)
assign_roleUser → Rolescope role theo domain (null ⇒ *, mọi domain)
join_domainUser → Merchant|Organizermembership; cấp năng lượng cho grant ANY_MEMBER (không có cột domain)
role_inheritsRole → Rolechung g, domain *
domain_inheritsdomain → domainlồng g3 (Merchant ⊂ Organizer)
resource_inheritsresource → resourceg4 (lồng obj)
action_inheritsaction → actiong5 (lattice action)
merchant_roleRole ↔ Merchantkhông được đọc bởi adapter (metadata UI của @nx/core)

domain của grant là một trong ba dạng scope. Adapter bọc domain có kiểu thành <Type>_<id> và giữ nguyên các literal scope (SYSTEM_WIDE / ANY_MEMBER).

Fixtures dùng bên dưới

Id tượng trưng (thay bằng id thật):

TokenNghĩa
Umột user id
MA, MB, MCmerchant id
Omột organizer id
R_OWNER, R_EMP, R_GUESTrole id (500_organizer-owner, 100_employee, 001_guest)
P_FIND, P_DELETEpermission id (Product.find read / Product.deleteById delete)

action của grant phải bằng action của chính permission (Product.findread, Product.deleteByIddelete), trừ khi có cạnh action_inherits (g5) nối chúng. effect mặc định allow.


Case 1 - Cấp permission cho role (quản theo Role)

Mục tiêu: role R_OWNER được Product.find. domain null mặc định thành ANY_MEMBER.

variantsubject_typesubject_idtarget_typetarget_iddomainactioneffect
grantRoleR_OWNERPermissionP_FINDNULLANY_MEMBERreadallow

Emit (chỉ khi có user thực sự giữ R_OWNER):

p, Role_R_OWNER, ANY_MEMBER, Product.find, read, allow

Chỉ khớp ở domain user đã join (g2). Thu hồi = soft-delete row này.


Case 2 - Cho user là thành viên của merchant

Mục tiêu: U thuộc merchant MA. Cần cho grant ANY_MEMBER (Case 1/4) và cho data-scoping.

variantsubject_typesubject_idtarget_typetarget_iddomain
join_domainUserUMerchantMA-

Emit:

g2, User_U, Merchant_MA

merchantIds = [MA] và thoả mệnh đề ANY_MEMBER g2(r.sub, r.dom)MA.


Case 3 - Gán role cho user, scope MỘT merchant (quản theo Merchant)

Mục tiêu: U là owner chỉMA.

variantsubject_typesubject_idtarget_typetarget_iddomain
assign_roleUserURoleR_OWNERMA

Emit (kèm grant Case 1 + membership Case 2):

g2, User_U, Merchant_MA
g, User_U, Role_R_OWNER, Merchant_MA
p, Role_R_OWNER, ANY_MEMBER, Product.find, read, allow
  • enforce (User_U, Merchant_MA, Product.find, read)ALLOW
  • enforce (User_U, Merchant_MB, Product.find, read)DENY (không có g line ở MB)

Case 4 - Gán role cho TẤT CẢ merchant của user

Mục tiêu: U là owner ở mọi merchant mình thuộc về.

Rows: membership (Case 2) cho từng merchant + MỘT role assignment domain *:

variantsubject_typesubject_idtarget_typetarget_iddomain
join_domainUserUMerchantMA-
join_domainUserUMerchantMB-
assign_roleUserURoleR_OWNERNULL*

Emit:

g2, User_U, Merchant_MA
g2, User_U, Merchant_MB
g, User_U, Role_R_OWNER, *
p, Role_R_OWNER, ANY_MEMBER, Product.find, read, allow

ALLOW ở MA & MB; DENY ở MC. (Domain role * khớp mọi nơi; grant ANY_MEMBER sau đó thu hẹp về đúng các membership thực của user.)


Case 5 - Grant scope theo Organizer cascade xuống merchant (lồng domain)

Mục tiêu: U là owner trên các merchant của organizer O mà không cần liệt kê, qua một grant <Type>_<id> + domain_inherits.

variantsubject_typesubject_idtarget_typetarget_iddomain
domain_inheritsMerchantMAOrganizerO-
domain_inheritsMerchantMBOrganizerO-
assign_roleUserURoleR_OWNERNULL*
grantRoleR_OWNERPermissionP_FINDO

Emit:

g3, Merchant_MA, Organizer_O
g3, Merchant_MB, Organizer_O
g, User_U, Role_R_OWNER, *
p, Role_R_OWNER, Organizer_O, Product.find, read, allow

ALLOW ở MA & MB (cả hai lồng dưới O, nên g3(r.dom, Organizer_O) đúng); DENY ở MC (không lồng dưới O). Không cần membership join_domain vì grant scope là <Type>_<id> cụ thể, không phải ANY_MEMBER.


Case 6 - Role global (guest), pre-merchant

Mục tiêu: U onboard được khi chưa có merchant. Grant guest dùng SYSTEM_WIDE nên khớp mọi domain bất kể membership (domain của assignment có thể NULL).

variantsubject_typesubject_idtarget_typetarget_iddomainaction
assign_roleUserURoleR_GUESTNULL*-
grantRoleR_GUESTPermission(Organizer.onBoarding)SYSTEM_WIDEcreate

Emit:

g, User_U, Role_R_GUEST, *
p, Role_R_GUEST, SYSTEM_WIDE, Organizer.onBoarding, create, allow

ALLOW Organizer.onBoarding ở MỌI domain - kể cả placeholder pre-merchant Merchant_00000000-0000-0000-0000-000000000000.


Case 7 - Direct permission cho user (bỏ qua role)

Một grant có subject là User (không phải Role). Self-link reflexive g(User_U, User_U, dom) của matcher giúp direct grant khớp.

7a. Scope một merchant (scope <Type>_<id>):

variantsubject_typesubject_idtarget_typetarget_iddomainactioneffect
grantUserUPermissionP_FINDMAreadallow
p, User_U, Merchant_MA, Product.find, read, allow - ALLOW chỉ ở MA (qua self-link g3
g3(Merchant_MA, Merchant_MA)).

7b. Mọi merchant của user: domain = NULLANY_MEMBER + membership (Case 2) → p, User_U, ANY_MEMBER, Product.find, read, allow - ALLOW ở mọi merchant đã join.

7c. Direct system-wide: domain = SYSTEM_WIDEp, User_U, SYSTEM_WIDE, Product.find, read, allow

  • ALLOW mọi domain (dùng dè dặt).

Case 8 - DENY tường minh (override allow)

Mục tiêu:R_OWNER cho phép Product.deleteById, chặn nó với user U.

variantsubject_typesubject_idtarget_typetarget_iddomainactioneffect
grantUserUPermissionP_DELETEMAdeletedeny
p, User_U, Merchant_MA, Product.deleteById, delete, deny. Effector default-DENY
some(allow) && !some(deny) → bất kỳ deny khớp đều thắng → DENY ở MA.

Pitfalls & các case không-happy

Tình huốngRow(s)Kết quả
Grant ANY_MEMBER, KHÔNG membershipgrant Case 1 nhưng thiếu join_domain Case 2grant ra không g2 line → vô hiệu. Lỗi hay gặp.
SYSTEM_WIDE trên role tenant (owner/employee)grant … domain=SYSTEM_WIDEkhớp mọi domain toàn hệ thống → vỡ isolation. Dùng ANY_MEMBER cho role tenant.
merchant_role dùng để scope enforcementmerchant_role Role↔Merchantkhông được đọc bởi adapter → không tác dụng (chỉ metadata UI).
Lệch actiongrant action=read cho permission *.deleteById (delete), không có cạnh g5matcher cần g5(r.act, p.act) → không bao giờ khớp. Đặt action = action của permission (hoặc thêm cạnh action_inherits).
Grant bị soft-deleteset deleted_atbị loại (filter deleted_at).
Role/Permission bị soft-deleterow trỏ tới role/perm đã xoábị loại → grant biến mất.
Row trùnghai cạnh giống hệtdedup in-memory → vô hại.
super-admin / admin / operator(không cần)always-allow bypass - mọi PolicyDefinition của họ bị bỏ qua khi enforce.
customer(không)không có grant backend.

INSERT nhanh (psql) ví dụ

sql
-- Case 1: cấp Product.find cho role owner (ANY_MEMBER ⇒ domain NULL)
INSERT INTO identity."PolicyDefinition"
  (id, variant, subject_type, subject_id, target_type, target_id, action, effect, domain)
VALUES (gen_random_uuid()::text, 'grant', 'Role', '<R_OWNER>', 'Permission', '<P_FIND>', 'read', 'allow', NULL);

-- Case 3: gán owner cho user U scope merchant MA (+ membership)
INSERT INTO identity."PolicyDefinition"
  (id, variant, subject_type, subject_id, target_type, target_id, domain)
VALUES
  (gen_random_uuid()::text, 'join_domain', 'User', '<U>', 'Merchant', '<MA>', NULL),
  (gen_random_uuid()::text, 'assign_role', 'User', '<U>', 'Role', '<R_OWNER>', '<MA>');

Trong luồng app nên dùng service/API policy-definition thay vì SQL thô - các INSERT này để hiểu/debug.

Xem thêm

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