Repository: casbin/casbin
Pull request #859 (2021-08-08 09:41:44)

fix: role inheritance fault
Signed-off-by: closetool <c299999999@qq.com>

Fix: #855 

closetool: @hsluoyz @tangyang9464 Changed a lot for next pr to support cross domain
tangyang9464: @closetool 
I still use #855 sample to test and found some errors which are same as #858
```
p, role||2, dom2, menu||2, *

g, role||5, role||2, tenant||1

g, user||1, role||5, *
g, user||2, role||5, dom2

r, user||2, dom2, menu||2, *  -->true, but should be false ?
```
More interestingly，I just exchange their order and the result is false
```
p, role||2, dom2, menu||2, *

g, user||2, role||5, dom2  // exchange order

g, role||5, role||2, tenant||1

g, user||1, role||5, *

r, user||2, dom2, menu||2, *  -->false
```
I think the reason is
```
g, role||5, role||2, tenant||1
g, user||1, role||5, *

The mapping relationship is
*
user||1  ->  role||5
role||5  ->  role||2   // This shouldn't exist

tenant||1
user||1  ->  role||5
role||5  ->  role||2
```
closetool: @tangyang9464 I test your case, but the result is `false`
hsluoyz: @ErikXu plz review
tangyang9464: @closetool
Different order will lead to different results，this error is the same as #701
```
g, user||1, role||5, *
g, role||5, role||2, tenant||1

g, role||5, role||2, tenant||1
g, user||1, role||5, *
```
It is caused by ``getPatternDomain`` method
closetool: @tangyang9464 OK, I have an idea, we build the whole relationship after adding links.
Then perhaps everything will be OK.
sagilio: In fact, Casbin.NET uses the same implementation as you now. but, It can not fix all errors.

This struct can only check whether has links on one domain (included pattern domain like `*`), at the single loop of `match domains`. Then independently handle the result of each loop (now the final result is determined by the first `true` like `allow override`).

So now we may return an error result when two roles/users have a link in more than one domain at the same time.
ErikXu: @hsluoyz I've changed my local codes and tried the new version of [role_manager.go](https://github.com/casbin/casbin/blob/9bbcbaf37c555cc59d4ff6433c1f9aabbce84ec5/rbac/default-role-manager/role_manager.go) in this pull request. But the result still seems to be incorrect. Did I miss any steps?
> @ErikXu plz review


tangyang9464: > @hsluoyz I've changed my local codes and tried the new version of [role_manager.go](https://github.com/casbin/casbin/blob/9bbcbaf37c555cc59d4ff6433c1f9aabbce84ec5/rbac/default-role-manager/role_manager.go) in this pull request. But the result still seems to be incorrect. Did I miss any steps?
> 
> > @ErikXu plz review

@Erikxu
you need call ``e.BuildRoleLinks()`` like this
```
e.AddNamedDomainMatchingFunc("g","",util.KeyMatch)
e.BuildRoleLinks()
```
tangyang9464: > @tangyang9464 OK, I have an idea, we build the whole relationship after adding links.
> Then perhaps everything will be OK.

@closetool maybe we can just call ``e.model.BuildRoleLinks(e.rmMap)`` instead of ``e.BuildRoleLinks()`` because ``e.BuildRoleLinks()`` will call ``rm.Clear()``.Then the error can be resloved
closetool: @sagilio @tangyang9464 I think @sagilio is right, perhaps I could not fix this bug. We need to clarify which is wild card and which is not.
tangyang9464: > @sagilio @tangyang9464 I think @sagilio is right, perhaps I could not fix this bug. We need to clarify which is wild card and which is not.

@closetool @sagilio I understand a little bit, but don't know the specific example.Can you show me a simple example?
ErikXu: I've tried again as you mentioned, and the result turns out to be correct now. Could you please help to estimate and approve this pull request ASAP. So I can use the new version directly. Thanks very much!

> @ErikXu
> you need call `e.BuildRoleLinks()` like this
> 
> ```
> e.AddNamedDomainMatchingFunc("g","",util.KeyMatch)
> e.BuildRoleLinks()
> ```


tangyang9464: > I've tried again as you mentioned, and the result turns out to be correct now. Could you please help to estimate and approve this pull request ASAP. So I can use the new version directly. Thanks very much!
> 
> > @ErikXu
> > you need call `e.BuildRoleLinks()` like this
> > ```
> > e.AddNamedDomainMatchingFunc("g","",util.KeyMatch)
> > e.BuildRoleLinks()
> > ```

