Repository: casbin/casbin
Pull request #475 (2020-05-30 02:11:46)

feat: Support pattern function in 3rd args of g
This commit not only supports pattern function in 3rd args of g, but also can be used in policy, such as

```
p, data1_admin, *, data1, read
p, data1_admin, *, data1, write

g, alice, data1_admin, domain1
```

dovics: Looks like there are still some problems, I will fix it immediately
hsluoyz: @dovics I think `p, data1_admin, *, data1, read` is not needed to support. We actually have keyMatch() and regexMatch() to support such requirements. Supporting * natively will only make things ambigious.
dovics: > @dovics I think `p, data1_admin, *, data1, read` is not needed to support. We actually have keyMatch() and regexMatch() to support such requirements. Supporting * natively will only make things ambigious.

It may be that I am not expressing clearly enough, what I mean here is that we can use pattern in policy.domain，Otherwise the user needs to write

```
p, data1_admin, domain1, data1, read
p, data1_admin, domain1, data1, write
p, data1_admin, domain2, data1,read
p, data1_admin, domain2, data1, write

p, alice, data_admin1, domian1
```

The `*` can be replaced by any pattern string
coveralls: 
[![Coverage Status](https://coveralls.io/builds/31129470/badge)](https://coveralls.io/builds/31129470)

Coverage increased (+0.01%) to 74.888% when pulling **62c1c17b6b15b6e8625fee22e5547eaa80308d5b on dovics:support-pattern-function-in-3rd-args-of-g** into **3df797354ac4cd0b55fb33089dbb03aa0df4e477 on casbin:master**.

nodece: @dovics Can we only use the `AddMatchingFunc` to support pattern?
hsluoyz: @dovics OK. I understood it now. But how to know which is `dom`? What about `p, *, data1_admin, data1, read`? Or you only recognize it from model: `p = sub, dom, obj, act`'s `dom`?
dovics: > @dovics OK. I understood it now. But how to know which is `dom`? What about `p, *, data1_admin, data1, read`? Or you only recognize it from model: `p = sub, dom, obj, act`'s `dom`?

@hsluoyz  the matcher is 
```
g(r.sub, p.sub, r.dom) && globMatch(r.dom, p.dom) && r.obj == p.obj && r.act == p.act 
```
The data in memory is like this
```
domain1: data1_admin -> domian1: alice
```
The key to being able to write like this is `globMatch(r.dom, p.dom)`.If there is no this pr, we can write this too.  But this is not pointed out in the doc
dovics: > @dovics Can we only use the `AddMatchingFunc` to support pattern?

I merged the two functions in the new commit
GopherJ: can we do every thing in `AddLink` function?
dovics: @GopherJ 
Let me first state my implementation
If call `HasLink(alice, admin, domain1)` We will get the following relationship
```
domain1::admin
       |
    *::admin
       |
    *::alice
       |
domain1:alice
```
but I just need one matchFunc, and It is easy to support case 3
```
domain1::book_admin
       |
  *::book_admin
       |
  *::/book/:id
       |
domain::/book/1
```

I added a new function to support it

In addition, I removed the part about `p, data1_admin, *, data1, read` in the unit test and examples
nodece: > Do you really think we have to deal with it in createRole()?

 @dovics  Because our previous design can't meet the current feature, so you don't have to follow the current design. 




GopherJ: @nodece @dovics 

Since I'm afraid of having bad bench on pattern(because we always need to iterate over all). I'm thinking if we can change all_roles' structure.

Here some of my thoughts
![image](https://user-images.githubusercontent.com/33961674/83618030-30bd3e80-a58a-11ea-8cf4-4b1cfb383977.png)


nodece: I'm worried about that, too.  Your idea looks good.  
dovics: @GopherJ I think It's a good idea, but I still have a little question 

>  if a domain is a pattern domain (eg. *). we clone its graph's reference to matched domain.

if we create the matched domain first, and then create pattern domain,  we couldn't clone the pattern domain's graph to matched domain, so we still need create the pattern domain first, and then merge the two graph to matched domain.

so I think the general step should be
1. Find the domain that not use matching func
2. Add the link,
3. Find the mathced domain or pattern domain, Merge the pattern domain's graph and the matched domain's graph, and store it to the matched domain

If I'm missing something, please point it out
GopherJ: @dovics I have been exploring my solution for a while but I didn't succeed to find a simple way to merge two graphs. simple and efficient.


dovics: @GopherJ I don't see a good enough way either, but I think we could use the worst way to add link one by one to the new graph, which is still more efficient than this implementation. I'm going to implement the current idea what you say now, and then take longer to think of a good enough solution

Patch:
diff --git a/errors/rbac_errors.go b/errors/rbac_errors.go
index 68b1450..28e8eed 100644
--- a/errors/rbac_errors.go
+++ b/errors/rbac_errors.go
@@ -18,7 +18,8 @@ import "errors"
 
 // Global errors for rbac defined here
 var (
-	ERR_NAME_NOT_FOUND    = errors.New("error: name does not exist")
-	ERR_DOMAIN_PARAMETER  = errors.New("error: domain should be 1 parameter")
-	ERR_NAMES12_NOT_FOUND = errors.New("error: name1 or name2 does not exist")
+	ERR_NAME_NOT_FOUND       = errors.New("error: name does not exist")
+	ERR_DOMAIN_PARAMETER     = errors.New("error: domain should be 1 parameter")
+	ERR_NAMES12_NOT_FOUND    = errors.New("error: name1 or name2 does not exist")
+	ERR_USE_DOMAIN_PARAMETER = errors.New("error: useDomain should be 1 parameter")
 )
diff --git a/examples/rbac_with_all_pattern_model.conf b/examples/rbac_with_all_pattern_model.conf
new file mode 100644
index 0000000..045bfa5
--- /dev/null
+++ b/examples/rbac_with_all_pattern_model.conf
@@ -0,0 +1,14 @@
+[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 = r.sub == p.sub && g(r.obj, p.obj, r.dom) && r.dom == p.dom && r.act == p.act 
\ No newline at end of file
diff --git a/examples/rbac_with_all_pattern_policy.csv b/examples/rbac_with_all_pattern_policy.csv
new file mode 100644
index 0000000..8097be8
--- /dev/null
+++ b/examples/rbac_with_all_pattern_policy.csv
@@ -0,0 +1,4 @@
+p, alice, domain1, book_group, read
+p, alice, domain2, book_group, write
+
+g, /book/:id, book_group, *
\ No newline at end of file
diff --git a/examples/rbac_with_domain_pattern_model.conf b/examples/rbac_with_domain_pattern_model.conf
new file mode 100644
index 0000000..774e441
--- /dev/null
+++ b/examples/rbac_with_domain_pattern_model.conf
@@ -0,0 +1,14 @@
+[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.dom == p.dom && r.obj == p.obj && r.act == p.act 
\ No newline at end of file
diff --git a/examples/rbac_with_domain_pattern_policy.csv b/examples/rbac_with_domain_pattern_policy.csv
new file mode 100644
index 0000000..c8abd35
--- /dev/null
+++ b/examples/rbac_with_domain_pattern_policy.csv
@@ -0,0 +1,7 @@
+p, admin, domain1, data1, read
+p, admin, domain1, data1, write
+p, admin, domain2, data2, read
+p, admin, domain2, data2, write
+
+g, alice, admin, *
+g, bob, admin, domain2
\ No newline at end of file
diff --git a/internal_api.go b/internal_api.go
index 455ebdf..12c453b 100644
--- a/internal_api.go
+++ b/internal_api.go
@@ -23,28 +23,33 @@ const (
 	notImplemented = "not implemented"
 )
 
+func (e *Enforcer) shouldPersist() bool {
+	return e.adapter != nil && e.autoSave
+}
+
 // addPolicy adds a rule to the current policy.
 func (e *Enforcer) addPolicy(sec string, ptype string, rule []string) (bool, error) {
-	ruleAdded := e.model.AddPolicy(sec, ptype, rule)
-	if !ruleAdded {
-		return ruleAdded, nil
-	}
-
-	if sec == "g" {
-		err := e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, [][]string{rule})
-		if err != nil {
-			return ruleAdded, err
-		}
+	if e.model.HasPolicy(sec, ptype, rule) {
+		return false, nil
 	}
 
-	if e.adapter != nil && e.autoSave {
+	if e.shouldPersist() {
 		if err := e.adapter.AddPolicy(sec, ptype, rule); err != nil {
 			if err.Error() != notImplemented {
-				return ruleAdded, err
+				return false, err
 			}
 		}
 	}
 
+	e.model.AddPolicy(sec, ptype, rule)
+
+	if sec == "g" {
+		err := e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, [][]string{rule})
+		if err != nil {
+			return true, err
+		}
+	}
+
 	if e.watcher != nil && e.autoNotifyWatcher {
 		var err error
 		if watcher, ok := e.watcher.(persist.WatcherEx); ok {
@@ -52,46 +57,55 @@ func (e *Enforcer) addPolicy(sec string, ptype string, rule []string) (bool, err
 		} else {
 			err = e.watcher.Update()
 		}
-		return ruleAdded, err
+		return true, err
 	}
 
-	return ruleAdded, nil
+	return true, nil
 }
 
 // addPolicies adds rules to the current policy.
 func (e *Enforcer) addPolicies(sec string, ptype string, rules [][]string) (bool, error) {
-	rulesAdded := e.model.AddPolicies(sec, ptype, rules)
-	if !rulesAdded {
-		return rulesAdded, nil
-	}
-
-	if sec == "g" {
-		err := e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, rules)
-		if err != nil {
-			return rulesAdded, err
-		}
+	if e.model.HasPolicies(sec, ptype, rules) {
+		return false, nil
 	}
 
-	if e.adapter != nil && e.autoSave {
+	if e.shouldPersist() {
 		if err := e.adapter.(persist.BatchAdapter).AddPolicies(sec, ptype, rules); err != nil {
 			if err.Error() != notImplemented {
-				return rulesAdded, err
+				return false, err
 			}
 		}
 	}
 
+	e.model.AddPolicies(sec, ptype, rules)
+
+	if sec == "g" {
+		err := e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, rules)
+		if err != nil {
+			return true, err
+		}
+	}
+
 	if e.watcher != nil && e.autoNotifyWatcher {
 		err := e.watcher.Update()
 		if err != nil {
-			return rulesAdded, err
+			return true, err
 		}
 	}
 
-	return rulesAdded, nil
+	return true, nil
 }
 
 // removePolicy removes a rule from the current policy.
 func (e *Enforcer) removePolicy(sec string, ptype string, rule []string) (bool, error) {
+	if e.shouldPersist() {
+		if err := e.adapter.(persist.BatchAdapter).RemovePolicy(sec, ptype, rule); err != nil {
+			if err.Error() != notImplemented {
+				return false, err
+			}
+		}
+	}
+
 	ruleRemoved := e.model.RemovePolicy(sec, ptype, rule)
 	if !ruleRemoved {
 		return ruleRemoved, nil
@@ -104,14 +118,6 @@ func (e *Enforcer) removePolicy(sec string, ptype string, rule []string) (bool,
 		}
 	}
 
-	if e.adapter != nil && e.autoSave {
-		if err := e.adapter.RemovePolicy(sec, ptype, rule); err != nil {
-			if err.Error() != notImplemented {
-				return ruleRemoved, err
-			}
-		}
-	}
-
 	if e.watcher != nil && e.autoNotifyWatcher {
 		var err error
 		if watcher, ok := e.watcher.(persist.WatcherEx); ok {
@@ -128,6 +134,18 @@ func (e *Enforcer) removePolicy(sec string, ptype string, rule []string) (bool,
 
 // removePolicies removes rules from the current policy.
 func (e *Enforcer) removePolicies(sec string, ptype string, rules [][]string) (bool, error) {
+	if !e.model.HasPolicies(sec, ptype, rules) {
+		return false, nil
+	}
+
+	if e.shouldPersist() {
+		if err := e.adapter.(persist.BatchAdapter).RemovePolicies(sec, ptype, rules); err != nil {
+			if err.Error() != notImplemented {
+				return false, err
+			}
+		}
+	}
+
 	rulesRemoved := e.model.RemovePolicies(sec, ptype, rules)
 	if !rulesRemoved {
 		return rulesRemoved, nil
@@ -140,14 +158,6 @@ func (e *Enforcer) removePolicies(sec string, ptype string, rules [][]string) (b
 		}
 	}
 
-	if e.adapter != nil && e.autoSave {
-		if err := e.adapter.(persist.BatchAdapter).RemovePolicies(sec, ptype, rules); err != nil {
-			if err.Error() != notImplemented {
-				return rulesRemoved, err
-			}
-		}
-	}
-
 	if e.watcher != nil && e.autoNotifyWatcher {
 		err := e.watcher.Update()
 		if err != nil {
@@ -160,6 +170,14 @@ func (e *Enforcer) removePolicies(sec string, ptype string, rules [][]string) (b
 
 // removeFilteredPolicy removes rules based on field filters from the current policy.
 func (e *Enforcer) removeFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) (bool, error) {
+	if e.shouldPersist() {
+		if err := e.adapter.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...); err != nil {
+			if err.Error() != notImplemented {
+				return false, err
+			}
+		}
+	}
+
 	ruleRemoved, effects := e.model.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...)
 	if !ruleRemoved {
 		return ruleRemoved, nil
@@ -172,14 +190,6 @@ func (e *Enforcer) removeFilteredPolicy(sec string, ptype string, fieldIndex int
 		}
 	}
 
-	if e.adapter != nil && e.autoSave {
-		if err := e.adapter.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...); err != nil {
-			if err.Error() != notImplemented {
-				return ruleRemoved, err
-			}
-		}
-	}
-
 	if e.watcher != nil && e.autoNotifyWatcher {
 		var err error
 		if watcher, ok := e.watcher.(persist.WatcherEx); ok {
diff --git a/model/policy.go b/model/policy.go
index 09fb041..c5dd381 100644
--- a/model/policy.go
+++ b/model/policy.go
@@ -20,7 +20,9 @@ import (
 	"github.com/casbin/casbin/v2/util"
 )
 
-type PolicyOp int
+type (
+	PolicyOp int
+)
 
 const (
 	PolicyAdd PolicyOp = iota
@@ -107,28 +109,27 @@ func (model Model) HasPolicy(sec string, ptype string, rule []string) bool {
 	return false
 }
 
-// AddPolicy adds a policy rule to the model.
-func (model Model) AddPolicy(sec string, ptype string, rule []string) bool {
-	if !model.HasPolicy(sec, ptype, rule) {
-		model[sec][ptype].Policy = append(model[sec][ptype].Policy, rule)
-		return true
-	}
-	return false
-}
-
-// AddPolicies adds policy rules to the model.
-func (model Model) AddPolicies(sec string, ptype string, rules [][]string) bool {
+// HasPolicies determines whether a model has any of the specified policies. If one is found we return false.
+func (model Model) HasPolicies(sec string, ptype string, rules [][]string) bool {
 	for i := 0; i < len(rules); i++ {
 		if model.HasPolicy(sec, ptype, rules[i]) {
-			return false
+			return true
 		}
 	}
 
+	return false
+}
+
+// AddPolicy adds a policy rule to the model.
+func (model Model) AddPolicy(sec string, ptype string, rule []string) {
+	model[sec][ptype].Policy = append(model[sec][ptype].Policy, rule)
+}
+
+// AddPolicies adds policy rules to the model.
+func (model Model) AddPolicies(sec string, ptype string, rules [][]string) {
 	for i := 0; i < len(rules); i++ {
 		model[sec][ptype].Policy = append(model[sec][ptype].Policy, rules[i])
 	}
-
-	return true
 }
 
 // RemovePolicy removes a policy rule from the model.
@@ -145,16 +146,6 @@ func (model Model) RemovePolicy(sec string, ptype string, rule []string) bool {
 
 // RemovePolicies removes policy rules from the model.
 func (model Model) RemovePolicies(sec string, ptype string, rules [][]string) bool {
-OUTER:
-	for j := 0; j < len(rules); j++ {
-		for _, r := range model[sec][ptype].Policy {
-			if util.ArrayEquals(rules[j], r) {
-				continue OUTER
-			}
-		}
-		return false
-	}
-
 	for j := 0; j < len(rules); j++ {
 		for i, r := range model[sec][ptype].Policy {
 			if util.ArrayEquals(rules[j], r) {
diff --git a/rbac/default-role-manager/role_manager.go b/rbac/default-role-manager/role_manager.go
index e203e90..98d4c23 100644
--- a/rbac/default-role-manager/role_manager.go
+++ b/rbac/default-role-manager/role_manager.go
@@ -23,118 +23,127 @@ import (
 	"github.com/casbin/casbin/v2/rbac"
 )
 
+const defaultDomain string = "casbin::default"
+
 type MatchingFunc func(arg1, arg2 string) bool
 
 // RoleManager provides a default implementation for the RoleManager interface
 type RoleManager struct {
-	allRoles          *sync.Map
-	maxHierarchyLevel int
-	hasPattern        bool
-	matchingFunc      MatchingFunc
+	allDomains         *sync.Map
+	maxHierarchyLevel  int
+	hasPattern         bool
+	matchingFunc       MatchingFunc
+	hasDomainPattern   bool
+	domainMatchingFunc MatchingFunc
 }
 
 // NewRoleManager is the constructor for creating an instance of the
 // default RoleManager implementation.
 func NewRoleManager(maxHierarchyLevel int) rbac.RoleManager {
 	rm := RoleManager{}
-	rm.allRoles = &sync.Map{}
+	rm.allDomains = &sync.Map{}
+	rm.allDomains.LoadOrStore(defaultDomain, new(Roles))
 	rm.maxHierarchyLevel = maxHierarchyLevel
 	rm.hasPattern = false
+	rm.hasDomainPattern = false
 
 	return &rm
 }
 
-// e.BuildRoleLinks must be called after AddMatchingFunc().
-//
-// example: e.GetRoleManager().(*defaultrolemanager.RoleManager).AddMatchingFunc('matcher', util.KeyMatch)
+// AddMatchingFunc support use pattern in g
 func (rm *RoleManager) AddMatchingFunc(name string, fn MatchingFunc) {
 	rm.hasPattern = true
 	rm.matchingFunc = fn
 }
 
-func (rm *RoleManager) hasRole(name string) bool {
-	var ok bool
-	if rm.hasPattern {
-		rm.allRoles.Range(func(key, value interface{}) bool {
-			if rm.matchingFunc(name, key.(string)) {
-				ok = true
+// AddDomainMatchingFunc support use domain pattern in g
+func (rm *RoleManager) AddDomainMatchingFunc(name string, fn MatchingFunc) {
+	rm.hasDomainPattern = true
+	rm.domainMatchingFunc = fn
+}
+
+func (rm *RoleManager) generateTempRoles(domain string) *Roles {
+	rm.allDomains.LoadOrStore(domain, new(Roles))
+
+	patternDomain := []string{domain}
+	if rm.hasDomainPattern {
+		rm.allDomains.Range(func(key, value interface{}) bool {
+			if rm.domainMatchingFunc(domain, key.(string)) {
+				patternDomain = append(patternDomain, key.(string))
 			}
 			return true
 		})
-	} else {
-		_, ok = rm.allRoles.Load(name)
 	}
 
-	return ok
-}
-
-func (rm *RoleManager) createRole(name string) *Role {
-	role, _ := rm.allRoles.LoadOrStore(name, newRole(name))
-
-	if rm.hasPattern {
-		rm.allRoles.Range(func(key, value interface{}) bool {
-			if rm.matchingFunc(name, key.(string)) && name!=key.(string) {
-				// Add new role to matching role
-				role1, _ := rm.allRoles.LoadOrStore(key.(string), newRole(key.(string)))
-				role.(*Role).addRole(role1.(*Role))
+	allRoles := new(Roles)
+	for _, domain := range patternDomain {
+		value, _ := rm.allDomains.LoadOrStore(domain, new(Roles))
+		value.(*Roles).Range(func(key, value interface{}) bool {
+			role2 := value.(*Role)
+			role1 := allRoles.createRole(role2.name, rm.matchingFunc)
+			for _, v := range role2.roles {
+				role3 := allRoles.createRole(v.name, rm.matchingFunc)
+				role1.addRole(role3)
 			}
+
 			return true
 		})
 	}
 
-	return role.(*Role)
+	return allRoles
 }
 
 // Clear clears all stored data and resets the role manager to the initial state.
 func (rm *RoleManager) Clear() error {
-	rm.allRoles = &sync.Map{}
+	rm.allDomains = &sync.Map{}
+	rm.allDomains.LoadOrStore(defaultDomain, new(Roles))
 	return nil
 }
 
 // AddLink adds the inheritance link between role: name1 and role: name2.
 // aka role: name1 inherits role: name2.
-// domain is a prefix to the roles.
 func (rm *RoleManager) AddLink(name1 string, name2 string, domain ...string) error {
-	if len(domain) == 1 {
-		name1 = domain[0] + "::" + name1
-		name2 = domain[0] + "::" + name2
+	if len(domain) == 0 {
+		domain = []string{defaultDomain}
 	} else if len(domain) > 1 {
 		return errors.ERR_DOMAIN_PARAMETER
 	}
 
-	role1 := rm.createRole(name1)
-	role2 := rm.createRole(name2)
-	role1.addRole(role2)
+	allRoles, _ := rm.allDomains.LoadOrStore(domain[0], new(Roles))
+
+	role1, _ := allRoles.(*Roles).LoadOrStore(name1, newRole(name1))
+	role2, _ := allRoles.(*Roles).LoadOrStore(name2, newRole(name2))
+	role1.(*Role).addRole(role2.(*Role))
 	return nil
 }
 
 // DeleteLink deletes the inheritance link between role: name1 and role: name2.
 // aka role: name1 does not inherit role: name2 any more.
-// domain is a prefix to the roles.
 func (rm *RoleManager) DeleteLink(name1 string, name2 string, domain ...string) error {
-	if len(domain) == 1 {
-		name1 = domain[0] + "::" + name1
-		name2 = domain[0] + "::" + name2
+	if len(domain) == 0 {
+		domain = []string{defaultDomain}
 	} else if len(domain) > 1 {
 		return errors.ERR_DOMAIN_PARAMETER
 	}
 
-	if !rm.hasRole(name1) || !rm.hasRole(name2) {
+	allRoles, _ := rm.allDomains.LoadOrStore(domain[0], new(Roles))
+
+	_, ok1 := allRoles.(*Roles).Load(name1)
+	_, ok2 := allRoles.(*Roles).Load(name1)
+	if !ok1 || !ok2 {
 		return errors.ERR_NAMES12_NOT_FOUND
 	}
 
-	role1 := rm.createRole(name1)
-	role2 := rm.createRole(name2)
-	role1.deleteRole(role2)
+	role1, _ := allRoles.(*Roles).LoadOrStore(name1, newRole(name1))
+	role2, _ := allRoles.(*Roles).LoadOrStore(name2, newRole(name2))
+	role1.(*Role).deleteRole(role2.(*Role))
 	return nil
 }
 
 // HasLink determines whether role: name1 inherits role: name2.
-// domain is a prefix to the roles.
 func (rm *RoleManager) HasLink(name1 string, name2 string, domain ...string) (bool, error) {
-	if len(domain) == 1 {
-		name1 = domain[0] + "::" + name1
-		name2 = domain[0] + "::" + name2
+	if len(domain) == 0 {
+		domain = []string{defaultDomain}
 	} else if len(domain) > 1 {
 		return false, errors.ERR_DOMAIN_PARAMETER
 	}
@@ -143,62 +152,77 @@ func (rm *RoleManager) HasLink(name1 string, name2 string, domain ...string) (bo
 		return true, nil
 	}
 
-	if !rm.hasRole(name1) || !rm.hasRole(name2) {
+	var allRoles *Roles
+	if rm.hasDomainPattern || rm.hasPattern {
+		allRoles = rm.generateTempRoles(domain[0])
+	} else {
+		roles, _ := rm.allDomains.LoadOrStore(domain[0], new(Roles))
+		allRoles = roles.(*Roles)
+	}
+
+	if !allRoles.hasRole(name1, rm.matchingFunc) || !allRoles.hasRole(name2, rm.matchingFunc) {
 		return false, nil
 	}
 
-	role1 := rm.createRole(name1)
+	role1 := allRoles.createRole(name1, rm.matchingFunc)
 	return role1.hasRole(name2, rm.maxHierarchyLevel), nil
 }
 
 // GetRoles gets the roles that a subject inherits.
-// domain is a prefix to the roles.
 func (rm *RoleManager) GetRoles(name string, domain ...string) ([]string, error) {
-	if len(domain) == 1 {
-		name = domain[0] + "::" + name
+	if len(domain) == 0 {
+		domain = []string{defaultDomain}
 	} else if len(domain) > 1 {
 		return nil, errors.ERR_DOMAIN_PARAMETER
 	}
 
-	if !rm.hasRole(name) {
-		return []string{}, nil
+	var allRoles *Roles
+	if rm.hasDomainPattern || rm.hasPattern {
+		allRoles = rm.generateTempRoles(domain[0])
+	} else {
+		roles, _ := rm.allDomains.LoadOrStore(domain[0], new(Roles))
+		allRoles = roles.(*Roles)
 	}
 
-	roles := rm.createRole(name).getRoles()
-	if len(domain) == 1 {
-		for i := range roles {
-			roles[i] = roles[i][len(domain[0])+2:]
-		}
+	if !allRoles.hasRole(name, rm.matchingFunc) {
+		return []string{}, nil
 	}
+
+	roles := allRoles.createRole(name, rm.matchingFunc).getRoles()
+
 	return roles, nil
 }
 
 // GetUsers gets the users that inherits a subject.
 // domain is an unreferenced parameter here, may be used in other implementations.
 func (rm *RoleManager) GetUsers(name string, domain ...string) ([]string, error) {
-	if len(domain) == 1 {
-		name = domain[0] + "::" + name
+	if len(domain) == 0 {
+		domain = []string{defaultDomain}
 	} else if len(domain) > 1 {
 		return nil, errors.ERR_DOMAIN_PARAMETER
 	}
 
-	if !rm.hasRole(name) {
+	var allRoles *Roles
+	if rm.hasDomainPattern || rm.hasPattern {
+		allRoles = rm.generateTempRoles(domain[0])
+	} else {
+		roles, _ := rm.allDomains.LoadOrStore(domain[0], new(Roles))
+		allRoles = roles.(*Roles)
+	}
+
+	if !allRoles.hasRole(name, rm.domainMatchingFunc) {
 		return nil, errors.ERR_NAME_NOT_FOUND
 	}
 
 	names := []string{}
-	rm.allRoles.Range(func(_, value interface{}) bool {
+	allRoles.Range(func(_, value interface{}) bool {
 		role := value.(*Role)
 		if role.hasDirectRole(name) {
 			names = append(names, role.name)
 		}
 		return true
 	})
-	if len(domain) == 1 {
-		for i := range names {
-			names[i] = names[i][len(domain[0])+2:]
-		}
-	}
+
 	return names, nil
 }
 
@@ -206,15 +230,19 @@ func (rm *RoleManager) GetUsers(name string, domain ...string) ([]string, error)
 func (rm *RoleManager) PrintRoles() error {
 	if log.GetLogger().IsEnabled() {
 		var sb strings.Builder
-		rm.allRoles.Range(func(_, value interface{}) bool {
-			if text := value.(*Role).toString(); text != "" {
-				if sb.Len() == 0 {
-					sb.WriteString(text)
-				} else {
-					sb.WriteString(", ")
-					sb.WriteString(text)
+		rm.allDomains.Range(func(_, value interface{}) bool {
+			value.(*Roles).Range(func(_, value interface{}) bool {
+				if text := value.(*Role).toString(); text != "" {
+					if sb.Len() == 0 {
+						sb.WriteString(text)
+					} else {
+						sb.WriteString(", ")
+						sb.WriteString(text)
+					}
 				}
-			}
+				return true
+			})
+
 			return true
 		})
 		log.LogPrint(sb.String())
@@ -222,6 +250,44 @@ func (rm *RoleManager) PrintRoles() error {
 	return nil
 }
 
+// Roles represents all roles in a domain
+type Roles struct {
+	sync.Map
+}
+
+func (roles *Roles) hasRole(name string, matchingFunc MatchingFunc) bool {
+	var ok bool
+	if matchingFunc != nil {
+		roles.Range(func(key, value interface{}) bool {
+			if matchingFunc(name, key.(string)) {
+				ok = true
+			}
+			return true
+		})
+	} else {
+		_, ok = roles.Load(name)
+	}
+
+	return ok
+}
+
+func (roles *Roles) createRole(name string, matchingFunc MatchingFunc) *Role {
+	role, _ := roles.LoadOrStore(name, newRole(name))
+
+	if matchingFunc != nil {
+		roles.Range(func(key, value interface{}) bool {
+			if matchingFunc(name, key.(string)) && name != key.(string) {
+				// Add new role to matching role
+				role1, _ := roles.LoadOrStore(key.(string), newRole(key.(string)))
+				role.(*Role).addRole(role1.(*Role))
+			}
+			return true
+		})
+	}
+
+	return role.(*Role)
+}
+
 // Role represents the data structure for a role in RBAC.
 type Role struct {
 	name  string


Test patch:
diff --git a/error_test.go b/error_test.go
index 26e41cc..1e950d4 100644
--- a/error_test.go
+++ b/error_test.go
@@ -125,7 +125,10 @@ func TestMockAdapterErrors(t *testing.T) {
 
 	e, _ := NewEnforcer("examples/rbac_with_domains_model.conf", adapter)
 
-	_, err := e.AddPolicy("admin", "domain3", "data1", "read")
+	added, err := e.AddPolicy("admin", "domain3", "data1", "read")
+	if added {
+		t.Errorf("added should be false")
+	}
 
 	if err == nil {
 		t.Errorf("Should be an error here.")
@@ -134,10 +137,13 @@ func TestMockAdapterErrors(t *testing.T) {
 		t.Log(err.Error())
 	}
 
-	rules := [][]string {
-			{"admin", "domain4", "data1", "read"},
+	rules := [][]string{
+		{"admin", "domain4", "data1", "read"},
+	}
+	added, err = e.AddPolicies(rules)
+	if added {
+		t.Errorf("added should be false")
 	}
-	_, err = e.AddPolicies(rules)
 
 	if err == nil {
 		t.Errorf("Should be an error here.")
@@ -146,7 +152,10 @@ func TestMockAdapterErrors(t *testing.T) {
 		t.Log(err.Error())
 	}
 
-	_, err2 := e.RemoveFilteredPolicy(1, "domain1", "data1")
+	removed, err2 := e.RemoveFilteredPolicy(1, "domain1", "data1")
+	if removed {
+		t.Errorf("removed should be false")
+	}
 
 	if err2 == nil {
 		t.Errorf("Should be an error here.")
@@ -155,7 +164,10 @@ func TestMockAdapterErrors(t *testing.T) {
 		t.Log(err2.Error())
 	}
 
-	_, err3 := e.RemovePolicy("admin", "domain2", "data2", "read")
+	removed, err3 := e.RemovePolicy("admin", "domain2", "data2", "read")
+	if removed {
+		t.Errorf("removed should be false")
+	}
 
 	if err3 == nil {
 		t.Errorf("Should be an error here.")
@@ -164,11 +176,13 @@ func TestMockAdapterErrors(t *testing.T) {
 		t.Log(err3.Error())
 	}
 
-	rules = [][]string {
-		{"admin", "domain4", "data1", "read"},
+	rules = [][]string{
+		{"admin", "domain1", "data1", "read"},
+	}
+	removed, err = e.RemovePolicies(rules)
+	if removed {
+		t.Errorf("removed should be false")
 	}
-
-	_, err = e.RemovePolicies(rules)
 
 	if err == nil {
 		t.Errorf("Should be an error here.")
@@ -176,7 +190,11 @@ func TestMockAdapterErrors(t *testing.T) {
 		t.Log("Test on error: ")
 		t.Log(err.Error())
 	}
-	_, err4 := e.AddGroupingPolicy("bob", "admin2")
+
+	added, err4 := e.AddGroupingPolicy("bob", "admin2")
+	if added {
+		t.Errorf("added should be false")
+	}
 
 	if err4 == nil {
 		t.Errorf("Should be an error here.")
@@ -185,7 +203,10 @@ func TestMockAdapterErrors(t *testing.T) {
 		t.Log(err4.Error())
 	}
 
-	_, err5 := e.AddNamedGroupingPolicy("g", []string{"eve", "admin2", "domain1"})
+	added, err5 := e.AddNamedGroupingPolicy("g", []string{"eve", "admin2", "domain1"})
+	if added {
+		t.Errorf("added should be false")
+	}
 
 	if err5 == nil {
 		t.Errorf("Should be an error here.")
@@ -194,7 +215,10 @@ func TestMockAdapterErrors(t *testing.T) {
 		t.Log(err5.Error())
 	}
 
-	_, err6 := e.AddNamedPolicy("p", []string{"admin2", "domain2", "data2", "write"})
+	added, err6 := e.AddNamedPolicy("p", []string{"admin2", "domain2", "data2", "write"})
+	if added {
+		t.Errorf("added should be false")
+	}
 
 	if err6 == nil {
 		t.Errorf("Should be an error here.")
@@ -203,7 +227,10 @@ func TestMockAdapterErrors(t *testing.T) {
 		t.Log(err6.Error())
 	}
 
-	_, err7 := e.RemoveGroupingPolicy("bob", "admin2")
+	removed, err7 := e.RemoveGroupingPolicy("bob", "admin2")
+	if removed {
+		t.Errorf("removed should be false")
+	}
 
 	if err7 == nil {
 		t.Errorf("Should be an error here.")
@@ -212,7 +239,10 @@ func TestMockAdapterErrors(t *testing.T) {
 		t.Log(err7.Error())
 	}
 
-	_, err8 := e.RemoveFilteredGroupingPolicy(0, "bob")
+	removed, err8 := e.RemoveFilteredGroupingPolicy(0, "bob")
+	if removed {
+		t.Errorf("removed should be false")
+	}
 
 	if err8 == nil {
 		t.Errorf("Should be an error here.")
@@ -221,7 +251,10 @@ func TestMockAdapterErrors(t *testing.T) {
 		t.Log(err8.Error())
 	}
 
-	_, err9 := e.RemoveNamedGroupingPolicy("g", []string{"alice", "admin", "domain1"})
+	removed, err9 := e.RemoveNamedGroupingPolicy("g", []string{"alice", "admin", "domain1"})
+	if removed {
+		t.Errorf("removed should be false")
+	}
 
 	if err9 == nil {
 		t.Errorf("Should be an error here.")
@@ -230,7 +263,10 @@ func TestMockAdapterErrors(t *testing.T) {
 		t.Log(err9.Error())
 	}
 
-	_, err10 := e.RemoveFilteredNamedGroupingPolicy("g", 0, "eve")
+	removed, err10 := e.RemoveFilteredNamedGroupingPolicy("g", 0, "eve")
+	if removed {
+		t.Errorf("removed should be false")
+	}
 
 	if err10 == nil {
 		t.Errorf("Should be an error here.")
diff --git a/model_test.go b/model_test.go
index c18cff1..00efd9b 100644
--- a/model_test.go
+++ b/model_test.go
@@ -563,7 +563,6 @@ func TestABACPolicy(t *testing.T) {
 
 func TestCommentModel(t *testing.T) {
 	e, _ := NewEnforcer("examples/comment_model.conf", "examples/basic_policy.csv")
-
 	testEnforce(t, e, "alice", "data1", "read", true)
 	testEnforce(t, e, "alice", "data1", "write", false)
 	testEnforce(t, e, "alice", "data2", "read", false)
@@ -573,3 +572,30 @@ func TestCommentModel(t *testing.T) {
 	testEnforce(t, e, "bob", "data2", "read", false)
 	testEnforce(t, e, "bob", "data2", "write", true)
 }
+
+func TestDomainMatchModel(t *testing.T) {
+	e, _ := NewEnforcer("examples/rbac_with_domain_pattern_model.conf", "examples/rbac_with_domain_pattern_policy.csv")
+	e.rm.(*defaultrolemanager.RoleManager).AddDomainMatchingFunc("keyMatch2", util.KeyMatch2)
+
+	testDomainEnforce(t, e, "alice", "domain1", "data1", "read", true)
+	testDomainEnforce(t, e, "alice", "domain1", "data1", "write", true)
+	testDomainEnforce(t, e, "alice", "domain1", "data2", "read", false)
+	testDomainEnforce(t, e, "alice", "domain1", "data2", "write", false)
+	testDomainEnforce(t, e, "alice", "domain2", "data2", "read", true)
+	testDomainEnforce(t, e, "alice", "domain2", "data2", "write", true)
+	testDomainEnforce(t, e, "bob", "domain2", "data1", "read", false)
+	testDomainEnforce(t, e, "bob", "domain2", "data1", "write", false)
+	testDomainEnforce(t, e, "bob", "domain2", "data2", "read", true)
+	testDomainEnforce(t, e, "bob", "domain2", "data2", "write", true)
+}
+
+func TestAllMatchModel(t *testing.T) {
+	e, _ := NewEnforcer("examples/rbac_with_all_pattern_model.conf", "examples/rbac_with_all_pattern_policy.csv")
+	e.rm.(*defaultrolemanager.RoleManager).AddMatchingFunc("keyMatch2", util.KeyMatch2)
+	e.rm.(*defaultrolemanager.RoleManager).AddDomainMatchingFunc("keyMatch2", util.KeyMatch2)
+
+	testDomainEnforce(t, e, "alice", "domain1", "/book/1", "read", true)
+	testDomainEnforce(t, e, "alice", "domain1", "/book/1", "write", false)
+	testDomainEnforce(t, e, "alice", "domain2", "/book/1", "read", false)
+	testDomainEnforce(t, e, "alice", "domain2", "/book/1", "write", true)
+}
diff --git a/rbac/default-role-manager/role_manager_test.go b/rbac/default-role-manager/role_manager_test.go
index 1e79af1..b8c1756 100644
--- a/rbac/default-role-manager/role_manager_test.go
+++ b/rbac/default-role-manager/role_manager_test.go
@@ -51,6 +51,15 @@ func testPrintRoles(t *testing.T, rm rbac.RoleManager, name string, res []string
 	}
 }
 
+func testPrintRolesWithDomain(t *testing.T, rm rbac.RoleManager, name string, domain string, res []string) {
+	t.Helper()
+	myRes, _ := rm.GetRoles(name, domain)
+
+	if !util.ArrayEquals(myRes, res) {
+		t.Errorf("%s: %s, supposed to be %s", name, myRes, res)
+	}
+}
+
 func TestRole(t *testing.T) {
 	rm := NewRoleManager(3)
 	rm.AddLink("u1", "g1")
@@ -221,3 +230,47 @@ func TestClear(t *testing.T) {
 	testRole(t, rm, "u4", "g2", false)
 	testRole(t, rm, "u4", "g3", false)
 }
+
+func TestDomainPartternRole(t *testing.T) {
+	rm := NewRoleManager(10)
+	rm.(*RoleManager).AddDomainMatchingFunc("keyMatch2", util.KeyMatch2)
+
+	rm.AddLink("u1", "g1", "domain1")
+	rm.AddLink("u2", "g1", "domain2")
+	rm.AddLink("u3", "g1", "*")
+	rm.AddLink("u4", "g2", "domain3")
+	// Current role inheritance tree after deleting the links:
+	//       domain1:g1    domain2:g1			domain3:g2
+	//		   /      \    /      \					|
+	//	 domain1:u1    *:g1     domain2:u2		domain3:u4
+	// 					|
+	// 				   *:u3
+	testDomainRole(t, rm, "u1", "g1", "domain1", true)
+	testDomainRole(t, rm, "u2", "g1", "domain1", false)
+	testDomainRole(t, rm, "u2", "g1", "domain2", true)
+	testDomainRole(t, rm, "u3", "g1", "domain1", true)
+	testDomainRole(t, rm, "u3", "g1", "domain2", true)
+	testDomainRole(t, rm, "u1", "g2", "domain1", false)
+	testDomainRole(t, rm, "u4", "g2", "domain3", true)
+	testDomainRole(t, rm, "u3", "g2", "domain3", false)
+
+	testPrintRolesWithDomain(t, rm, "u3", "domain1", []string{"g1"})
+	testPrintRolesWithDomain(t, rm, "u1", "domain1", []string{"g1"})
+	testPrintRolesWithDomain(t, rm, "u3", "domain2", []string{"g1"})
+	testPrintRolesWithDomain(t, rm, "u1", "domain2", []string{})
+	testPrintRolesWithDomain(t, rm, "u4", "domain3", []string{"g2"})
+}
+
+func TestAllMatchingFunc(t *testing.T) {
+	rm := NewRoleManager(10)
+	rm.(*RoleManager).AddMatchingFunc("keyMatch2", util.KeyMatch2)
+	rm.(*RoleManager).AddDomainMatchingFunc("keyMatch2", util.KeyMatch2)
+
+	rm.AddLink("/book/:id", "book_group", "*")
+	// Current role inheritance tree after deleting the links:
+	//  		*:book_group
+	//				|
+	// 			*:/book/:id
+	testDomainRole(t, rm, "/book/1", "book_group", "domain1", true)
+	testDomainRole(t, rm, "/book/2", "book_group", "domain1", true)
+}
diff --git a/rbac_api_test.go b/rbac_api_test.go
index 76c8577..b41cb11 100644
--- a/rbac_api_test.go
+++ b/rbac_api_test.go
@@ -15,10 +15,11 @@
 package casbin
 
 import (
-	defaultrolemanager "github.com/casbin/casbin/v2/rbac/default-role-manager"
 	"sort"
 	"testing"
 
+	defaultrolemanager "github.com/casbin/casbin/v2/rbac/default-role-manager"
+
 	"github.com/casbin/casbin/v2/errors"
 	"github.com/casbin/casbin/v2/util"
 )
@@ -233,10 +234,6 @@ func TestImplicitRoleAPI(t *testing.T) {
 	e, _ = NewEnforcer("examples/rbac_with_pattern_model.conf", "examples/rbac_with_pattern_policy.csv")
 
 	e.GetRoleManager().(*defaultrolemanager.RoleManager).AddMatchingFunc("matcher", util.KeyMatch)
-	err := e.BuildRoleLinks()
-	if err != nil {
-		t.Error(err)
-	}
 
 	testGetImplicitRoles(t, e, "cathy", []string{"/book/1/2/3/4/5", "pen_admin", "/book/*", "book_group"})
 	testGetRoles(t, e, []string{"/book/1/2/3/4/5", "pen_admin"}, "cathy")