Skip to content

Commit 9ce1641

Browse files
fix: align RoleManager behavior with documentation (#1404)
1 parent f2818d0 commit 9ce1641

File tree

2 files changed

+202
-34
lines changed

2 files changed

+202
-34
lines changed

rbac/default-role-manager/role_manager.go

Lines changed: 93 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ type Role struct {
3535
matchedBy *sync.Map
3636
linkConditionFuncMap *sync.Map
3737
linkConditionFuncParamsMap *sync.Map
38+
level int
3839
}
3940

4041
func newRole(name string) *Role {
@@ -46,17 +47,41 @@ func newRole(name string) *Role {
4647
r.matchedBy = &sync.Map{}
4748
r.linkConditionFuncMap = &sync.Map{}
4849
r.linkConditionFuncParamsMap = &sync.Map{}
50+
r.level = -1
4951
return &r
5052
}
5153

54+
func (r *Role) updateLevel() {
55+
maxLevel := 0
56+
r.users.Range(func(_, value interface{}) bool {
57+
if parentRole := value.(*Role); parentRole.level >= 0 && parentRole.level+1 > maxLevel {
58+
maxLevel = parentRole.level + 1
59+
}
60+
return true
61+
})
62+
if r.level == maxLevel {
63+
return
64+
}
65+
r.level = maxLevel
66+
r.roles.Range(func(_, value interface{}) bool {
67+
value.(*Role).updateLevel()
68+
return true
69+
})
70+
return
71+
}
72+
5273
func (r *Role) addRole(role *Role) {
5374
r.roles.Store(role.name, role)
5475
role.addUser(r)
76+
r.updateLevel()
77+
role.updateLevel()
5578
}
5679

5780
func (r *Role) removeRole(role *Role) {
5881
r.roles.Delete(role.name)
5982
role.removeUser(r)
83+
r.updateLevel()
84+
role.updateLevel()
6085
}
6186

6287
// should only be called inside addRole.
@@ -228,7 +253,9 @@ func (rm *RoleManagerImpl) rebuild() {
228253
roles := rm.allRoles
229254
_ = rm.Clear()
230255
rangeLinks(roles, func(name1, name2 string, domain ...string) bool {
231-
_ = rm.AddLink(name1, name2, domain...)
256+
if err := rm.AddLink(name1, name2, domain...); err != nil {
257+
rm.logger.LogError(err)
258+
}
232259
return true
233260
})
234261
}
@@ -329,9 +356,21 @@ func (rm *RoleManagerImpl) Clear() error {
329356
// AddLink adds the inheritance link between role: name1 and role: name2.
330357
// aka role: name1 inherits role: name2.
331358
func (rm *RoleManagerImpl) AddLink(name1 string, name2 string, domains ...string) error {
359+
if hasLink, err := rm.HasLink(name2, name1, domains...); err != nil {
360+
return err
361+
} else if hasLink {
362+
return fmt.Errorf("cannot add link from '%s' to '%s': would create a cycle", name1, name2)
363+
}
364+
332365
user, _ := rm.getRole(name1)
333366
role, _ := rm.getRole(name2)
334367
user.addRole(role)
368+
369+
if rm.maxHierarchyLevel > 0 && user.level > rm.maxHierarchyLevel-1 {
370+
user.removeRole(role)
371+
return fmt.Errorf("cannot add link from '%s' to '%s': role '%s' is at level %d, exceeding the maximum allowed hierarchy level of %d",
372+
name1, name2, name2, role.level, rm.maxHierarchyLevel)
373+
}
335374
return nil
336375
}
337376

@@ -392,7 +431,8 @@ func (rm *RoleManagerImpl) GetRoles(name string, domains ...string) ([]string, e
392431
if created {
393432
defer rm.removeRole(user.name)
394433
}
395-
return user.getRoles(), nil
434+
435+
return rm.filterHelper(user.getRoles()), nil
396436
}
397437

398438
// GetUsers gets the users of a role.
@@ -402,7 +442,37 @@ func (rm *RoleManagerImpl) GetUsers(name string, domain ...string) ([]string, er
402442
if created {
403443
defer rm.removeRole(role.name)
404444
}
405-
return role.getUsers(), nil
445+
446+
return rm.filterHelper(role.getUsers()), nil
447+
}
448+
449+
func (rm *RoleManagerImpl) filterHelper(names []string) []string {
450+
if rm.maxHierarchyLevel <= 0 {
451+
return names
452+
}
453+
454+
filteredNames := make([]string, 0, len(names))
455+
var toRemove []string
456+
457+
for _, name := range names {
458+
role, created := rm.getRole(name)
459+
if created {
460+
toRemove = append(toRemove, role.name)
461+
}
462+
if role.level <= rm.maxHierarchyLevel {
463+
filteredNames = append(filteredNames, name)
464+
}
465+
}
466+
467+
if toRemove == nil {
468+
return filteredNames
469+
}
470+
471+
for _, removeName := range toRemove {
472+
rm.removeRole(removeName)
473+
}
474+
475+
return filteredNames
406476
}
407477

408478
func (rm *RoleManagerImpl) toString() []string {
@@ -443,7 +513,9 @@ func (rm *RoleManagerImpl) GetAllDomains() ([]string, error) {
443513

444514
func (rm *RoleManagerImpl) copyFrom(other *RoleManagerImpl) {
445515
other.Range(func(name1, name2 string, domain ...string) bool {
446-
_ = rm.AddLink(name1, name2, domain...)
516+
if err := rm.AddLink(name1, name2, domain...); err != nil {
517+
rm.logger.LogError(err)
518+
}
447519
return true
448520
})
449521
}
@@ -605,12 +677,16 @@ func (dm *DomainManager) AddLink(name1 string, name2 string, domains ...string)
605677
return err
606678
}
607679
roleManager := dm.getRoleManager(domain, true) // create role manager if it does not exist
608-
_ = roleManager.AddLink(name1, name2, domains...)
680+
if err := roleManager.AddLink(name1, name2, domains...); err != nil {
681+
return err
682+
}
609683

610684
dm.rangeAffectedRoleManagers(domain, func(rm *RoleManagerImpl) {
611-
_ = rm.AddLink(name1, name2, domains...)
685+
if err == nil {
686+
err = rm.AddLink(name1, name2, domains...)
687+
}
612688
})
613-
return nil
689+
return err
614690
}
615691

616692
// DeleteLink deletes the inheritance link between role: name1 and role: name2.
@@ -733,7 +809,9 @@ type ConditionalRoleManager struct {
733809

734810
func (crm *ConditionalRoleManager) copyFrom(other *ConditionalRoleManager) {
735811
other.Range(func(name1, name2 string, domain ...string) bool {
736-
_ = crm.AddLink(name1, name2, domain...)
812+
if err := crm.AddLink(name1, name2, domain...); err != nil {
813+
crm.logger.LogError(err)
814+
}
737815
return true
738816
})
739817
}
@@ -966,12 +1044,16 @@ func (cdm *ConditionalDomainManager) AddLink(name1 string, name2 string, domains
9661044
return err
9671045
}
9681046
conditionalRoleManager := cdm.getConditionalRoleManager(domain, true) // create role manager if it does not exist
969-
_ = conditionalRoleManager.AddLink(name1, name2, domain)
1047+
if err := conditionalRoleManager.AddLink(name1, name2, domain); err != nil {
1048+
return err
1049+
}
9701050

9711051
cdm.rangeAffectedRoleManagers(domain, func(rm *RoleManagerImpl) {
972-
_ = rm.AddLink(name1, name2, domain)
1052+
if err == nil {
1053+
err = rm.AddLink(name1, name2, domain)
1054+
}
9731055
})
974-
return nil
1056+
return err
9751057
}
9761058

9771059
// DeleteLink deletes the inheritance link between role: name1 and role: name2.

rbac/default-role-manager/role_manager_test.go

Lines changed: 109 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -364,29 +364,47 @@ func TestTemporaryRoles(t *testing.T) {
364364
}
365365

366366
func TestMaxHierarchyLevel(t *testing.T) {
367-
rm := NewRoleManager(1)
368-
_ = rm.AddLink("level0", "level1")
369-
_ = rm.AddLink("level1", "level2")
370-
_ = rm.AddLink("level2", "level3")
371-
372-
testRole(t, rm, "level0", "level0", true)
373-
testRole(t, rm, "level0", "level1", true)
374-
testRole(t, rm, "level0", "level2", false)
375-
testRole(t, rm, "level0", "level3", false)
376-
testRole(t, rm, "level1", "level2", true)
377-
testRole(t, rm, "level1", "level3", false)
378-
379-
rm = NewRoleManager(2)
380-
_ = rm.AddLink("level0", "level1")
381-
_ = rm.AddLink("level1", "level2")
382-
_ = rm.AddLink("level2", "level3")
383-
384-
testRole(t, rm, "level0", "level0", true)
385-
testRole(t, rm, "level0", "level1", true)
386-
testRole(t, rm, "level0", "level2", true)
387-
testRole(t, rm, "level0", "level3", false)
388-
testRole(t, rm, "level1", "level2", true)
389-
testRole(t, rm, "level1", "level3", true)
367+
rm1 := NewRoleManager(1)
368+
_ = rm1.AddLink("level0", "level1")
369+
_ = rm1.AddLink("level1", "level2")
370+
_ = rm1.AddLink("level2", "level3")
371+
372+
testRole(t, rm1, "level0", "level0", true)
373+
testRole(t, rm1, "level0", "level1", true)
374+
testRole(t, rm1, "level0", "level2", false)
375+
testRole(t, rm1, "level0", "level3", false)
376+
testRole(t, rm1, "level1", "level2", false)
377+
testRole(t, rm1, "level1", "level3", false)
378+
379+
rm2 := NewRoleManager(2)
380+
_ = rm2.AddLink("level0", "level1")
381+
_ = rm2.AddLink("level1", "level2")
382+
_ = rm2.AddLink("level2", "level3")
383+
384+
testRole(t, rm2, "level0", "level0", true)
385+
testRole(t, rm2, "level0", "level1", true)
386+
testRole(t, rm2, "level0", "level2", true)
387+
testRole(t, rm2, "level0", "level3", false)
388+
testRole(t, rm2, "level1", "level2", true)
389+
testRole(t, rm2, "level1", "level3", false)
390+
testRole(t, rm2, "level2", "level3", false)
391+
392+
rm3 := NewRoleManager(3)
393+
_ = rm3.AddLink("level0", "level1")
394+
_ = rm3.AddLink("level1", "level2")
395+
_ = rm3.AddLink("level2", "level3")
396+
_ = rm3.AddLink("level3", "level4")
397+
_ = rm3.AddLink("level4", "level5")
398+
399+
testRole(t, rm3, "level0", "level0", true)
400+
testRole(t, rm3, "level0", "level1", true)
401+
testRole(t, rm3, "level0", "level2", true)
402+
testRole(t, rm3, "level0", "level3", true)
403+
testRole(t, rm3, "level1", "level2", true)
404+
testRole(t, rm3, "level1", "level3", true)
405+
testRole(t, rm3, "level2", "level3", true)
406+
testRole(t, rm3, "level2", "level4", false)
407+
testRole(t, rm3, "level2", "level5", false)
390408
}
391409

392410
// TestConcurrentHasLink tests concurrent HasLink calls for race conditions.
@@ -431,3 +449,71 @@ func TestConcurrentHasLink(t *testing.T) {
431449
inconsistencies, numGoroutines*numIterations)
432450
}
433451
}
452+
453+
func TestAddLink_Cycle(t *testing.T) {
454+
rm := NewRoleManager(10)
455+
_ = rm.AddLink("a", "b")
456+
_ = rm.AddLink("b", "c")
457+
err := rm.AddLink("c", "a")
458+
459+
if err == nil {
460+
t.Errorf("Expected an error when creating a cycle, but got nil")
461+
} else {
462+
t.Logf("Successfully detected cycle: %s", err)
463+
}
464+
}
465+
466+
func TestHierarchyLimitAndGetRoles(t *testing.T) {
467+
rm := NewRoleManager(3)
468+
469+
_ = rm.AddLink("g0", "g1")
470+
_ = rm.AddLink("g2", "g3")
471+
_ = rm.AddLink("g1", "g2")
472+
_ = rm.AddLink("g3", "g4")
473+
474+
expectedLevels := map[string]int{
475+
"g0": 0,
476+
"g1": 1,
477+
"g2": 2,
478+
"g3": 3,
479+
"g4": 0,
480+
}
481+
482+
rm.rmMap.Range(func(key, value interface{}) bool {
483+
domain := key.(string)
484+
rm := value.(*RoleManagerImpl)
485+
fmt.Printf("Domain: %s\n", domain)
486+
rm.allRoles.Range(func(k, v interface{}) bool {
487+
role := v.(*Role)
488+
if expectedLevels[role.name] != role.level {
489+
t.Errorf("Role %s level mismatch: got %d, want %d", role.name, role.level, expectedLevels[role.name])
490+
}
491+
//fmt.Printf(" Role name: %s, Level: %d\n", role.name, role.level)
492+
return true
493+
})
494+
return true
495+
})
496+
497+
_ = rm.DeleteLink("g1", "g2")
498+
499+
expectedLevelsAfterDelete := map[string]int{
500+
"g0": 0,
501+
"g1": 1,
502+
"g2": 0,
503+
"g3": 1,
504+
}
505+
506+
rm.rmMap.Range(func(key, value interface{}) bool {
507+
domain := key.(string)
508+
rm := value.(*RoleManagerImpl)
509+
fmt.Printf("After delete, Domain: %s\n", domain)
510+
rm.allRoles.Range(func(k, v interface{}) bool {
511+
role := v.(*Role)
512+
if expectedLevelsAfterDelete[role.name] != role.level {
513+
t.Errorf("[After delete] Role %s level mismatch: got %d, want %d", role.name, role.level, expectedLevelsAfterDelete[role.name])
514+
}
515+
return true
516+
})
517+
return true
518+
})
519+
}

0 commit comments

Comments
 (0)