@ErikXu now this PR still has some other bugs.
ErikXu: OK, I understand. Please keep going with your own schedule. I will continue watching this issue. Thanks!

> > I've tried again as you mentioned, and the result turns out to be correct now. Could you please help to estimate and approve this pull request ASAP. So I can use the new version directly. Thanks very much!
> > > @ErikXu
> > > you need call `e.BuildRoleLinks()` like this
> > > ```
> > > e.AddNamedDomainMatchingFunc("g","",util.KeyMatch)
> > > e.BuildRoleLinks()
> > > ```
> 
> @ErikXu now this PR still has some other bugs.


closetool: @tangyang9464 @sagilio This pr should fixed all known bugs, plz review

Patch:
diff --git a/model/assertion.go b/model/assertion.go
index 99b3811..b4e41ad 100644
--- a/model/assertion.go
+++ b/model/assertion.go
@@ -64,6 +64,15 @@ func (ast *Assertion) buildIncrementalRoleLinks(rm rbac.RoleManager, op PolicyOp
 		}
 	}
 
+	if op == PolicyAdd {
+		for _, rule := range rules {
+			err := rm.BuildRelationship(rule[0],rule[1],rule[2:]...)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
 	return nil
 }
 
@@ -86,6 +95,13 @@ func (ast *Assertion) buildRoleLinks(rm rbac.RoleManager) error {
 		}
 	}
 
+	for _, rule := range ast.Policy {
+		err := ast.RM.BuildRelationship(rule[0],rule[1],rule[2:]...)
+		if err != nil {
+			return err
+		}
+	}
+
 	return nil
 }
 
diff --git a/model/policy.go b/model/policy.go
index ec1ab73..473c380 100644
--- a/model/policy.go
+++ b/model/policy.go
@@ -302,7 +302,7 @@ func (model Model) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int
 			effects = append(effects, rule)
 		} else {
 			tmp = append(tmp, rule)
-			model[sec][ptype].PolicyMap[strings.Join(rule, DefaultSep)] = len(tmp)-1
+			model[sec][ptype].PolicyMap[strings.Join(rule, DefaultSep)] = len(tmp) - 1
 		}
 	}
 
