Repository: casbin/casbin
Pull request #931 (2021-12-13 08:45:26)

fix: multi-level inheritance with different domains
Fix: https://github.com/casbin/casbin/issues/929

## How it works?

Refer to: https://github.com/casbin/casbin/issues/929#issuecomment-992190046

casbin-bot:  @tangyang9464 @closetool @sagilio please review
hsluoyz: @amit-chandak-unskript plz review
amit-chandak-unskript: @Abingcbc , one of the checks are failing.
@hsluoyz, the changes look good.

Patch:
diff --git a/rbac/default-role-manager/role_manager.go b/rbac/default-role-manager/role_manager.go
index eb59083..27c0b10 100644
--- a/rbac/default-role-manager/role_manager.go
+++ b/rbac/default-role-manager/role_manager.go
@@ -214,35 +214,52 @@ func (rm *RoleManager) HasLink(name1 string, name2 string, domains ...string) (b
 
 		matchedDomain := rm.getPatternDomain(domains[0])
 
-		for _, domainName := range matchedDomain {
+		roleQueue := []string{name1}
+		inherited := make(map[string]bool)
 
-			domainValue, _ := rm.allDomains.LoadOrStore(domainName, &Roles{})
-			domain := domainValue.(*Roles)
-
-			if rm.hasPattern {
-				flag := false
-				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
+		for len(roleQueue) != 0 {
+			role := roleQueue[0]
+			roleQueue = roleQueue[1:]
+			if _, ok := inherited[role]; ok {
+				continue
+			}
+			inherited[role] = true
+
+			for _, domainName := range matchedDomain {
+
+				domainValue, _ := rm.allDomains.LoadOrStore(domainName, &Roles{})
+				domain := domainValue.(*Roles)
+
+				if rm.hasPattern {
+					flag := false
+					domain.Range(func(key, value interface{}) bool {
+						if rm.match(role, key.(string)) && value.(*Role).hasRoleWithMatchingFunc(name2, rm.maxHierarchyLevel, rm.match) {
+							flag = true
+							return false
+						}
+						return true
+					})
+					if flag {
+						return true, nil
+					}
+				} else {
+					role1Value, ok := domain.Load(role)
+					if !ok {
+						continue
+					}
+					role1 := role1Value.(*Role)
+					result := role1.hasRole(name2, rm.maxHierarchyLevel)
+					if result {
+						return true, nil
+					} else {
+						for _, r := range role1.roles {
+							roleQueue = append(roleQueue, r.name)
+						}
 					}
-					return true
-				})
-				if flag {
-					return true, nil
-				}
-			} else {
-				role1Value, ok := domain.Load(name1)
-				if !ok {
-					continue
-				}
-				role1 := role1Value.(*Role)
-				result := role1.hasRole(name2, rm.maxHierarchyLevel)
-				if result {
-					return true, nil
 				}
 			}
 		}
+
 		return false, nil
 	default:
 		return false, errors.ERR_DOMAIN_PARAMETER


Test patch:
diff --git a/rbac/default-role-manager/role_manager_test.go b/rbac/default-role-manager/role_manager_test.go
index 1061208..4abc158 100644
--- a/rbac/default-role-manager/role_manager_test.go
+++ b/rbac/default-role-manager/role_manager_test.go
@@ -299,3 +299,14 @@ func TestMatchingFuncOrder(t *testing.T) {
 	testRole(t, rm, "u1", "g1", true)
 	testRole(t, rm, "u1", "g2", true)
 }
+
+func TestDomainMatchingFuncWithDifferentDomain(t *testing.T) {
+	rm := NewRoleManager(10)
+	rm.AddDomainMatchingFunc("keyMatch", util.KeyMatch)
+
+	_ = rm.AddLink("alice", "editor", "*")
+	_ = rm.AddLink("editor", "admin", "domain1")
+
+	testDomainRole(t, rm, "alice", "admin", "domain1", true)
+	testDomainRole(t, rm, "alice", "admin", "domain2", false)
+}