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

feat: support domain matching when getting permissions #992

Merged
merged 1 commit into from Jul 3, 2022
Merged

feat: support domain matching when getting permissions #992

merged 1 commit into from Jul 3, 2022

Conversation

Abingcbc
Copy link
Member

fix: #969

How it works

GetImplicitPermissionsForUser depends on GetFilteredPolicies to query permissions. But when enforcer gets filtered policies, it doesn't support domain matching.
So there are main changes in this PR:

  1. Make match of role manager public and reuse this function for domain matching when getting policies.
  2. Add a new function GetFilteredPolicyWithDomainMatching for the user requirements mentioned in
    [Question] Get implicit permissions with pattern-matching in RBAC don't work #969.
  3. Unit tests.

@casbin-bot
Copy link
Member

@tangyang9464 @closetool @sagilio please review

@hsluoyz
Copy link
Member

hsluoyz commented Apr 21, 2022

@sasakiyori @tangyang9464 plz review

@tangyang9464
Copy link
Member

@Abingcbc I have an idea, why not just use rm.HasLink to get permissions directly, because GetFilteredPolicies only works without pattern. This PR solves Domain Match, how to solve RBAC pattern? If use rm.HasLink, then GetImplicitPermissionsForUser and GetPermissionsForUser can be combined into the same method. Code draft

func (e *Enforcer) GetPermissionsForUser(user string, domain ...string) [][]string {
	permission := make([][]string, 0)
	rm := e.GetRoleManager()
	//
		add domain match here
	//
	for _, rule := range e.model["p"]["p"].Policy {
		matched, _ := rm.HasLink(user, rule[0], domain...)
		if matched {
			permission = append(permission, rule)
		}
	}
	return permission
}

@Abingcbc
Copy link
Member Author

@tangyang9464 Thx for your idea! I will refactor this PR.

BTW what do you mean by

GetImplicitPermissionsForUser and GetPermissionsForUser can be combined into the same method.

@tangyang9464
Copy link
Member

@tangyang9464 Thx for your idea! I will refactor this PR.

BTW what do you mean by

GetImplicitPermissionsForUser and GetPermissionsForUser can be combined into the same method.

@Abingcbc I wanted to combine the two methods before, but now I think it's inappropriate, please ignore.

@Abingcbc
Copy link
Member Author

@tangyang9464 PTAL

@hsluoyz
Copy link
Member

hsluoyz commented Jun 26, 2022

@sasakiyori @tangyang9464 plz review

rbac_api.go Outdated Show resolved Hide resolved
rbac_api_test.go Show resolved Hide resolved
@Abingcbc
Copy link
Member Author

@tangyang9464 plz review

