casbin icon indicating copy to clipboard operation
casbin copied to clipboard

How can I design a matcher that looks for membership in two (g & g2) role_definitions?

Open benschw opened this issue 1 year ago • 1 comments
trafficstars

Want to prioritize this issue? Try:

issuehunt-to-marktext


What's your scenario? What do you want to achieve?

I would like to model a set of roles and entitlements to match when a user is granted both the role & entitlement offering a specific permission.

In other words:

  • to model a single set of roles that cover everything the app offers
  • to model a set of entitlements to group the permissions needed for various features
  • and only grant a user access if they have the appropriate role and have the respective entitlement (i.e. have purchased the relevant feature)

e.g. the "admin" role offers access to everything in the app, but an actual admin user only has access to premium_feature if they have the premium_entitlement as well

I'm pretty sure I am just using the matchers wrong... I can us g to determine match for role access & g2 to match for entitlement access successfully, however I am having trouble putting those two matchers together to match only when both evaluate to true

Your model:

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[role_definition]
g = _, _
g2 = _, _

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = (g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act) && (g2(r.sub, p.sub) && r.obj == p.obj && r.act == p.act)
# match for roles
#m = (g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act)
# match for entitlements
#m = (g2(r.sub, p.sub) && r.obj == p.obj && r.act == p.act)

Your policy:

p, basic_entitlement, basic_feature, read
p, basic_entitlement, basic_feature, write
p, premium_entitlement, premium_feature, read
p, premium_entitlement, premium_feature, write

p, admin_role, basic_feature, read
p, admin_role, basic_feature, write
p, admin_role, premium_feature, read
p, admin_role, premium_feature, write
p, auditor_role, basic_feature, read
p, auditor_role, premium_feature, read

# alice is an admin that has purchased the premium feature
g, alice, admin_role
g2, alice, basic_entitlement
g2, alice, premium_entitlement

# bob is an admin on a free account
g, bob, admin_role
g2, bob, basic_entitlement

# charlie is an auditor that has purchased the premium feature
g, charlie, auditor_role
g2, charlie, basic_entitlement
g2, charlie, premium_entitlement

# derek is an auditor on a free account
g, derek, auditor_role
g2, derek, basic_entitlement

Your request(s):

alice, basic_feature, read ---> false (expected: true)
alice, basic_feature, write ---> false (expected: true)
alice, premium_feature, read ---> false (expected: true)
alice, premium_feature, write ---> false (expected: true)

bob, basic_feature, read ---> false (expected: true)
bob, basic_feature, write ---> false (expected: true)
bob, premium_feature, read ---> false (expected: false)
bob, premium_feature, write ---> false (expected: false)

charlie, basic_feature, read ---> false (expected: true)
charlie, basic_feature, write ---> false (expected: false)
charlie, premium_feature, read ---> false (expected: true)
charlie, premium_feature, write ---> false (expected: false)

derek, basic_feature, read ---> false (expected: true)
derek, basic_feature, write ---> false (expected: false)
derek, premium_feature, read ---> false (expected: false)
derek, premium_feature, write ---> false (expected: false)

benschw avatar Apr 16 '24 21:04 benschw

@tangyang9464 @JalinWang

casbin-bot avatar Apr 16 '24 21:04 casbin-bot

Sadly that's not so simple as you expect. The problem is that enforcer would try to find only one role from g or g2 which meet reqirement, not one from g and one from g2.

Your model is more likely a RBAC with Domains model, since there is no relationship between roles and entitlements. Currently casbin is not good at implementing multi-positive check. And I didn't find a way to perfectly implement your model.

Anyway, here is a working solution.

model:

[request_definition]
r = sub, dom, obj, act

[policy_definition]
p = sub, dom, obj, act

[role_definition]
g = _, _, _

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = g(r.sub, p.sub, r.dom) && r.obj == p.obj && r.act == p.act

policy:

p, basic, entitlement, basic_feature, read
p, basic, entitlement, basic_feature, write
p, premium, entitlement, premium_feature, read
p, premium, entitlement, premium_feature, write

p, admin, role, basic_feature, read
p, admin, role, basic_feature, write
p, admin, role, premium_feature, read
p, admin, role, premium_feature, write
p, auditor, role, basic_feature, read
p, auditor, role, premium_feature, read

# alice is an admin that has purchased the premium feature
g, alice, admin, role
g, alice, basic, entitlement
g, alice, premium, entitlement

# bob is an admin on a free account
g, bob, admin, role
g, bob, basic, entitlement

# charlie is an auditor that has purchased the premium feature
g, charlie, auditor, role
g, charlie, basic, entitlement
g, charlie, premium, entitlement

# derek is an auditor on a free account
g, derek, auditor, role
g, derek, basic, entitlement

code:

func Test_1384(t *testing.T) {
	e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")

	testMultiDomainEnforce := func(t *testing.T, e *Enforcer, sub, obj, act string, res bool) {
		t.Helper()
		res1, err := e.Enforce(sub, "role", obj, act)
		if err != nil {
			t.Errorf("Enforce Error: %s", err)
			return
		}
		res2, err := e.Enforce(sub, "entitlement", obj, act)
		if err != nil {
			t.Errorf("Enforce Error: %s", err)
			return
		}
		if res != res1 && res2 {
			t.Errorf("%s, %s, %s: %t %t, supposed to be %t", sub, obj, act, res1, res2, res)
		}
		// Pass
	}

	testMultiDomainEnforce(t, e, "alice", "basic_feature", "read", true)
	testMultiDomainEnforce(t, e, "alice", "basic_feature", "write", true)
	testMultiDomainEnforce(t, e, "alice", "premium_feature", "read", true)
	testMultiDomainEnforce(t, e, "alice", "premium_feature", "write", true)

	testMultiDomainEnforce(t, e, "bob", "basic_feature", "read", true)
	testMultiDomainEnforce(t, e, "bob", "basic_feature", "write", true)
	testMultiDomainEnforce(t, e, "bob", "premium_feature", "read", false)
	testMultiDomainEnforce(t, e, "bob", "premium_feature", "write", false)

	testMultiDomainEnforce(t, e, "charlie", "basic_feature", "read", true)
	testMultiDomainEnforce(t, e, "charlie", "basic_feature", "write", false)
	testMultiDomainEnforce(t, e, "charlie", "premium_feature", "read", true)
	testMultiDomainEnforce(t, e, "charlie", "premium_feature", "write", false)

	testMultiDomainEnforce(t, e, "derek", "basic_feature", "read", true)
	testMultiDomainEnforce(t, e, "derek", "basic_feature", "write", false)
	testMultiDomainEnforce(t, e, "derek", "premium_feature", "read", false)
	testMultiDomainEnforce(t, e, "derek", "premium_feature", "write", false)
}

MuZhou233 avatar May 15 '24 00:05 MuZhou233

appreciated!

benschw avatar May 20 '24 14:05 benschw