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
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/rbac_with_domain_pattern_policy.csv
Expand Up @@ -2,6 +2,7 @@ p, admin, domain1, data1, read
p, admin, domain1, data1, write
p, admin, domain2, data2, read
p, admin, domain2, data2, write
p, admin, *, data3, read

g, alice, admin, *
g, bob, admin, domain2
30 changes: 20 additions & 10 deletions rbac/default-role-manager/role_manager.go
Expand Up @@ -201,12 +201,17 @@ func (rm *RoleManagerImpl) rebuild() {
})
}

func (rm *RoleManagerImpl) match(str string, pattern string) bool {
func (rm *RoleManagerImpl) Match(str string, pattern string) bool {
cacheKey := strings.Join([]string{str, pattern}, "$$")
if v, has := rm.matchingFuncCache.Get(cacheKey); has {
return v.(bool)
} else {
matched := rm.matchingFunc(str, pattern)
var matched bool
if rm.matchingFunc != nil {
matched = rm.matchingFunc(str, pattern)
} else {
matched = str == pattern
}
rm.matchingFuncCache.Put(cacheKey, matched)
return matched
}
Expand All @@ -215,9 +220,9 @@ func (rm *RoleManagerImpl) match(str string, pattern string) bool {
func (rm *RoleManagerImpl) rangeMatchingRoles(name string, isPattern bool, fn func(role *Role) bool) {
rm.allRoles.Range(func(key, value interface{}) bool {
name2 := key.(string)
if isPattern && name != name2 && rm.match(name2, name) {
if isPattern && name != name2 && rm.Match(name2, name) {
fn(value.(*Role))
} else if !isPattern && name != name2 && rm.match(name, name2) {
} else if !isPattern && name != name2 && rm.Match(name, name2) {
fn(value.(*Role))
}
return true
Expand Down Expand Up @@ -313,7 +318,7 @@ func (rm *RoleManagerImpl) DeleteLink(name1 string, name2 string, domains ...str

// HasLink determines whether role: name1 inherits role: name2.
func (rm *RoleManagerImpl) HasLink(name1 string, name2 string, domains ...string) (bool, error) {
if name1 == name2 || (rm.matchingFunc != nil && rm.match(name1, name2)) {
if name1 == name2 || (rm.matchingFunc != nil && rm.Match(name1, name2)) {
return true, nil
}

Expand All @@ -337,7 +342,7 @@ func (rm *RoleManagerImpl) hasLinkHelper(targetName string, roles map[string]*Ro

nextRoles := map[string]*Role{}
for _, role := range roles {
if targetName == role.name || (rm.matchingFunc != nil && rm.match(role.name, targetName)) {
if targetName == role.name || (rm.matchingFunc != nil && rm.Match(role.name, targetName)) {
return true
}
role.rangeRoles(func(key, value interface{}) bool {
Expand Down Expand Up @@ -507,12 +512,17 @@ func (dm *DomainManager) getDomain(domains ...string) (domain string, err error)
}
}

func (dm *DomainManager) match(str string, pattern string) bool {
func (dm *DomainManager) Match(str string, pattern string) bool {
cacheKey := strings.Join([]string{str, pattern}, "$$")
if v, has := dm.matchingFuncCache.Get(cacheKey); has {
return v.(bool)
} else {
matched := dm.domainMatchingFunc(str, pattern)
var matched bool
if dm.domainMatchingFunc != nil {
matched = dm.domainMatchingFunc(str, pattern)
} else {
matched = str == pattern
}
dm.matchingFuncCache.Put(cacheKey, matched)
return matched
}
Expand All @@ -522,7 +532,7 @@ func (dm *DomainManager) rangeAffectedRoleManagers(domain string, fn func(rm *Ro
if dm.domainMatchingFunc != nil {
dm.rmMap.Range(func(key, value interface{}) bool {
domain2 := key.(string)
if domain != domain2 && dm.match(domain2, domain) {
if domain != domain2 && dm.Match(domain2, domain) {
fn(value.(*RoleManagerImpl))
}
return true
Expand Down Expand Up @@ -551,7 +561,7 @@ func (dm *DomainManager) getRoleManager(domain string, store bool) *RoleManagerI
dm.rmMap.Range(func(key, value interface{}) bool {
domain2 := key.(string)
rm2 := value.(*RoleManagerImpl)
if domain != domain2 && dm.match(domain, domain2) {
if domain != domain2 && dm.Match(domain, domain2) {
rm.copyFrom(rm2)
}
return true
Expand Down
48 changes: 33 additions & 15 deletions rbac_api.go
Expand Up @@ -17,6 +17,7 @@ package casbin
import (
"github.com/casbin/casbin/v2/constant"
"github.com/casbin/casbin/v2/errors"
"github.com/casbin/casbin/v2/rbac/default-role-manager"
"github.com/casbin/casbin/v2/util"
)

Expand Down Expand Up @@ -294,22 +295,32 @@ func (e *Enforcer) GetImplicitPermissionsForUser(user string, domain ...string)
// GetImplicitPermissionsForUser("alice") can only get: [["admin", "data1", "read"]], whose policy is default policy "p"
// But you can specify the named policy "p2" to get: [["admin", "create"]] by GetNamedImplicitPermissionsForUser("p2","alice")
func (e *Enforcer) GetNamedImplicitPermissionsForUser(ptype string, user string, domain ...string) ([][]string, error) {
roles, err := e.GetImplicitRolesForUser(user, domain...)
if err != nil {
return nil, err
}

roles = append([]string{user}, roles...)

var res [][]string
var permissions [][]string
for _, role := range roles {
permissions = e.GetNamedPermissionsForUser(ptype, role, domain...)

res = append(res, permissions...)
permission := make([][]string, 0)
rm := e.GetRoleManager()
domainIndex, _ := e.GetFieldIndex(ptype, constant.DomainIndex)
for _, rule := range e.model["p"][ptype].Policy {
if len(domain) == 0 {
matched, _ := rm.HasLink(user, rule[0])
if matched {
permission = append(permission, deepCopyPolicy(rule))
}
} 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.

if !matched {
continue
}
matched, _ = rm.HasLink(user, rule[0], d)
if matched {
newRule := deepCopyPolicy(rule)
newRule[domainIndex] = d
permission = append(permission, newRule)
break
}
}
}
}

return res, nil
return permission, nil
}

// GetImplicitUsersForPermission gets implicit users for a permission.
Expand Down Expand Up @@ -397,3 +408,10 @@ func (e *Enforcer) GetImplicitResourcesForUser(user string, domain ...string) ([
}
return res, nil
}

// deepCopyPolicy returns a deepcopy version of the policy to prevent changing policies through returned slice
func deepCopyPolicy(src []string) []string {
newRule := make([]string, len(src))
copy(newRule, src)
return newRule
}
11 changes: 9 additions & 2 deletions rbac_api_test.go
Expand Up @@ -314,9 +314,9 @@ func TestImplicitRoleAPI(t *testing.T) {
testGetRoles(t, e, []string{"/book/1/2/3/4/5", "pen_admin"}, "cathy")
}

func testGetImplicitPermissions(t *testing.T, e *Enforcer, name string, res [][]string) {
func testGetImplicitPermissions(t *testing.T, e *Enforcer, name string, res [][]string, domain ...string) {
t.Helper()
myRes, _ := e.GetImplicitPermissionsForUser(name)
myRes, _ := e.GetImplicitPermissionsForUser(name, domain...)
t.Log("Implicit permissions for ", name, ": ", myRes)

if !util.Set2DEquals(res, myRes) {
Expand Down Expand Up @@ -353,10 +353,17 @@ func TestImplicitPermissionAPI(t *testing.T) {
testGetImplicitPermissions(t, e, "alice", [][]string{{"alice", "data1", "read"}, {"data1_admin", "data1", "read"}, {"data1_admin", "data1", "write"}, {"data2_admin", "data2", "read"}, {"data2_admin", "data2", "write"}})
testGetImplicitPermissions(t, e, "bob", [][]string{{"bob", "data2", "write"}})

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

testGetImplicitPermissions(t, e, "admin", [][]string{{"admin", "domain1", "data1", "read"}, {"admin", "domain1", "data1", "write"}, {"admin", "domain1", "data3", "read"}}, "domain1")
Abingcbc marked this conversation as resolved.
Show resolved Hide resolved
testGetImplicitPermissions(t, e, "admin", [][]string{{"admin", "domain1", "data1", "read"}, {"admin", "domain1", "data1", "write"}, {"admin", "domain2", "data2", "read"}, {"admin", "domain2", "data2", "write"}, {"admin", "domain1", "data3", "read"}}, "domain1", "domain2")

e, _ = NewEnforcer("examples/rbac_with_multiple_policy_model.conf", "examples/rbac_with_multiple_policy_policy.csv")

testGetNamedImplicitPermissions(t, e, "p", "alice", [][]string{{"user", "/data", "GET"}, {"admin", "/data", "POST"}})
testGetNamedImplicitPermissions(t, e, "p2", "alice", [][]string{{"user", "view"}, {"admin", "create"}})

}

func TestImplicitPermissionAPIWithDomain(t *testing.T) {
Expand Down