Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
benschw opened this issue Apr 16, 2024 · 3 comments

Comments

@benschw
Copy link

benschw commented Apr 16, 2024

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)

@casbin-bot
Copy link
Member

@tangyang9464 @JalinWang

@MuZhou233
Copy link
Contributor

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)
}

@benschw
Copy link
Author

benschw commented May 20, 2024

appreciated!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

4 participants