Files
upstream-casbin/rbac/default-role-manager/role_manager.go
upstream-mirror d8971e16fb
Some checks failed
Build / test (1.21) (push) Successful in 2m21s
Build / benchmark (1.21) (push) Failing after 2m28s
golangci-lint / golangci (push) Successful in 19m40s
snapshot of https://github.com/casbin/casbin.git (master, 2026-09-15; blobs > 131072B omitted)
2026-09-15 21:46:19 +05:00

1253 lines
37 KiB
Go

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package defaultrolemanager
import (
"errors"
"sync"
"github.com/casbin/casbin/v3/rbac"
"github.com/casbin/casbin/v3/util"
)
const defaultDomain string = ""
// Role represents the data structure for a role in RBAC.
type Role struct {
name string
roles *sync.Map
users *sync.Map
matched *sync.Map
matchedBy *sync.Map
linkConditionFuncMap *sync.Map
linkConditionFuncParamsMap *sync.Map
}
func newRole(name string) *Role {
r := Role{}
r.name = name
r.roles = &sync.Map{}
r.users = &sync.Map{}
r.matched = &sync.Map{}
r.matchedBy = &sync.Map{}
r.linkConditionFuncMap = &sync.Map{}
r.linkConditionFuncParamsMap = &sync.Map{}
return &r
}
func (r *Role) addRole(role *Role) {
r.roles.Store(role.name, role)
role.addUser(r)
}
func (r *Role) removeRole(role *Role) {
r.roles.Delete(role.name)
role.removeUser(r)
}
// should only be called inside addRole.
func (r *Role) addUser(user *Role) {
r.users.Store(user.name, user)
}
// should only be called inside removeRole.
func (r *Role) removeUser(user *Role) {
r.users.Delete(user.name)
}
func (r *Role) addMatch(role *Role) {
r.matched.Store(role.name, role)
role.matchedBy.Store(r.name, r)
}
func (r *Role) removeMatch(role *Role) {
r.matched.Delete(role.name)
role.matchedBy.Delete(r.name)
}
func (r *Role) removeMatches() {
r.matched.Range(func(key, value interface{}) bool {
r.removeMatch(value.(*Role))
return true
})
r.matchedBy.Range(func(key, value interface{}) bool {
value.(*Role).removeMatch(r)
return true
})
}
func (r *Role) rangeRoles(fn func(key, value interface{}) bool) {
r.roles.Range(fn)
r.roles.Range(func(key, value interface{}) bool {
role := value.(*Role)
role.matched.Range(fn)
return true
})
r.matchedBy.Range(func(key, value interface{}) bool {
role := value.(*Role)
role.roles.Range(fn)
return true
})
}
func (r *Role) rangeUsers(fn func(key, value interface{}) bool) {
r.users.Range(fn)
r.users.Range(func(key, value interface{}) bool {
role := value.(*Role)
role.matched.Range(fn)
return true
})
r.matchedBy.Range(func(key, value interface{}) bool {
role := value.(*Role)
role.users.Range(fn)
return true
})
}
func (r *Role) getRoles() []string {
var names []string
r.rangeRoles(func(key, value interface{}) bool {
names = append(names, key.(string))
return true
})
return util.RemoveDuplicateElement(names)
}
func (r *Role) getUsers() []string {
var names []string
r.rangeUsers(func(key, value interface{}) bool {
names = append(names, key.(string))
return true
})
return names
}
type linkConditionFuncKey struct {
roleName string
domainName string
}
func (r *Role) addLinkConditionFunc(role *Role, domain string, fn rbac.LinkConditionFunc) {
r.linkConditionFuncMap.Store(linkConditionFuncKey{role.name, domain}, fn)
}
func (r *Role) getLinkConditionFunc(role *Role, domain string) (rbac.LinkConditionFunc, bool) {
fn, ok := r.linkConditionFuncMap.Load(linkConditionFuncKey{role.name, domain})
if fn == nil {
return nil, ok
}
return fn.(rbac.LinkConditionFunc), ok
}
func (r *Role) setLinkConditionFuncParams(role *Role, domain string, params ...string) {
r.linkConditionFuncParamsMap.Store(linkConditionFuncKey{role.name, domain}, params)
}
func (r *Role) getLinkConditionFuncParams(role *Role, domain string) ([]string, bool) {
params, ok := r.linkConditionFuncParamsMap.Load(linkConditionFuncKey{role.name, domain})
if params == nil {
return nil, ok
}
return params.([]string), ok
}
// RoleManagerImpl provides a default implementation for the RoleManager interface.
type RoleManagerImpl struct {
allRoles *sync.Map
maxHierarchyLevel int
matchingFunc rbac.MatchingFunc
domainMatchingFunc rbac.MatchingFunc
matchingFuncCache *util.SyncLRUCache
mutex sync.Mutex
}
// NewRoleManagerImpl is the constructor for creating an instance of the
// default RoleManager implementation.
func NewRoleManagerImpl(maxHierarchyLevel int) *RoleManagerImpl {
rm := RoleManagerImpl{}
_ = rm.Clear() // init allRoles and matchingFuncCache
rm.maxHierarchyLevel = maxHierarchyLevel
return &rm
}
// use this constructor to avoid rebuild of AddMatchingFunc.
func newRoleManagerWithMatchingFunc(maxHierarchyLevel int, fn rbac.MatchingFunc) *RoleManagerImpl {
rm := NewRoleManagerImpl(maxHierarchyLevel)
rm.matchingFunc = fn
return rm
}
// rebuilds role cache.
func (rm *RoleManagerImpl) rebuild() {
roles := rm.allRoles
_ = rm.Clear()
rangeLinks(roles, func(name1, name2 string, domain ...string) bool {
_ = rm.AddLink(name1, name2, domain...)
return true
})
}
func (rm *RoleManagerImpl) Match(str string, pattern string) bool {
if str == pattern {
return true
}
if rm.matchingFunc != nil {
return rm.matchingFunc(str, pattern)
} else {
return false
}
}
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) {
fn(value.(*Role))
} else if !isPattern && name != name2 && rm.Match(name, name2) {
fn(value.(*Role))
}
return true
})
}
func (rm *RoleManagerImpl) load(name interface{}) (value *Role, ok bool) {
if r, ok := rm.allRoles.Load(name); ok {
return r.(*Role), true
}
return nil, false
}
// loads or creates a role.
func (rm *RoleManagerImpl) getRole(name string) (r *Role, created bool) {
var role *Role
var ok bool
if role, ok = rm.load(name); !ok {
role = newRole(name)
rm.allRoles.Store(name, role)
if rm.matchingFunc != nil {
rm.rangeMatchingRoles(name, false, func(r *Role) bool {
r.addMatch(role)
return true
})
rm.rangeMatchingRoles(name, true, func(r *Role) bool {
role.addMatch(r)
return true
})
}
}
return role, !ok
}
func loadAndDelete(m *sync.Map, name string) (value interface{}, loaded bool) {
value, loaded = m.Load(name)
if loaded {
m.Delete(name)
}
return value, loaded
}
func (rm *RoleManagerImpl) removeRole(name string) {
if role, ok := loadAndDelete(rm.allRoles, name); ok {
role.(*Role).removeMatches()
}
}
// AddMatchingFunc support use pattern in g.
func (rm *RoleManagerImpl) AddMatchingFunc(name string, fn rbac.MatchingFunc) {
rm.matchingFunc = fn
rm.rebuild()
}
// AddDomainMatchingFunc support use domain pattern in g.
func (rm *RoleManagerImpl) AddDomainMatchingFunc(name string, fn rbac.MatchingFunc) {
rm.domainMatchingFunc = fn
}
// Clear clears all stored data and resets the role manager to the initial state.
func (rm *RoleManagerImpl) Clear() error {
rm.matchingFuncCache = util.NewSyncLRUCache(100)
rm.allRoles = &sync.Map{}
return nil
}
// AddLink adds the inheritance link between role: name1 and role: name2.
// aka role: name1 inherits role: name2.
func (rm *RoleManagerImpl) AddLink(name1 string, name2 string, domains ...string) error {
user, _ := rm.getRole(name1)
role, _ := rm.getRole(name2)
user.addRole(role)
return nil
}
// DeleteLink deletes the inheritance link between role: name1 and role: name2.
// aka role: name1 does not inherit role: name2 any more.
func (rm *RoleManagerImpl) DeleteLink(name1 string, name2 string, domains ...string) error {
user, _ := rm.getRole(name1)
role, _ := rm.getRole(name2)
user.removeRole(role)
return nil
}
// 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)) {
return true, nil
}
// Lock to prevent race conditions between getRole and removeRole
rm.mutex.Lock()
defer rm.mutex.Unlock()
user, userCreated := rm.getRole(name1)
role, roleCreated := rm.getRole(name2)
if userCreated {
defer rm.removeRole(user.name)
}
if roleCreated {
defer rm.removeRole(role.name)
}
return rm.hasLinkHelper(role.name, map[string]*Role{user.name: user}, rm.maxHierarchyLevel), nil
}
func (rm *RoleManagerImpl) hasLinkHelper(targetName string, roles map[string]*Role, level int) bool {
if level < 0 || len(roles) == 0 {
return false
}
nextRoles := map[string]*Role{}
for _, role := range roles {
if targetName == role.name || (rm.matchingFunc != nil && rm.Match(role.name, targetName)) {
return true
}
role.rangeRoles(func(key, value interface{}) bool {
nextRoles[key.(string)] = value.(*Role)
return true
})
}
return rm.hasLinkHelper(targetName, nextRoles, level-1)
}
// GetRoles gets the roles that a user inherits.
func (rm *RoleManagerImpl) GetRoles(name string, domains ...string) ([]string, error) {
user, created := rm.getRole(name)
if created {
defer rm.removeRole(user.name)
}
return user.getRoles(), nil
}
// GetUsers gets the users of a role.
// domain is an unreferenced parameter here, may be used in other implementations.
func (rm *RoleManagerImpl) GetUsers(name string, domain ...string) ([]string, error) {
role, created := rm.getRole(name)
if created {
defer rm.removeRole(role.name)
}
return role.getUsers(), nil
}
// GetImplicitRoles gets the implicit roles that a user inherits, respecting maxHierarchyLevel.
func (rm *RoleManagerImpl) GetImplicitRoles(name string, domain ...string) ([]string, error) {
user, created := rm.getRole(name)
if created {
defer rm.removeRole(user.name)
}
var res []string
roleSet := make(map[string]bool)
roleSet[name] = true
roles := map[string]*Role{user.name: user}
return rm.getImplicitRolesHelper(roles, roleSet, res, 0), nil
}
// GetImplicitUsers gets the implicit users that inherits a role, respecting maxHierarchyLevel.
func (rm *RoleManagerImpl) GetImplicitUsers(name string, domain ...string) ([]string, error) {
role, created := rm.getRole(name)
if created {
defer rm.removeRole(role.name)
}
var res []string
userSet := make(map[string]bool)
userSet[name] = true
users := map[string]*Role{role.name: role}
return rm.getImplicitUsersHelper(users, userSet, res, 0), nil
}
// getImplicitRolesHelper is a helper function for GetImplicitRoles that respects maxHierarchyLevel.
func (rm *RoleManagerImpl) getImplicitRolesHelper(roles map[string]*Role, roleSet map[string]bool, res []string, level int) []string {
if level >= rm.maxHierarchyLevel || len(roles) == 0 {
return res
}
nextRoles := map[string]*Role{}
for _, role := range roles {
role.rangeRoles(func(key, value interface{}) bool {
roleName := key.(string)
if _, ok := roleSet[roleName]; !ok {
res = append(res, roleName)
roleSet[roleName] = true
nextRoles[roleName] = value.(*Role)
}
return true
})
}
return rm.getImplicitRolesHelper(nextRoles, roleSet, res, level+1)
}
// getImplicitUsersHelper is a helper function for GetImplicitUsers that respects maxHierarchyLevel.
func (rm *RoleManagerImpl) getImplicitUsersHelper(users map[string]*Role, userSet map[string]bool, res []string, level int) []string {
if level >= rm.maxHierarchyLevel || len(users) == 0 {
return res
}
nextUsers := map[string]*Role{}
for _, user := range users {
user.rangeUsers(func(key, value interface{}) bool {
userName := key.(string)
if _, ok := userSet[userName]; !ok {
res = append(res, userName)
userSet[userName] = true
nextUsers[userName] = value.(*Role)
}
return true
})
}
return rm.getImplicitUsersHelper(nextUsers, userSet, res, level+1)
}
// PrintRoles prints all the roles to log.
func (rm *RoleManagerImpl) PrintRoles() error {
// Logger has been removed - this is now a no-op
return nil
}
// GetDomains gets domains that a user has.
func (rm *RoleManagerImpl) GetDomains(name string) ([]string, error) {
domains := []string{defaultDomain}
return domains, nil
}
// GetAllDomains gets all domains.
func (rm *RoleManagerImpl) GetAllDomains() ([]string, error) {
domains := []string{defaultDomain}
return domains, nil
}
func (rm *RoleManagerImpl) copyFrom(other *RoleManagerImpl) {
other.Range(func(name1, name2 string, domain ...string) bool {
_ = rm.AddLink(name1, name2, domain...)
return true
})
}
func rangeLinks(users *sync.Map, fn func(name1, name2 string, domain ...string) bool) {
users.Range(func(_, value interface{}) bool {
user := value.(*Role)
user.roles.Range(func(key, _ interface{}) bool {
roleName := key.(string)
return fn(user.name, roleName, defaultDomain)
})
return true
})
}
func (rm *RoleManagerImpl) Range(fn func(name1, name2 string, domain ...string) bool) {
rangeLinks(rm.allRoles, fn)
}
// Deprecated: BuildRelationship is no longer required.
func (rm *RoleManagerImpl) BuildRelationship(name1 string, name2 string, domain ...string) error {
return nil
}
type DomainManager struct {
rmMap *sync.Map
maxHierarchyLevel int
matchingFunc rbac.MatchingFunc
domainMatchingFunc rbac.MatchingFunc
matchingFuncCache *util.SyncLRUCache
}
// NewDomainManager is the constructor for creating an instance of the
// default DomainManager implementation.
func NewDomainManager(maxHierarchyLevel int) *DomainManager {
dm := &DomainManager{}
_ = dm.Clear() // init rmMap and rmCache
dm.maxHierarchyLevel = maxHierarchyLevel
return dm
}
// AddMatchingFunc support use pattern in g.
func (dm *DomainManager) AddMatchingFunc(name string, fn rbac.MatchingFunc) {
dm.matchingFunc = fn
dm.rmMap.Range(func(key, value interface{}) bool {
value.(*RoleManagerImpl).AddMatchingFunc(name, fn)
return true
})
}
// AddDomainMatchingFunc support use domain pattern in g.
func (dm *DomainManager) AddDomainMatchingFunc(name string, fn rbac.MatchingFunc) {
dm.domainMatchingFunc = fn
dm.rmMap.Range(func(key, value interface{}) bool {
value.(*RoleManagerImpl).AddDomainMatchingFunc(name, fn)
return true
})
dm.rebuild()
}
// clears the map of RoleManagers.
func (dm *DomainManager) rebuild() {
rmMap := dm.rmMap
_ = dm.Clear()
rmMap.Range(func(key, value interface{}) bool {
domain := key.(string)
rm := value.(*RoleManagerImpl)
rm.Range(func(name1, name2 string, _ ...string) bool {
_ = dm.AddLink(name1, name2, domain)
return true
})
return true
})
}
// Clear clears all stored data and resets the role manager to the initial state.
func (dm *DomainManager) Clear() error {
dm.rmMap = &sync.Map{}
dm.matchingFuncCache = util.NewSyncLRUCache(100)
return nil
}
func (dm *DomainManager) getDomain(domains ...string) (domain string, err error) {
switch len(domains) {
case 0:
return defaultDomain, nil
default:
return domains[0], nil
}
}
func (dm *DomainManager) Match(str string, pattern string) bool {
if str == pattern {
return true
}
if dm.domainMatchingFunc != nil {
return dm.domainMatchingFunc(str, pattern)
} else {
return false
}
}
func (dm *DomainManager) rangeAffectedRoleManagers(domain string, fn func(rm *RoleManagerImpl)) {
if dm.domainMatchingFunc != nil {
dm.rmMap.Range(func(key, value interface{}) bool {
domain2 := key.(string)
if domain != domain2 && dm.Match(domain2, domain) {
fn(value.(*RoleManagerImpl))
}
return true
})
}
}
func (dm *DomainManager) load(name interface{}) (value *RoleManagerImpl, ok bool) {
if r, ok := dm.rmMap.Load(name); ok {
return r.(*RoleManagerImpl), true
}
return nil, false
}
// load or create a RoleManager instance of domain.
func (dm *DomainManager) getRoleManager(domain string, store bool) *RoleManagerImpl {
var rm *RoleManagerImpl
var ok bool
if rm, ok = dm.load(domain); !ok {
rm = newRoleManagerWithMatchingFunc(dm.maxHierarchyLevel, dm.matchingFunc)
if store {
dm.rmMap.Store(domain, rm)
}
if dm.domainMatchingFunc != nil {
dm.rmMap.Range(func(key, value interface{}) bool {
domain2 := key.(string)
rm2 := value.(*RoleManagerImpl)
if domain != domain2 && dm.Match(domain, domain2) {
rm.copyFrom(rm2)
}
return true
})
}
}
return rm
}
// AddLink adds the inheritance link between role: name1 and role: name2.
// aka role: name1 inherits role: name2.
func (dm *DomainManager) AddLink(name1 string, name2 string, domains ...string) error {
domain, err := dm.getDomain(domains...)
if err != nil {
return err
}
roleManager := dm.getRoleManager(domain, true) // create role manager if it does not exist
_ = roleManager.AddLink(name1, name2, domains...)
dm.rangeAffectedRoleManagers(domain, func(rm *RoleManagerImpl) {
_ = rm.AddLink(name1, name2, domains...)
})
return nil
}
// DeleteLink deletes the inheritance link between role: name1 and role: name2.
// aka role: name1 does not inherit role: name2 any more.
func (dm *DomainManager) DeleteLink(name1 string, name2 string, domains ...string) error {
domain, err := dm.getDomain(domains...)
if err != nil {
return err
}
roleManager := dm.getRoleManager(domain, true) // create role manager if it does not exist
_ = roleManager.DeleteLink(name1, name2, domains...)
dm.rangeAffectedRoleManagers(domain, func(rm *RoleManagerImpl) {
_ = rm.DeleteLink(name1, name2, domains...)
})
return nil
}
// HasLink determines whether role: name1 inherits role: name2.
func (dm *DomainManager) HasLink(name1 string, name2 string, domains ...string) (bool, error) {
domain, err := dm.getDomain(domains...)
if err != nil {
return false, err
}
rm := dm.getRoleManager(domain, false)
return rm.HasLink(name1, name2, domains...)
}
// GetRoles gets the roles that a subject inherits.
func (dm *DomainManager) GetRoles(name string, domains ...string) ([]string, error) {
domain, err := dm.getDomain(domains...)
if err != nil {
return nil, err
}
rm := dm.getRoleManager(domain, false)
return rm.GetRoles(name, domains...)
}
// GetUsers gets the users of a role.
func (dm *DomainManager) GetUsers(name string, domains ...string) ([]string, error) {
domain, err := dm.getDomain(domains...)
if err != nil {
return nil, err
}
rm := dm.getRoleManager(domain, false)
return rm.GetUsers(name, domains...)
}
// GetImplicitRoles gets the implicit roles that a subject inherits, respecting maxHierarchyLevel.
func (dm *DomainManager) GetImplicitRoles(name string, domains ...string) ([]string, error) {
domain, err := dm.getDomain(domains...)
if err != nil {
return nil, err
}
rm := dm.getRoleManager(domain, false)
return rm.GetImplicitRoles(name, domains...)
}
// GetImplicitUsers gets the implicit users that inherits a role, respecting maxHierarchyLevel.
func (dm *DomainManager) GetImplicitUsers(name string, domains ...string) ([]string, error) {
domain, err := dm.getDomain(domains...)
if err != nil {
return nil, err
}
rm := dm.getRoleManager(domain, false)
return rm.GetImplicitUsers(name, domains...)
}
// PrintRoles prints all the roles to log.
func (dm *DomainManager) PrintRoles() error {
// Logger has been removed - this is now a no-op
return nil
}
// GetDomains gets domains that a user has.
func (dm *DomainManager) GetDomains(name string) ([]string, error) {
var domains []string
dm.rmMap.Range(func(key, value interface{}) bool {
domain := key.(string)
rm := value.(*RoleManagerImpl)
role, created := rm.getRole(name)
if created {
defer rm.removeRole(role.name)
}
if len(role.getUsers()) > 0 || len(role.getRoles()) > 0 {
domains = append(domains, domain)
}
return true
})
return domains, nil
}
// GetAllDomains gets all domains.
func (dm *DomainManager) GetAllDomains() ([]string, error) {
var domains []string
dm.rmMap.Range(func(key, value interface{}) bool {
domains = append(domains, key.(string))
return true
})
return domains, nil
}
// Deprecated: BuildRelationship is no longer required.
func (dm *DomainManager) BuildRelationship(name1 string, name2 string, domain ...string) error {
return nil
}
// DeleteDomain deletes the specified domain from DomainManager.
func (dm *DomainManager) DeleteDomain(domain string) error {
dm.rmMap.Delete(domain)
return nil
}
type RoleManager struct {
*DomainManager
}
func NewRoleManager(maxHierarchyLevel int) *RoleManager {
rm := &RoleManager{}
rm.DomainManager = NewDomainManager(maxHierarchyLevel)
return rm
}
// DeleteDomain does nothing for RoleManagerImpl (no domain concept).
func (rm *RoleManagerImpl) DeleteDomain(domain string) error {
return errors.New("DeleteDomain is not supported by RoleManagerImpl (no domain concept)")
}
type ConditionalRoleManager struct {
RoleManagerImpl
}
func (crm *ConditionalRoleManager) copyFrom(other *ConditionalRoleManager) {
other.Range(func(name1, name2 string, domain ...string) bool {
_ = crm.AddLink(name1, name2, domain...)
return true
})
}
// use this constructor to avoid rebuild of AddMatchingFunc.
func newConditionalRoleManagerWithMatchingFunc(maxHierarchyLevel int, fn rbac.MatchingFunc) *ConditionalRoleManager {
rm := NewConditionalRoleManager(maxHierarchyLevel)
rm.matchingFunc = fn
return rm
}
// NewConditionalRoleManager is the constructor for creating an instance of the
// ConditionalRoleManager implementation.
func NewConditionalRoleManager(maxHierarchyLevel int) *ConditionalRoleManager {
rm := ConditionalRoleManager{}
_ = rm.Clear() // init allRoles and matchingFuncCache
rm.maxHierarchyLevel = maxHierarchyLevel
return &rm
}
// HasLink determines whether role: name1 inherits role: name2.
func (crm *ConditionalRoleManager) HasLink(name1 string, name2 string, domains ...string) (bool, error) {
if name1 == name2 || (crm.matchingFunc != nil && crm.Match(name1, name2)) {
return true, nil
}
// Lock to prevent race conditions between getRole and removeRole
crm.mutex.Lock()
defer crm.mutex.Unlock()
user, userCreated := crm.getRole(name1)
role, roleCreated := crm.getRole(name2)
if userCreated {
defer crm.removeRole(user.name)
}
if roleCreated {
defer crm.removeRole(role.name)
}
return crm.hasLinkHelper(role.name, map[string]*Role{user.name: user}, crm.maxHierarchyLevel, domains...), nil
}
// hasLinkHelper use the Breadth First Search algorithm to traverse the Role tree
// Judging whether the user has a role (has link) is to judge whether the role node can be reached from the user node.
func (crm *ConditionalRoleManager) hasLinkHelper(targetName string, roles map[string]*Role, level int, domains ...string) bool {
if level < 0 || len(roles) == 0 {
return false
}
nextRoles := map[string]*Role{}
for _, role := range roles {
if targetName == role.name || (crm.matchingFunc != nil && crm.Match(role.name, targetName)) {
return true
}
role.rangeRoles(func(key, value interface{}) bool {
nextRole := value.(*Role)
return crm.getNextRoles(role, nextRole, domains, nextRoles)
})
}
return crm.hasLinkHelper(targetName, nextRoles, level-1, domains...)
}
func (crm *ConditionalRoleManager) getNextRoles(currentRole, nextRole *Role, domains []string, nextRoles map[string]*Role) bool {
passLinkConditionFunc, err := crm.checkLinkCondition(currentRole.name, nextRole.name, domains)
if err != nil {
// Logger has been removed - error is ignored
return false
}
if passLinkConditionFunc {
nextRoles[nextRole.name] = nextRole
}
return true
}
func (crm *ConditionalRoleManager) checkLinkCondition(name1, name2 string, domain []string) (bool, error) {
passLinkConditionFunc := true
var err error
if len(domain) == 0 {
if linkConditionFunc, existLinkCondition := crm.GetLinkConditionFunc(name1, name2); existLinkCondition {
params, _ := crm.GetLinkConditionFuncParams(name1, name2)
passLinkConditionFunc, err = linkConditionFunc(params...)
}
} else {
if linkConditionFunc, existLinkCondition := crm.GetDomainLinkConditionFunc(name1, name2, domain[0]); existLinkCondition {
params, _ := crm.GetLinkConditionFuncParams(name1, name2, domain[0])
passLinkConditionFunc, err = linkConditionFunc(params...)
}
}
return passLinkConditionFunc, err
}
func (crm *ConditionalRoleManager) GetRoles(name string, domains ...string) ([]string, error) {
user, created := crm.getRole(name)
if created {
defer crm.removeRole(user.name)
}
var roles []string
user.rangeRoles(func(key, value interface{}) bool {
roleName := key.(string)
passLinkConditionFunc, err := crm.checkLinkCondition(name, roleName, domains)
if err != nil {
// Logger has been removed - error is ignored
return true
}
if passLinkConditionFunc {
roles = append(roles, roleName)
}
return true
})
return roles, nil
}
func (crm *ConditionalRoleManager) GetUsers(name string, domains ...string) ([]string, error) {
role, created := crm.getRole(name)
if created {
defer crm.removeRole(name)
}
var users []string
role.rangeUsers(func(key, value interface{}) bool {
userName := key.(string)
passLinkConditionFunc, err := crm.checkLinkCondition(userName, name, domains)
if err != nil {
// Logger has been removed - error is ignored
return true
}
if passLinkConditionFunc {
users = append(users, userName)
}
return true
})
return users, nil
}
// GetImplicitRoles gets the implicit roles that a user inherits, respecting maxHierarchyLevel and link conditions.
func (crm *ConditionalRoleManager) GetImplicitRoles(name string, domain ...string) ([]string, error) {
user, created := crm.getRole(name)
if created {
defer crm.removeRole(user.name)
}
var res []string
roleSet := make(map[string]bool)
roleSet[name] = true
roles := map[string]*Role{user.name: user}
return crm.getImplicitRolesHelper(roles, roleSet, res, 0, domain), nil
}
// GetImplicitUsers gets the implicit users that inherits a role, respecting maxHierarchyLevel and link conditions.
func (crm *ConditionalRoleManager) GetImplicitUsers(name string, domain ...string) ([]string, error) {
role, created := crm.getRole(name)
if created {
defer crm.removeRole(role.name)
}
var res []string
userSet := make(map[string]bool)
userSet[name] = true
users := map[string]*Role{role.name: role}
return crm.getImplicitUsersHelper(users, userSet, res, 0, domain), nil
}
// getImplicitRolesHelper is a helper function for GetImplicitRoles that respects maxHierarchyLevel and link conditions.
func (crm *ConditionalRoleManager) getImplicitRolesHelper(roles map[string]*Role, roleSet map[string]bool, res []string, level int, domains []string) []string {
if level >= crm.maxHierarchyLevel || len(roles) == 0 {
return res
}
nextRoles := map[string]*Role{}
for _, role := range roles {
role.rangeRoles(func(key, value interface{}) bool {
roleName := key.(string)
if _, ok := roleSet[roleName]; !ok {
passLinkConditionFunc, err := crm.checkLinkCondition(role.name, roleName, domains)
if err != nil {
// Logger has been removed - error is ignored
return true
}
if passLinkConditionFunc {
res = append(res, roleName)
roleSet[roleName] = true
nextRoles[roleName] = value.(*Role)
}
}
return true
})
}
return crm.getImplicitRolesHelper(nextRoles, roleSet, res, level+1, domains)
}
// getImplicitUsersHelper is a helper function for GetImplicitUsers that respects maxHierarchyLevel and link conditions.
func (crm *ConditionalRoleManager) getImplicitUsersHelper(users map[string]*Role, userSet map[string]bool, res []string, level int, domains []string) []string {
if level >= crm.maxHierarchyLevel || len(users) == 0 {
return res
}
nextUsers := map[string]*Role{}
for _, user := range users {
user.rangeUsers(func(key, value interface{}) bool {
userName := key.(string)
if _, ok := userSet[userName]; !ok {
passLinkConditionFunc, err := crm.checkLinkCondition(userName, user.name, domains)
if err != nil {
// Logger has been removed - error is ignored
return true
}
if passLinkConditionFunc {
res = append(res, userName)
userSet[userName] = true
nextUsers[userName] = value.(*Role)
}
}
return true
})
}
return crm.getImplicitUsersHelper(nextUsers, userSet, res, level+1, domains)
}
// GetLinkConditionFunc get LinkConditionFunc based on userName, roleName.
func (crm *ConditionalRoleManager) GetLinkConditionFunc(userName, roleName string) (rbac.LinkConditionFunc, bool) {
return crm.GetDomainLinkConditionFunc(userName, roleName, defaultDomain)
}
// GetDomainLinkConditionFunc get LinkConditionFunc based on userName, roleName, domain.
func (crm *ConditionalRoleManager) GetDomainLinkConditionFunc(userName, roleName, domain string) (rbac.LinkConditionFunc, bool) {
user, userCreated := crm.getRole(userName)
role, roleCreated := crm.getRole(roleName)
if userCreated {
crm.removeRole(user.name)
return nil, false
}
if roleCreated {
crm.removeRole(role.name)
return nil, false
}
return user.getLinkConditionFunc(role, domain)
}
// GetLinkConditionFuncParams gets parameters of LinkConditionFunc based on userName, roleName, domain.
func (crm *ConditionalRoleManager) GetLinkConditionFuncParams(userName, roleName string, domain ...string) ([]string, bool) {
user, userCreated := crm.getRole(userName)
role, roleCreated := crm.getRole(roleName)
if userCreated {
crm.removeRole(user.name)
return nil, false
}
if roleCreated {
crm.removeRole(role.name)
return nil, false
}
domainName := defaultDomain
if len(domain) != 0 {
domainName = domain[0]
}
if params, ok := user.getLinkConditionFuncParams(role, domainName); ok {
return params, true
} else {
return nil, false
}
}
// AddLinkConditionFunc is based on userName, roleName, add LinkConditionFunc.
func (crm *ConditionalRoleManager) AddLinkConditionFunc(userName, roleName string, fn rbac.LinkConditionFunc) {
crm.AddDomainLinkConditionFunc(userName, roleName, defaultDomain, fn)
}
// AddDomainLinkConditionFunc is based on userName, roleName, domain, add LinkConditionFunc.
func (crm *ConditionalRoleManager) AddDomainLinkConditionFunc(userName, roleName, domain string, fn rbac.LinkConditionFunc) {
user, _ := crm.getRole(userName)
role, _ := crm.getRole(roleName)
user.addLinkConditionFunc(role, domain, fn)
}
// SetLinkConditionFuncParams sets parameters of LinkConditionFunc based on userName, roleName, domain.
func (crm *ConditionalRoleManager) SetLinkConditionFuncParams(userName, roleName string, params ...string) {
crm.SetDomainLinkConditionFuncParams(userName, roleName, defaultDomain, params...)
}
// SetDomainLinkConditionFuncParams sets parameters of LinkConditionFunc based on userName, roleName, domain.
func (crm *ConditionalRoleManager) SetDomainLinkConditionFuncParams(userName, roleName, domain string, params ...string) {
user, _ := crm.getRole(userName)
role, _ := crm.getRole(roleName)
user.setLinkConditionFuncParams(role, domain, params...)
}
type ConditionalDomainManager struct {
ConditionalRoleManager
DomainManager
}
// NewConditionalDomainManager is the constructor for creating an instance of the
// ConditionalDomainManager implementation.
func NewConditionalDomainManager(maxHierarchyLevel int) *ConditionalDomainManager {
rm := ConditionalDomainManager{}
_ = rm.Clear() // init allRoles and matchingFuncCache
rm.maxHierarchyLevel = maxHierarchyLevel
return &rm
}
func (cdm *ConditionalDomainManager) load(name interface{}) (value *ConditionalRoleManager, ok bool) {
if r, ok := cdm.rmMap.Load(name); ok {
return r.(*ConditionalRoleManager), true
}
return nil, false
}
// load or create a ConditionalRoleManager instance of domain.
func (cdm *ConditionalDomainManager) getConditionalRoleManager(domain string, store bool) *ConditionalRoleManager {
var rm *ConditionalRoleManager
var ok bool
if rm, ok = cdm.load(domain); !ok {
rm = newConditionalRoleManagerWithMatchingFunc(cdm.maxHierarchyLevel, cdm.matchingFunc)
if store {
cdm.rmMap.Store(domain, rm)
}
if cdm.domainMatchingFunc != nil {
cdm.rmMap.Range(func(key, value interface{}) bool {
domain2 := key.(string)
rm2 := value.(*ConditionalRoleManager)
if domain != domain2 && cdm.Match(domain, domain2) {
rm.copyFrom(rm2)
}
return true
})
}
}
return rm
}
// HasLink determines whether role: name1 inherits role: name2.
func (cdm *ConditionalDomainManager) HasLink(name1 string, name2 string, domains ...string) (bool, error) {
domain, err := cdm.getDomain(domains...)
if err != nil {
return false, err
}
rm := cdm.getConditionalRoleManager(domain, false)
return rm.HasLink(name1, name2, domains...)
}
func (cdm *ConditionalDomainManager) GetRoles(name string, domains ...string) ([]string, error) {
domain, err := cdm.getDomain(domains...)
if err != nil {
return nil, err
}
crm := cdm.getConditionalRoleManager(domain, false)
return crm.GetRoles(name, domains...)
}
func (cdm *ConditionalDomainManager) GetUsers(name string, domains ...string) ([]string, error) {
domain, err := cdm.getDomain(domains...)
if err != nil {
return nil, err
}
crm := cdm.getConditionalRoleManager(domain, false)
return crm.GetUsers(name, domains...)
}
func (cdm *ConditionalDomainManager) GetImplicitRoles(name string, domains ...string) ([]string, error) {
domain, err := cdm.getDomain(domains...)
if err != nil {
return nil, err
}
crm := cdm.getConditionalRoleManager(domain, false)
return crm.GetImplicitRoles(name, domains...)
}
func (cdm *ConditionalDomainManager) GetImplicitUsers(name string, domains ...string) ([]string, error) {
domain, err := cdm.getDomain(domains...)
if err != nil {
return nil, err
}
crm := cdm.getConditionalRoleManager(domain, false)
return crm.GetImplicitUsers(name, domains...)
}
// AddLink adds the inheritance link between role: name1 and role: name2.
// aka role: name1 inherits role: name2.
func (cdm *ConditionalDomainManager) AddLink(name1 string, name2 string, domains ...string) error {
domain, err := cdm.getDomain(domains...)
if err != nil {
return err
}
conditionalRoleManager := cdm.getConditionalRoleManager(domain, true) // create role manager if it does not exist
_ = conditionalRoleManager.AddLink(name1, name2, domain)
cdm.rangeAffectedRoleManagers(domain, func(rm *RoleManagerImpl) {
_ = rm.AddLink(name1, name2, domain)
})
return nil
}
// DeleteLink deletes the inheritance link between role: name1 and role: name2.
// aka role: name1 does not inherit role: name2 any more.
func (cdm *ConditionalDomainManager) DeleteLink(name1 string, name2 string, domains ...string) error {
domain, err := cdm.getDomain(domains...)
if err != nil {
return err
}
conditionalRoleManager := cdm.getConditionalRoleManager(domain, true) // create role manager if it does not exist
_ = conditionalRoleManager.DeleteLink(name1, name2, domain)
cdm.rangeAffectedRoleManagers(domain, func(rm *RoleManagerImpl) {
_ = rm.DeleteLink(name1, name2, domain)
})
return nil
}
// AddLinkConditionFunc is based on userName, roleName, add LinkConditionFunc.
func (cdm *ConditionalDomainManager) AddLinkConditionFunc(userName, roleName string, fn rbac.LinkConditionFunc) {
cdm.rmMap.Range(func(key, value interface{}) bool {
value.(*ConditionalRoleManager).AddLinkConditionFunc(userName, roleName, fn)
return true
})
}
// AddDomainLinkConditionFunc is based on userName, roleName, domain, add LinkConditionFunc.
func (cdm *ConditionalDomainManager) AddDomainLinkConditionFunc(userName, roleName, domain string, fn rbac.LinkConditionFunc) {
cdm.rmMap.Range(func(key, value interface{}) bool {
value.(*ConditionalRoleManager).AddDomainLinkConditionFunc(userName, roleName, domain, fn)
return true
})
}
// SetLinkConditionFuncParams sets parameters of LinkConditionFunc based on userName, roleName.
func (cdm *ConditionalDomainManager) SetLinkConditionFuncParams(userName, roleName string, params ...string) {
cdm.rmMap.Range(func(key, value interface{}) bool {
value.(*ConditionalRoleManager).SetLinkConditionFuncParams(userName, roleName, params...)
return true
})
}
// SetDomainLinkConditionFuncParams sets parameters of LinkConditionFunc based on userName, roleName, domain.
func (cdm *ConditionalDomainManager) SetDomainLinkConditionFuncParams(userName, roleName, domain string, params ...string) {
cdm.rmMap.Range(func(key, value interface{}) bool {
value.(*ConditionalRoleManager).SetDomainLinkConditionFuncParams(userName, roleName, domain, params...)
return true
})
}
// AddDomainMatchingFunc support use domain pattern in g.
func (cdm *ConditionalDomainManager) AddDomainMatchingFunc(name string, fn rbac.MatchingFunc) {
cdm.domainMatchingFunc = fn
cdm.rmMap.Range(func(key, value interface{}) bool {
value.(*ConditionalRoleManager).AddDomainMatchingFunc(name, fn)
return true
})
cdm.rebuild()
}
// rebuild clears the map of ConditionalRoleManagers.
func (cdm *ConditionalDomainManager) rebuild() {
rmMap := cdm.rmMap
_ = cdm.Clear()
rmMap.Range(func(key, value interface{}) bool {
domain := key.(string)
crm := value.(*ConditionalRoleManager)
crm.Range(func(name1, name2 string, _ ...string) bool {
_ = cdm.AddLink(name1, name2, domain)
return true
})
return true
})
}