diff --git a/rbac/default-role-manager/role_manager.go b/rbac/default-role-manager/role_manager.go
index ab9cb1e..30f1bd7 100644
--- a/rbac/default-role-manager/role_manager.go
+++ b/rbac/default-role-manager/role_manager.go
@@ -15,7 +15,6 @@
 package defaultrolemanager
 
 import (
-	"fmt"
 	"strings"
 	"sync"
 
@@ -25,14 +24,12 @@ import (
 )
 
 const defaultDomain string = ""
-const defaultSeparator = "::"
 
 type MatchingFunc func(arg1, arg2 string) bool
 
 // RoleManager provides a default implementation for the RoleManager interface
 type RoleManager struct {
-	roles   *Roles
-	domains map[string]struct{}
+	allDomains *sync.Map
 
 	maxHierarchyLevel  int
 	hasPattern         bool
@@ -50,8 +47,7 @@ type RoleManager struct {
 // default RoleManager implementation.
 func NewRoleManager(maxHierarchyLevel int) *RoleManager {
 	rm := RoleManager{}
-	rm.roles = &Roles{sync.Map{}}
-	rm.domains = make(map[string]struct{})
+	rm.allDomains = &sync.Map{}
 	rm.maxHierarchyLevel = maxHierarchyLevel
 	rm.hasPattern = false
 	rm.hasDomainPattern = false
@@ -82,8 +78,7 @@ func (rm *RoleManager) SetLogger(logger log.Logger) {
 
 // Clear clears all stored data and resets the role manager to the initial state.
 func (rm *RoleManager) Clear() error {
-	rm.roles = &Roles{sync.Map{}}
-	rm.domains = make(map[string]struct{})
+	rm.allDomains = &sync.Map{}
 	rm.matchingFuncCache = make(map[string]bool)
 	rm.domainMatchingFuncCache = make(map[string]bool)
 	return nil
@@ -91,75 +86,99 @@ func (rm *RoleManager) Clear() error {
 
 // AddLink adds the inheritance link between role: name1 and role: name2.
 // aka role: name1 inherits role: name2.
-func (rm *RoleManager) AddLink(name1 string, name2 string, domain ...string) error {
-	switch len(domain) {
+func (rm *RoleManager) AddLink(name1 string, name2 string, domains ...string) error {
+	switch len(domains) {
 	case 0:
-		domain = []string{defaultDomain}
+		domains = []string{defaultDomain}
 		fallthrough
 	case 1:
-		rm.domains[domain[0]] = struct{}{}
-		patternDomain := rm.getPatternDomain(domain[0])
+		domainValue, _ := rm.allDomains.LoadOrStore(domains[0], &Roles{})
+		domain := domainValue.(*Roles)
 
-		for _, domain := range patternDomain {
-			name1WithDomain := getNameWithDomain(domain, name1)
-			name2WithDomain := getNameWithDomain(domain, name2)
+		role1 := domain.createRole(name1)
+		role2 := domain.createRole(name2)
+		role1.addRole(role2)
+
+		return nil
+	default:
+		return errors.ERR_DOMAIN_PARAMETER
+	}
+
+}
+
+func (rm *RoleManager) BuildRelationship(name1, name2 string, domains ...string) error {
+	switch len(domains) {
+	case 0:
+		domains = []string{defaultDomain}
+		fallthrough
+	case 1:
+		domainValue, _ := rm.allDomains.LoadOrStore(domains[0], &Roles{})
+		domain := domainValue.(*Roles)
 
-			role1 := rm.roles.createRole(name1WithDomain)
-			role2 := rm.roles.createRole(name2WithDomain)
-			role1.addRole(role2)
+		role1 := domain.createRole(name1)
+		role2 := domain.createRole(name2)
+
+		patternDomain := rm.getPatternDomain(domains[0])
+		for _, domain := range patternDomain {
+			allRoles, _ := rm.allDomains.LoadOrStore(domain, new(Roles))
 
 			if rm.hasPattern {
-				rm.roles.Range(func(key, value interface{}) bool {
-					domainPattern, namePattern := getNameAndDomain(key.(string))
-					if name1 == namePattern || name2 == namePattern {
-						return true
+				allRoles.(*Roles).Range(func(key, value interface{}) bool {
+					namePattern := key.(string)
+					if rm.match(namePattern, name1) {
+						value.(*Role).addRole(role1)
 					}
-					if rm.hasDomainPattern {
-						if !rm.domainMatch(domain, domainPattern) {
-							return true
-						}
-					} else {
-						if domain != domainPattern {
-							return true
-						}
+					if rm.match(name1, namePattern) {
+						role1.addRole(value.(*Role))
 					}
-					if rm.match(namePattern, name1) {
-						valueRole, _ := rm.roles.LoadOrStore(key.(string), newRole(key.(string)))
-						valueRole.(*Role).addRole(role1)
+					if rm.match(namePattern, name2) {
+						value.(*Role).addRole(role2)
 					}
 					if rm.match(name2, namePattern) {
 						role2.addRole(value.(*Role))
 					}
 					return true
 				})
+			} else {
+				if domain == domains[0] {
+					continue
+				}
+				allRoles.(*Roles).Range(func(key, value interface{}) bool {
+					if name1 == key.(string) {
+						role1.addRole(value.(*Role))
+					}
+					if name2 == key.(string) {
+						role2.addRole(value.(*Role))
+					}
+					return true
+				})
 			}
 		}
 		return nil
 	default:
 		return errors.ERR_DOMAIN_PARAMETER
 	}
-
 }
 
 // DeleteLink deletes the inheritance link between role: name1 and role: name2.
 // aka role: name1 does not inherit role: name2 any more.
-func (rm *RoleManager) DeleteLink(name1 string, name2 string, domain ...string) error {
-	switch len(domain) {
+func (rm *RoleManager) DeleteLink(name1 string, name2 string, domains ...string) error {
+	switch len(domains) {
 	case 0:
-		domain = []string{defaultDomain}
+		domains = []string{defaultDomain}
 		fallthrough
 	case 1:
-		name1WithDomain := getNameWithDomain(domain[0], name1)
-		name2WithDomain := getNameWithDomain(domain[0], name2)
 
-		_, ok1 := rm.roles.Load(name1WithDomain)
-		_, ok2 := rm.roles.Load(name2WithDomain)
+		domainValue, _ := rm.allDomains.LoadOrStore(domains[0], &Roles{})
+		domain := domainValue.(*Roles)
+		_, ok1 := domain.Load(name1)
+		_, ok2 := domain.Load(name2)
 		if !ok1 || !ok2 {
 			return errors.ERR_NAMES12_NOT_FOUND
 		}
 
-		role1 := rm.roles.createRole(name1WithDomain)
-		role2 := rm.roles.createRole(name2WithDomain)
+		role1 := domain.createRole(name1)
+		role2 := domain.createRole(name2)
 		role1.deleteRole(role2)
 		return nil
 	default:
@@ -170,49 +189,40 @@ func (rm *RoleManager) DeleteLink(name1 string, name2 string, domain ...string)
 func (rm *RoleManager) getPatternDomain(domain string) []string {
 	matchedDomains := []string{domain}
 	if rm.hasDomainPattern {
-		for domainPattern := range rm.domains {
+		rm.allDomains.Range(func(key, value interface{}) bool {
+			domainPattern := key.(string)
+
 			if domain != domainPattern && rm.domainMatch(domain, domainPattern) {
 				matchedDomains = append(matchedDomains, domainPattern)
 			}
-		}
+			return true
+		})
 	}
 	return matchedDomains
 }
 
 // HasLink determines whether role: name1 inherits role: name2.
-func (rm *RoleManager) HasLink(name1 string, name2 string, domain ...string) (bool, error) {
-	switch len(domain) {
+func (rm *RoleManager) HasLink(name1 string, name2 string, domains ...string) (bool, error) {
+	switch len(domains) {
 	case 0:
-		domain = []string{defaultDomain}
+		domains = []string{defaultDomain}
 		fallthrough
 	case 1:
 		if name1 == name2 {
 			return true, nil
 		}
 
-		matchedDomain := rm.getPatternDomain(domain[0])
+		matchedDomain := rm.getPatternDomain(domains[0])
 
-		for _, domain := range matchedDomain {
-			if !rm.hasRole(domain, name1) || !rm.hasRole(domain, name2) {
-				continue
-			}
+		for _, domainName := range matchedDomain {
 
-			name1WithDomain := getNameWithDomain(domain, name1)
-			name2WithDomain := getNameWithDomain(domain, name2)
+			domainValue, _ := rm.allDomains.LoadOrStore(domainName, &Roles{})
+			domain := domainValue.(*Roles)
 
 			if rm.hasPattern {
 				flag := false
-				rm.roles.Range(func(key, value interface{}) bool {
-					nameWithDomain := key.(string)
-					keyDomain, name := getNameAndDomain(nameWithDomain)
-					if rm.hasDomainPattern {
-						if !rm.domainMatch(domain, keyDomain) {
-							return true
-						}
-					} else if domain != keyDomain {
-						return true
-					}
-					if rm.match(name1, name) && value.(*Role).hasRoleWithMatchingFunc(name2, rm.maxHierarchyLevel, rm.match) {
+				domain.Range(func(key, value interface{}) bool {
+					if rm.match(name1, key.(string)) && value.(*Role).hasRoleWithMatchingFunc(name2, rm.maxHierarchyLevel, rm.match) {
 						flag = true
 						return false
 					}
@@ -222,8 +232,12 @@ func (rm *RoleManager) HasLink(name1 string, name2 string, domain ...string) (bo
 					return true, nil
 				}
 			} else {
-				role1 := rm.roles.createRole(name1WithDomain)
-				result := role1.hasRole(name2WithDomain, rm.maxHierarchyLevel)
+				role1Value, ok := domain.Load(name1)
+				if !ok {
+					continue
+				}
+				role1 := role1Value.(*Role)
+				result := role1.hasRole(name2, rm.maxHierarchyLevel)
 				if result {
 					return true, nil
 				}
@@ -236,21 +250,21 @@ func (rm *RoleManager) HasLink(name1 string, name2 string, domain ...string) (bo
 }
 
 // GetRoles gets the roles that a subject inherits.
-func (rm *RoleManager) GetRoles(name string, domain ...string) ([]string, error) {
-	switch len(domain) {
+func (rm *RoleManager) GetRoles(name string, domains ...string) ([]string, error) {
+	switch len(domains) {
 	case 0:
-		domain = []string{defaultDomain}
+		domains = []string{defaultDomain}
 		fallthrough
 	case 1:
-		patternDomain := rm.getPatternDomain(domain[0])
+		patternDomain := rm.getPatternDomain(domains[0])
 
 		var gottenRoles []string
 		for _, domain := range patternDomain {
-			nameWithDomain := getNameWithDomain(domain, name)
 			if !rm.hasRole(domain, name) {
 				continue
 			}
-			gottenRoles = append(gottenRoles, rm.roles.createRole(nameWithDomain).getRoles()...)
+			domainValue, _ := rm.allDomains.LoadOrStore(domain, &Roles{})
+			gottenRoles = append(gottenRoles, domainValue.(*Roles).createRole(name).getRoles()...)
 		}
 		gottenRoles = util.RemoveDuplicateElement(gottenRoles)
 		return gottenRoles, nil
@@ -271,16 +285,16 @@ func (rm *RoleManager) GetUsers(name string, domain ...string) ([]string, error)
 
 		var names []string
 		for _, domain := range patternDomain {
-			nameWithDomain := getNameWithDomain(domain, name)
 			if !rm.hasRole(domain, name) {
 				return nil, errors.ERR_NAME_NOT_FOUND
 			}
 
-			rm.roles.Range(func(_, value interface{}) bool {
+			domainValue, _ := rm.allDomains.LoadOrStore(domain, &Roles{})
+
+			domainValue.(*Roles).Range(func(_, value interface{}) bool {
 				role := value.(*Role)
-				if role.hasDirectRole(nameWithDomain) {
-					_, roleName := getNameAndDomain(role.nameWithDomain)
-					names = append(names, roleName)
+				if role.hasDirectRole(name) {
+					names = append(names, role.name)
 				}
 				return true
 			})
@@ -299,10 +313,13 @@ func (rm *RoleManager) PrintRoles() error {
 	}
 
 	var roles []string
-	rm.roles.Range(func(_, value interface{}) bool {
-		if text := value.(*Role).toString(); text != "" {
-			roles = append(roles, text)
-		}
+	rm.allDomains.Range(func(key, value interface{}) bool {
+		value.(*Roles).Range(func(_, value interface{}) bool {
+			if text := value.(*Role).toString(); text != "" {
+				roles = append(roles, text)
+			}
+			return true
+		})
 		return true
 	})
 
@@ -313,12 +330,13 @@ func (rm *RoleManager) PrintRoles() error {
 // GetDomains gets domains that a user has
 func (rm *RoleManager) GetDomains(name string) ([]string, error) {
 	var domains []string
-	for domain := range rm.domains {
-		if rm.hasAnyRole(name, domain) {
-			domains = append(domains, domain)
+	rm.allDomains.Range(func(key, value interface{}) bool {
+		domainName := key.(string)
+		if rm.hasAnyRole(name, domainName) {
+			domains = append(domains, domainName)
 		}
-	}
-	domains = util.RemoveDuplicateElement(domains)
+		return true
+	})
 	return domains, nil
 }
 
@@ -333,10 +351,12 @@ func (rm *RoleManager) hasAnyRole(name string, domain string) bool {
 }
 
 func (rm *RoleManager) hasRole(domain, name string) bool {
+	roles, _ := rm.allDomains.LoadOrStore(domain, &Roles{})
+	allRoles := roles.(*Roles)
 	if rm.hasPattern {
-		return rm.roles.hasRole(domain, name, rm.match)
+		return allRoles.hasRole(domain, name, rm.match)
 	} else {
-		return rm.roles.hasRole(domain, name, nil)
+		return allRoles.hasRole(domain, name, nil)
 	}
 }
 
@@ -362,8 +382,6 @@ func (rm *RoleManager) domainMatch(domain1, domain2 string) bool {
 	}
 }
 
-
-
 // Roles represents all roles in a domain
 type Roles struct {
 	sync.Map
@@ -373,14 +391,14 @@ func (roles *Roles) hasRole(domain, name string, matchingFunc MatchingFunc) bool
 	var ok bool
 	if matchingFunc != nil {
 		roles.Range(func(key, value interface{}) bool {
-			domainPattern, namePattern := getNameAndDomain(key.(string))
-			if domainPattern == domain && matchingFunc(name, namePattern) {
+			namePattern := key.(string)
+			if matchingFunc(name, namePattern) {
 				ok = true
 			}
 			return true
 		})
 	} else {
-		_, ok = roles.Load(getNameWithDomain(domain, name))
+		_, ok = roles.Load(name)
 	}
 
 	return ok
@@ -393,20 +411,20 @@ func (roles *Roles) createRole(name string) *Role {
 
 // Role represents the data structure for a role in RBAC.
 type Role struct {
-	nameWithDomain string
-	roles          []*Role
+	name  string
+	roles []*Role
 }
 
 func newRole(name string) *Role {
 	r := Role{}
-	r.nameWithDomain = name
+	r.name = name
 	return &r
 }
 
 func (r *Role) addRole(role *Role) {
 	// determine whether this role has been added
 	for _, rr := range r.roles {
-		if rr.nameWithDomain == role.nameWithDomain {
+		if rr.name == role.name {
 			return
 		}
 	}
@@ -416,15 +434,15 @@ func (r *Role) addRole(role *Role) {
 
 func (r *Role) deleteRole(role *Role) {
 	for i, rr := range r.roles {
-		if rr.nameWithDomain == role.nameWithDomain {
+		if rr.name == role.name {
 			r.roles = append(r.roles[:i], r.roles[i+1:]...)
 			return
 		}
 	}
 }
 
-func (r *Role) hasRole(nameWithDomain string, hierarchyLevel int) bool {
-	if r.hasDirectRole(nameWithDomain) {
+func (r *Role) hasRole(name string, hierarchyLevel int) bool {
+	if r.hasDirectRole(name) {
 		return true
 	}
 
@@ -433,7 +451,7 @@ func (r *Role) hasRole(nameWithDomain string, hierarchyLevel int) bool {
 	}
 
 	for _, role := range r.roles {
-		if role.hasRole(nameWithDomain, hierarchyLevel-1) {
+		if role.hasRole(name, hierarchyLevel-1) {
 			return true
 		}
 	}
@@ -457,9 +475,9 @@ func (r *Role) hasRoleWithMatchingFunc(name string, hierarchyLevel int, matching
 	return false
 }
 
-func (r *Role) hasDirectRole(nameWithDomain string) bool {
+func (r *Role) hasDirectRole(name string) bool {
 	for _, role := range r.roles {
-		if role.nameWithDomain == nameWithDomain {
+		if role.name == name {
 			return true
 		}
 	}
@@ -469,8 +487,7 @@ func (r *Role) hasDirectRole(nameWithDomain string) bool {
 
 func (r *Role) hasDirectRoleWithMatchingFunc(name string, matchingFunc MatchingFunc) bool {
 	for _, role := range r.roles {
-		_, roleName := getNameAndDomain(role.nameWithDomain)
-		if roleName == name || (matchingFunc(name, roleName) && name != roleName) {
+		if role.name == name || (matchingFunc(name, role.name) && name != role.name) {
 			return true
 		}
 	}
@@ -484,7 +501,7 @@ func (r *Role) toString() string {
 	}
 
 	var sb strings.Builder
-	sb.WriteString(r.nameWithDomain)
+	sb.WriteString(r.name)
 	sb.WriteString(" < ")
 	if len(r.roles) != 1 {
 		sb.WriteString("(")
@@ -492,10 +509,10 @@ func (r *Role) toString() string {
 
 	for i, role := range r.roles {
 		if i == 0 {
-			sb.WriteString(role.nameWithDomain)
+			sb.WriteString(role.name)
 		} else {
 			sb.WriteString(", ")
-			sb.WriteString(role.nameWithDomain)
+			sb.WriteString(role.name)
 		}
 	}
 
@@ -509,23 +526,7 @@ func (r *Role) toString() string {
 func (r *Role) getRoles() []string {
 	names := []string{}
 	for _, role := range r.roles {
-		_, roleName := getNameAndDomain(role.nameWithDomain)
-		names = append(names, roleName)
+		names = append(names, role.name)
 	}
 	return names
 }
-
-func getNameWithDomain(domain, name string) string {
-	if domain == "" {
-		return name
-	}
-	return fmt.Sprintf("%s%s%s", domain, defaultSeparator, name)
-}
-
-func getNameAndDomain(domainAndName string) (string, string) {
-	t := strings.Split(domainAndName, defaultSeparator)
-	if len(t) == 1 {
-		return defaultDomain, t[0]
-	}
-	return t[0], t[1]
-}
diff --git a/rbac/role_manager.go b/rbac/role_manager.go
index 008544c..362f71c 100644
--- a/rbac/role_manager.go
+++ b/rbac/role_manager.go
@@ -23,6 +23,7 @@ type RoleManager interface {
 	// AddLink adds the inheritance link between two roles. role: name1 and role: name2.
 	// domain is a prefix to the roles (can be used for other purposes).
 	AddLink(name1 string, name2 string, domain ...string) error
+	BuildRelationship(name1 string, name2 string, domain ...string) error
 	// DeleteLink deletes the inheritance link between two roles. role: name1 and role: name2.
 	// domain is a prefix to the roles (can be used for other purposes).
 	DeleteLink(name1 string, name2 string, domain ...string) error


Test patch:
diff --git a/model_test.go b/model_test.go
index e5a783e..798e9ce 100644
--- a/model_test.go
+++ b/model_test.go
@@ -329,6 +329,9 @@ func (rm *testCustomRoleManager) Clear() error { return nil }
 func (rm *testCustomRoleManager) AddLink(name1 string, name2 string, domain ...string) error {
 	return nil
 }
+func (rm *testCustomRoleManager) BuildRelationship(name1 string,name2 string, domain ...string) error {
+	return nil
+}
 func (rm *testCustomRoleManager) DeleteLink(name1 string, name2 string, domain ...string) error {
 	return nil
 }
diff --git a/rbac/default-role-manager/role_manager_test.go b/rbac/default-role-manager/role_manager_test.go
index b290bc6..68e12f4 100644
--- a/rbac/default-role-manager/role_manager_test.go
+++ b/rbac/default-role-manager/role_manager_test.go
@@ -281,12 +281,16 @@ func TestMatchingFuncOrder(t *testing.T) {
 
 	_ = rm.AddLink("g\\d+", "root")
 	_ = rm.AddLink("u1", "g1")
+	_ = rm.BuildRelationship("g\\d+","root")
+	_ = rm.BuildRelationship("u1","g1")
 	testRole(t, rm, "u1", "root", true)
 
 	_ = rm.Clear()
 
 	_ = rm.AddLink("u1", "g1")
 	_ = rm.AddLink("g\\d+", "root")
+	_ = rm.BuildRelationship("u1", "g1")
+	_ = rm.BuildRelationship("g\\d+", "root")
 	testRole(t, rm, "u1", "root", true)
 
 	_ = rm.Clear()
diff --git a/role_manager_b_test.go b/role_manager_b_test.go
index bff7949..6f53582 100644
--- a/role_manager_b_test.go
+++ b/role_manager_b_test.go
@@ -151,7 +151,7 @@ func BenchmarkHasLinkWithPatternLarge(b *testing.B) {
 	e, _ := NewEnforcer("examples/performance/rbac_with_pattern_large_scale_model.conf", "examples/performance/rbac_with_pattern_large_scale_policy.csv")
 	e.AddNamedMatchingFunc("g", "", util.KeyMatch4)
 	_ = e.BuildRoleLinks()
-	rm := e.rmMap["g"];
+	rm := e.rmMap["g"]
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
 		_, _ = rm.HasLink("staffUser1001", "staff001", "/orgs/1/sites/site001")
@@ -162,7 +162,7 @@ func BenchmarkHasLinkWithDomainPatternLarge(b *testing.B) {
 	e, _ := NewEnforcer("examples/performance/rbac_with_pattern_large_scale_model.conf", "examples/performance/rbac_with_pattern_large_scale_policy.csv")
 	e.AddNamedDomainMatchingFunc("g", "", util.KeyMatch4)
 	_ = e.BuildRoleLinks()
-	rm := e.rmMap["g"];
+	rm := e.rmMap["g"]
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
 		_, _ = rm.HasLink("staffUser1001", "staff001", "/orgs/1/sites/site001")
@@ -175,9 +175,9 @@ func BenchmarkHasLinkWithPatternAndDomainPatternLarge(b *testing.B) {
 	e.AddNamedMatchingFunc("g", "", util.KeyMatch4)
 	e.AddNamedDomainMatchingFunc("g", "", util.KeyMatch4)
 	_ = e.BuildRoleLinks()
-	rm := e.rmMap["g"];
+	rm := e.rmMap["g"]
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
 		_, _ = rm.HasLink("staffUser1001", "staff001", "/orgs/1/sites/site001")
 	}
-}
\ No newline at end of file
+}