rbac_api.go Outdated
if matched {
// Copy rule to prevent changing policies through returned slice
newRule := make([]string, len(rule))
copy(newRule, rule)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the rule change affect the original policy?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, the rule is a slice. If users update the value in returned slice, the policy will also be changed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should also check if this bug happens anywhere else?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, You're right. Could you do this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will fix this issue in a new PR

rbac_api.go Outdated Show resolved Hide resolved
@Abingcbc
Copy link
Member Author

@tangyang9464 plz review

@Abingcbc
Copy link
Member Author

Abingcbc commented Jul 3, 2022

@tangyang9464 @hsluoyz plz review

Copy link
Member

@tangyang9464 tangyang9464 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@hsluoyz hsluoyz merged commit 49154f4 into casbin:master Jul 3, 2022
@github-actions
Copy link

github-actions bot commented Jul 3, 2022

🎉 This PR is included in version 2.50.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@JalinWang
Copy link
Member

JalinWang commented Jul 19, 2022

@Abingcbc @tangyang9464 Hi, I don't think this PR has solved this problem totally.

policy

g, alice, admin, *
g, bob, admin, domain2

test code

	e, _ = NewEnforcer("examples/rbac_with_domain_pattern_model.conf", "examples/rbac_with_domain_pattern_policy.csv")
	e.AddNamedDomainMatchingFunc("g", "KeyMatch", util.KeyMatch)

	res, _ := e.GetRolesForUser("bob", "*")
	t.Log(res)

	res, _ = e.GetRolesForUser("bob", "domain1")
	t.Log(res)

	res, _ = e.GetRolesForUser("bob", "domain2")
	t.Log(res)

result:
image

expected result:
res, _ := e.GetRolesForUser("bob", "*") returns [admin] (for * matches domain2 and bob is admin in domain2)

/cc @hsluoyz

}
} else {
for _, d := range domain {
matched := rm.(*defaultrolemanager.RoleManager).Match(d, rule[domainIndex])
Copy link
Member

@JalinWang JalinWang Jul 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's more, we can't assume the RoleManager is the default one. And this dependency should not be introduced into Enforcer.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, we may refactor from the first imported dependency

casbin/enforcer.go

Lines 730 to 745 in 9b43426

func (e *Enforcer) AddNamedMatchingFunc(ptype, name string, fn defaultrolemanager.MatchingFunc) bool {
if rm, ok := e.rmMap[ptype]; ok {
rm.(*defaultrolemanager.RoleManager).AddMatchingFunc(name, fn)
return true
}
return false
}
// AddNamedDomainMatchingFunc add MatchingFunc by ptype to RoleManager
func (e *Enforcer) AddNamedDomainMatchingFunc(ptype, name string, fn defaultrolemanager.MatchingFunc) bool {
if rm, ok := e.rmMap[ptype]; ok {
rm.(*defaultrolemanager.RoleManager).AddDomainMatchingFunc(name, fn)
return true
}
return false
}

An interface may be better than struct.

@JalinWang
Copy link
Member

JalinWang commented Jul 19, 2022

I think we should modify the code of DomainManager.
One elegant way is to enable getRoleManager to return all the role managers that match the domain, e.g., "*" return "domain" and "domain2", while now it only returns one.

// load or create a RoleManager instance of domain
func (dm *DomainManager) getRoleManager(domain string, store bool) *RoleManagerImpl {
	var rm *RoleManagerImpl
	var ok bool

	if rm, ok = dm.load(domain); !ok {
		rm = newRoleManagerWithMatchingFunc(dm.maxHierarchyLevel, dm.matchingFunc)
		if store {
			dm.rmMap.Store(domain, rm)
		}
		if dm.domainMatchingFunc != nil {
			dm.rmMap.Range(func(key, value interface{}) bool {
				domain2 := key.(string)
				rm2 := value.(*RoleManagerImpl)
				if domain != domain2 && dm.Match(domain, domain2) {
					rm.copyFrom(rm2)
				}
				return true // change to append()
			})
		}
	}
	return rm
}

@Abingcbc
Copy link
Member Author

Abingcbc commented Jul 19, 2022

policy

g, alice, admin, *
g, bob, admin, domain2

test code

	e, _ = NewEnforcer("examples/rbac_with_domain_pattern_model.conf", "examples/rbac_with_domain_pattern_policy.csv")
	e.AddNamedDomainMatchingFunc("g", "KeyMatch", util.KeyMatch)

	res, _ := e.GetRolesForUser("bob", "*")
	t.Log(res)

	res, _ = e.GetRolesForUser("bob", "domain1")
	t.Log(res)

	res, _ = e.GetRolesForUser("bob", "domain2")
	t.Log(res)

result: image

expected result: res, _ := e.GetRolesForUser("bob", "*") returns [admin] (for * matches domain2 and bob is admin in domain2)

Sure, this PR only adds domain matching when getting permissions. And GetRolesForUser is not included in GetImplicitPermission.

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

Successfully merging this pull request may close these issues.

[Question] Get implicit permissions with pattern-matching in RBAC don't work
5 participants