V1 rbac rework
This commit is contained in:
9
server/pkg/rbac/index_stats.go
Normal file
9
server/pkg/rbac/index_stats.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package rbac
|
||||
|
||||
type (
|
||||
statLogger interface {
|
||||
CacheHit(roles []uint64, resource string, op string)
|
||||
CacheMiss(roles []uint64, resource string, op string)
|
||||
CacheUpdate(*Rule)
|
||||
}
|
||||
)
|
||||
@@ -116,7 +116,7 @@ func statRoles(rr ...*Role) (stats map[roleKind]int) {
|
||||
}
|
||||
|
||||
// compare list of session roles (ids) with preloaded roles and calculate the final list
|
||||
func getContextRoles(s Session, res Resource, preloadedRoles ...*Role) (out partRoles) {
|
||||
func evalRoles(s Session, res Resource, preloadedRoles ...*Role) (out partRoles) {
|
||||
var (
|
||||
mm = slice.ToUint64BoolMap(s.Roles())
|
||||
scope = make(map[string]interface{})
|
||||
|
||||
@@ -97,7 +97,7 @@ func Test_getContextRoles(t *testing.T) {
|
||||
req = require.New(t)
|
||||
)
|
||||
|
||||
req.Equal(partitionRoles(tc.output...), getContextRoles(&session{rr: tc.sessionRoles}, tc.res, tc.preloadRoles...))
|
||||
req.Equal(partitionRoles(tc.output...), evalRoles(&session{rr: tc.sessionRoles}, tc.res, tc.preloadRoles...))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,29 +71,29 @@ func (index *ruleIndex) add(rules ...*Rule) {
|
||||
}
|
||||
}
|
||||
|
||||
func (index *ruleIndex) remove(rules ...*Rule) {
|
||||
if len(rules) == 0 {
|
||||
func (index *ruleIndex) remove(role uint64, resource string, ops ...string) {
|
||||
if _, ok := index.children[role]; !ok {
|
||||
return
|
||||
}
|
||||
|
||||
for _, r := range rules {
|
||||
if _, ok := index.children[r.RoleID]; !ok {
|
||||
continue
|
||||
}
|
||||
auxOps := ops
|
||||
|
||||
if !index.has(r) {
|
||||
continue
|
||||
if len(auxOps) == 0 {
|
||||
for op := range index.children[role].children {
|
||||
auxOps = append(auxOps, op)
|
||||
}
|
||||
}
|
||||
|
||||
bits := append([]string{r.Operation}, strings.Split(r.Resource, "/")...)
|
||||
index.removeRec(index.children[r.RoleID], bits)
|
||||
for _, op := range auxOps {
|
||||
bits := append([]string{op}, strings.Split(resource, "/")...)
|
||||
index.removeRec(index.children[role], bits)
|
||||
|
||||
// Finishing touch cleanup
|
||||
if len(index.children[r.RoleID].children[r.Operation].children) == 0 {
|
||||
delete(index.children[r.RoleID].children, r.Operation)
|
||||
if len(index.children[role].children[op].children) == 0 {
|
||||
delete(index.children[role].children, op)
|
||||
}
|
||||
if len(index.children[r.RoleID].children) == 0 {
|
||||
delete(index.children, r.RoleID)
|
||||
if len(index.children[role].children) == 0 {
|
||||
delete(index.children, role)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
28
server/pkg/rbac/stats.go
Normal file
28
server/pkg/rbac/stats.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package rbac
|
||||
|
||||
type (
|
||||
// @todo :)
|
||||
stats struct {
|
||||
cacheHitChan chan string
|
||||
cacheMissChan chan string
|
||||
|
||||
// cacheHits
|
||||
|
||||
}
|
||||
|
||||
noopStatLogger struct{}
|
||||
)
|
||||
|
||||
func Statser() {
|
||||
|
||||
}
|
||||
|
||||
func (l *stats) CacheHit([]uint64, string, string) {}
|
||||
func (l *stats) CacheMiss([]uint64, string, string) {}
|
||||
func (l *stats) CacheUpdate(in *Rule) {}
|
||||
|
||||
// Noop
|
||||
|
||||
func (l *noopStatLogger) CacheHit([]uint64, string, string) {}
|
||||
func (l *noopStatLogger) CacheMiss([]uint64, string, string) {}
|
||||
func (l *noopStatLogger) CacheUpdate(in *Rule) {}
|
||||
@@ -17,4 +17,8 @@ type (
|
||||
// @todo this isn't ok since we're referencing sys types
|
||||
SearchRoles(ctx context.Context, f types.RoleFilter) (types.RoleSet, types.RoleFilter, error)
|
||||
}
|
||||
|
||||
rbacRoleStore interface {
|
||||
SearchRoles(ctx context.Context, f types.RoleFilter) (types.RoleSet, types.RoleFilter, error)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -23,10 +23,6 @@ type (
|
||||
|
||||
// incChan sends instructions to the counter re. key K increment
|
||||
incChan chan K
|
||||
// sigEvict lets the counter notify the manager what key K should be evicted
|
||||
sigEvict chan K
|
||||
// @todo remove
|
||||
sigChan chan K
|
||||
|
||||
// decayInterval denotes in what interval the decay factor should apply
|
||||
decayInterval time.Duration
|
||||
@@ -152,6 +148,10 @@ func (svc *usageCounter[K]) worstPerformers(n int) (out []K) {
|
||||
// procNew notes a new key in the thing, defaults and stuff
|
||||
func (svc *usageCounter[K]) procNew(key K) {
|
||||
n := time.Now()
|
||||
if svc.index == nil {
|
||||
svc.index = make(map[K]counterItem[K])
|
||||
}
|
||||
|
||||
svc.index[key] = counterItem[K]{
|
||||
score: 1,
|
||||
added: n,
|
||||
@@ -182,7 +182,6 @@ func (svc *usageCounter[K]) watch(ctx context.Context) {
|
||||
}
|
||||
|
||||
decayT := time.NewTicker(svc.decayInterval)
|
||||
evictT := time.NewTicker(svc.cleanupInterval)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
@@ -190,12 +189,6 @@ func (svc *usageCounter[K]) watch(ctx context.Context) {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
|
||||
case <-evictT.C:
|
||||
evicted := svc.evict()
|
||||
for _, e := range evicted {
|
||||
svc.sigEvict <- e
|
||||
}
|
||||
|
||||
case <-decayT.C:
|
||||
svc.decay()
|
||||
|
||||
71
server/pkg/rbac/svc_index.go
Normal file
71
server/pkg/rbac/svc_index.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package rbac
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type (
|
||||
wrapperIndex struct {
|
||||
mux sync.RWMutex
|
||||
index *ruleIndex
|
||||
indexed map[string]bool
|
||||
}
|
||||
)
|
||||
|
||||
func (svc *wrapperIndex) add(role uint64, resource string, rules ...*Rule) {
|
||||
svc.mux.Lock()
|
||||
defer svc.mux.Unlock()
|
||||
|
||||
if svc.indexed == nil {
|
||||
svc.indexed = make(map[string]bool, 24)
|
||||
}
|
||||
|
||||
if svc.index == nil {
|
||||
svc.index = &ruleIndex{}
|
||||
}
|
||||
|
||||
svc.indexed[svc.mkkey(role, resource)] = true
|
||||
svc.index.add(rules...)
|
||||
}
|
||||
|
||||
func (svc *wrapperIndex) get(role uint64, op string, res string) (out []*Rule) {
|
||||
svc.mux.RLock()
|
||||
defer svc.mux.RUnlock()
|
||||
|
||||
if svc.index == nil {
|
||||
return
|
||||
}
|
||||
|
||||
return svc.index.get(role, op, res)
|
||||
}
|
||||
|
||||
func (svc *wrapperIndex) getIndexed() (out []string) {
|
||||
for k := range svc.indexed {
|
||||
out = append(out, k)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (svc *wrapperIndex) getSize() int {
|
||||
svc.mux.RLock()
|
||||
defer svc.mux.RUnlock()
|
||||
|
||||
return len(svc.indexed)
|
||||
}
|
||||
|
||||
func (svc *wrapperIndex) isIndexed(role uint64, resource string) (ok bool) {
|
||||
svc.mux.RLock()
|
||||
defer svc.mux.RUnlock()
|
||||
|
||||
if svc.indexed == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return svc.indexed[svc.mkkey(role, resource)]
|
||||
}
|
||||
|
||||
func (svc *wrapperIndex) mkkey(role uint64, resource string) string {
|
||||
return fmt.Sprintf("%d:%s", role, resource)
|
||||
}
|
||||
@@ -1,430 +0,0 @@
|
||||
package rbac
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/filter"
|
||||
"github.com/cortezaproject/corteza/server/system/types"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
type (
|
||||
WrapperConfig struct {
|
||||
InitialIndexedRoles []uint64
|
||||
MaxIndexSize int
|
||||
}
|
||||
|
||||
wrapperService struct {
|
||||
cfg WrapperConfig
|
||||
|
||||
store rbacRulesStore
|
||||
counter *usageCounter
|
||||
index *wrapperIndex
|
||||
roles []*Role
|
||||
}
|
||||
)
|
||||
|
||||
func dftWrapperCfg(base WrapperConfig) (out WrapperConfig) {
|
||||
out = base
|
||||
|
||||
if base.MaxIndexSize == 0 {
|
||||
out.MaxIndexSize = -1
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func Wrapper(ctx context.Context, store rbacRulesStore, cc WrapperConfig) (x *wrapperService, err error) {
|
||||
cc = dftWrapperCfg(cc)
|
||||
|
||||
uc := &usageCounter{
|
||||
incChan: make(chan uint64, 256),
|
||||
sigChan: make(chan counterEntry, 8),
|
||||
}
|
||||
|
||||
x = &wrapperService{
|
||||
cfg: cc,
|
||||
|
||||
store: store,
|
||||
counter: uc,
|
||||
}
|
||||
|
||||
x.roles, err = x.loadRoles(ctx, store)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
x.index, err = x.loadIndex(ctx, store, x.roles)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
uc.watch(ctx)
|
||||
x.watch(ctx)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (svc *wrapperService) Clear() {
|
||||
svc.store = nil
|
||||
svc.counter = nil
|
||||
svc.index = nil
|
||||
svc.roles = nil
|
||||
}
|
||||
|
||||
func (svc *wrapperService) Can(ses Session, op string, res Resource) (ok bool, err error) {
|
||||
ac, err := svc.Check(ses, op, res)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return ac == Allow, nil
|
||||
}
|
||||
|
||||
func (svc *wrapperService) Check(ses Session, op string, res Resource) (a Access, err error) {
|
||||
if hasWildcards(res.RbacResource()) {
|
||||
// prevent use of wildcard resources for checking permissions
|
||||
return Inherit, nil
|
||||
}
|
||||
|
||||
fRoles := getContextRoles(ses, res, svc.roles...)
|
||||
|
||||
return svc.check(ses.Context(), fRoles, op, res.RbacResource())
|
||||
}
|
||||
|
||||
func (svc *wrapperService) check(ctx context.Context, rolesByKind partRoles, op, res string) (a Access, err error) {
|
||||
if member(rolesByKind, AnonymousRole) && len(rolesByKind) > 1 {
|
||||
// Integrity check; when user is member of anonymous role
|
||||
// should not be member of any other type of role
|
||||
return resolve(nil, Deny, failedIntegrityCheck), nil
|
||||
}
|
||||
|
||||
if member(rolesByKind, BypassRole) {
|
||||
// if user has at least one bypass role, we allow access
|
||||
return resolve(nil, Allow, bypassRoleMembership), nil
|
||||
}
|
||||
|
||||
// if indexedRules.empty() {
|
||||
// // no rules to check
|
||||
// return resolve(nil, Inherit, noRules)
|
||||
// }
|
||||
|
||||
var (
|
||||
match *Rule
|
||||
allowed bool
|
||||
)
|
||||
|
||||
indexed, unindexed, err := svc.segmentRoles(ctx, rolesByKind)
|
||||
if err != nil {
|
||||
return Inherit, err
|
||||
}
|
||||
|
||||
//
|
||||
// if trace != nil {
|
||||
// // from this point on, there is a chance trace (if set)
|
||||
// // will contain some rules.
|
||||
// //
|
||||
// // Stable order needs to be ensured: there is no production
|
||||
// // code that relies on that but tests might fail and API
|
||||
// // response would be flaky.
|
||||
// defer sortTraceRules(trace)
|
||||
// }
|
||||
|
||||
st := evlState{
|
||||
op: op,
|
||||
res: res,
|
||||
|
||||
unindexedRoles: unindexed,
|
||||
indexedRoles: indexed,
|
||||
}
|
||||
|
||||
st.unindexedRules, err = svc.pullUnindexed(ctx, unindexed, op, res)
|
||||
if err != nil {
|
||||
return Inherit, err
|
||||
}
|
||||
|
||||
// Priority is important here. We want to have
|
||||
// stable RBAC check behaviour and ability
|
||||
// to override allow/deny depending on how niche the role (type) is:
|
||||
// - context (eg owners) are more niche than common
|
||||
// - rules for common roles are more important than authenticated and anonymous role types
|
||||
//
|
||||
// Note that bypass roles are intentionally ignored here; if user is member of
|
||||
// bypass role there is no need to check any other rule
|
||||
for _, kind := range []roleKind{ContextRole, CommonRole, AuthenticatedRole, AnonymousRole} {
|
||||
// not a member of any role of this kind
|
||||
if len(rolesByKind[kind]) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// reset allowed to false
|
||||
// for each role kind
|
||||
allowed = false
|
||||
|
||||
for r := range rolesByKind[kind] {
|
||||
match = svc.getMatching(st, kind, r)
|
||||
|
||||
// check all rules for each role the security-context
|
||||
if match == nil {
|
||||
// no rules match
|
||||
continue
|
||||
}
|
||||
|
||||
// if trace != nil {
|
||||
// // if trace is enabled, append
|
||||
// // each matching rule
|
||||
// trace.Rules = append(trace.Rules, match)
|
||||
// }
|
||||
|
||||
if match.Access == Deny {
|
||||
// if we stumble upon Deny we short-circuit the check
|
||||
return resolve(nil, Deny, ""), nil
|
||||
}
|
||||
|
||||
if match.Access == Allow {
|
||||
// allow rule found, we need to check rules on other roles
|
||||
// before we allow it
|
||||
allowed = true
|
||||
}
|
||||
}
|
||||
|
||||
if allowed {
|
||||
// at least one of the roles (per role type) in the security context
|
||||
// allows operation on a resource
|
||||
return resolve(nil, Allow, ""), nil
|
||||
}
|
||||
}
|
||||
|
||||
// No rule matched
|
||||
return resolve(nil, Inherit, noMatch), nil
|
||||
}
|
||||
|
||||
func (svc *wrapperService) segmentRoles(ctx context.Context, roles partRoles) (indexed, unindexed partRoles, err error) {
|
||||
unindexed = partRoles{}
|
||||
indexed = partRoles{}
|
||||
|
||||
unindexed[CommonRole] = make(map[uint64]bool)
|
||||
indexed[CommonRole] = make(map[uint64]bool)
|
||||
|
||||
for k, rg := range roles {
|
||||
for r := range rg {
|
||||
if svc.index.hasRole(r) {
|
||||
indexed[k][r] = true
|
||||
continue
|
||||
}
|
||||
|
||||
unindexed[k][r] = true
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type (
|
||||
evlState struct {
|
||||
unindexedRoles partRoles
|
||||
indexedRoles partRoles
|
||||
|
||||
unindexedRules [5]map[uint64][]*Rule
|
||||
|
||||
res string
|
||||
op string
|
||||
}
|
||||
)
|
||||
|
||||
func (svc *wrapperService) getMatching(st evlState, kind roleKind, role uint64) (rule *Rule) {
|
||||
var (
|
||||
aux []*Rule
|
||||
rules RuleSet
|
||||
)
|
||||
|
||||
// Indexed
|
||||
aux = svc.index.get(role, st.op, st.res)
|
||||
rules = append(rules, aux...)
|
||||
|
||||
// Unindexed
|
||||
aux = st.unindexedRules[kind][role]
|
||||
rules = append(rules, aux...)
|
||||
|
||||
set := RuleSet(rules)
|
||||
sort.Sort(set)
|
||||
|
||||
for _, s := range set {
|
||||
if s.Access == Inherit {
|
||||
continue
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *wrapperService) pullUnindexed(ctx context.Context, unindexed partRoles, op, res string) (out [5]map[uint64][]*Rule, err error) {
|
||||
resPerm := make([]string, 0, 8)
|
||||
resPerm = append(resPerm, res)
|
||||
|
||||
// Get all the resource permissions
|
||||
// @todo get permissions for parent resources; this will probs be some lookup table
|
||||
rr := strings.Split(res, "/")
|
||||
for i := len(rr) - 1; i >= 0; i-- {
|
||||
rr[i] = "*"
|
||||
resPerm = append(resPerm, strings.Join(rr, "/"))
|
||||
}
|
||||
|
||||
for rk, rr := range unindexed {
|
||||
for r := range rr {
|
||||
auxRr := make([]*Rule, 0, 4)
|
||||
auxRr, _, err = svc.store.SearchRbacRules(ctx, RuleFilter{
|
||||
RoleID: r,
|
||||
Resource: resPerm,
|
||||
Operation: op,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if out[rk] == nil {
|
||||
out[rk] = map[uint64][]*Rule{
|
||||
r: auxRr,
|
||||
}
|
||||
} else {
|
||||
out[rk][r] = auxRr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (svc *wrapperService) IndexRoleChange(ctx context.Context, roleID uint64) (err error) {
|
||||
aux, _, err := svc.store.SearchRbacRules(ctx, RuleFilter{
|
||||
RoleID: roleID,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// @todo cap this
|
||||
if len(svc.index.rules.children) > svc.cfg.MaxIndexSize {
|
||||
// @note probably remove a few extra just to avoid constantly doing this
|
||||
// @todo is this a good idea? Not sure if worth it since all of this is behind the scene anyways
|
||||
wp := svc.counter.worstPerformers(4)
|
||||
svc.index.remove(wp...)
|
||||
}
|
||||
|
||||
svc.index.add(aux...)
|
||||
return
|
||||
}
|
||||
|
||||
func (svc *wrapperService) watch(ctx context.Context) {
|
||||
t := time.NewTicker(time.Minute * 5)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
spew.Dump("ticking")
|
||||
|
||||
case change := <-svc.counter.sigChan:
|
||||
err := svc.IndexRoleChange(ctx, change.key)
|
||||
if err != nil {
|
||||
spew.Dump("wrapper watch change err", err)
|
||||
}
|
||||
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
|
||||
func makeKey(op, res string, role uint64) string {
|
||||
return fmt.Sprintf("%d:%s:%s", role, op, res)
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
// Boilerplate & state management stuff
|
||||
|
||||
func (svc *wrapperService) loadRoles(ctx context.Context, s rbacRulesStore) (out []*Role, err error) {
|
||||
auxRoles, _, err := s.SearchRoles(ctx, types.RoleFilter{
|
||||
Paging: filter.Paging{
|
||||
Limit: 0,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, ar := range auxRoles {
|
||||
out = append(out, &Role{
|
||||
id: ar.ID,
|
||||
handle: ar.Handle,
|
||||
kind: CommonRole,
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (svc *wrapperService) loadIndex(ctx context.Context, s rbacRulesStore, allRoles []*Role) (out *wrapperIndex, err error) {
|
||||
// @todo smarter way to figure out what/how many roles we want to load up
|
||||
roles := svc.getIndexRoles(allRoles)
|
||||
|
||||
rules := make(RuleSet, 0, 1024)
|
||||
var aux RuleSet
|
||||
for _, role := range roles {
|
||||
aux, _, err = s.SearchRbacRules(ctx, RuleFilter{
|
||||
RoleID: role.id,
|
||||
Limit: 0,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rules = append(rules, aux...)
|
||||
}
|
||||
|
||||
out = &wrapperIndex{
|
||||
rules: buildRuleIndex(rules),
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (svc *wrapperService) getIndexRoles(allRoles []*Role) (out []*Role) {
|
||||
// User-specified what we want to index; respect that to the t
|
||||
if len(svc.cfg.InitialIndexedRoles) > 0 {
|
||||
for _, r := range allRoles {
|
||||
for _, ir := range svc.cfg.InitialIndexedRoles {
|
||||
if r.id == ir {
|
||||
out = append(out, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Straight up limit
|
||||
// @todo add some counters to figure out which roles are most used from the start
|
||||
if svc.cfg.MaxIndexSize == -1 {
|
||||
return allRoles
|
||||
}
|
||||
|
||||
if svc.cfg.MaxIndexSize == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// @todo smarter way to figure out what/how many roles we want to load up
|
||||
return allRoles[:int(math.Min(float64(len(allRoles)), float64(svc.cfg.MaxIndexSize)))]
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package rbac
|
||||
|
||||
import "sync"
|
||||
|
||||
type (
|
||||
wrapperIndex struct {
|
||||
mux sync.RWMutex
|
||||
rules *ruleIndex
|
||||
}
|
||||
)
|
||||
|
||||
func (svc *wrapperIndex) get(role uint64, op string, res string) (out []*Rule) {
|
||||
svc.mux.RLock()
|
||||
defer svc.mux.RUnlock()
|
||||
|
||||
return svc.rules.get(role, op, res)
|
||||
}
|
||||
|
||||
func (svc *wrapperIndex) hasRole(role uint64) (ok bool) {
|
||||
svc.mux.RLock()
|
||||
defer svc.mux.RUnlock()
|
||||
|
||||
_, ok = svc.rules.children[role]
|
||||
return
|
||||
}
|
||||
|
||||
// @todo since it's like so, we might not need the trie to have deletable elements
|
||||
func (svc *wrapperIndex) remove(roles ...uint64) {
|
||||
svc.mux.Lock()
|
||||
defer svc.mux.Unlock()
|
||||
for _, r := range roles {
|
||||
delete(svc.rules.children, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *wrapperIndex) add(rules ...*Rule) {
|
||||
svc.mux.Lock()
|
||||
defer svc.mux.Unlock()
|
||||
svc.rules.add(rules...)
|
||||
}
|
||||
Reference in New Issue
Block a user