Files
upstream-corteza/server/pkg/rbac/rule_index.go
upstream-mirror 7e385fc126
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
2026-09-18 01:22:23 +05:00

166 lines
3.7 KiB
Go

package rbac
import (
"sort"
"strings"
)
type (
// ruleIndex indexes all given RBAC rules to optimize lookup times
//
// The algorithm is based on the standard trie structure.
// The max depth for a check operation is M+2 where M is the number of
// RBAC resource path elements + component + some meta.
ruleIndex struct {
children map[uint64]*ruleIndexNode
}
ruleIndexNode struct {
children map[string]*ruleIndexNode
isLeaf bool
access Access
rule *Rule
}
)
// buildRuleIndex indexes the given rules for optimal lookups
//
// The build isn't that cleanned up but the lookup is good, I promise <3
func buildRuleIndex(rules []*Rule) (index *ruleIndex) {
index = &ruleIndex{
children: make(map[uint64]*ruleIndexNode, 8),
}
for _, r := range rules {
if _, ok := index.children[r.RoleID]; !ok {
index.children[r.RoleID] = &ruleIndexNode{
children: make(map[string]*ruleIndexNode, 4),
}
}
n := index.children[r.RoleID]
bits := append([]string{r.Operation}, strings.Split(r.Resource, "/")...)
for _, b := range bits {
if _, ok := n.children[b]; !ok {
n.children[b] = &ruleIndexNode{
children: make(map[string]*ruleIndexNode, 4),
}
}
n = n.children[b]
}
n.isLeaf = true
n.access = r.Access
n.rule = r
}
return index
}
// get returns all RBAC rules matching these constraints
//
// The get operation's lookup complexity is the longest RBAC key + 1 for
// the operation + 1 for the role.
//
// Our longest bit will be 6 so this is essentially constant time.
func (t *ruleIndex) get(role uint64, op, res string) (out []*Rule) {
if t.children == nil {
return
}
if _, ok := t.children[role]; !ok {
return
}
// An edge case implied by the test suite
if op == "" && res == "" {
out = append(out, t.children[role].children[""].children[""].rule)
return
}
// Pull out the nodes for the role
aux, ok := t.children[role]
if !ok {
return
}
aux, ok = aux.children[op]
if !ok {
return
}
return aux.get(res, 0)
}
// get returns all of the rules matching these constraints
//
// Under the hood...
// We're avoiding string processing (concatenation, splitting, ...) as that can
// be a memory hog in scenarios where we're pounding this function.
//
// The from denotes the substring we've not yet processed.
func (n *ruleIndexNode) get(res string, from int) (out []*Rule) {
if n == nil || n.children == nil {
return
}
// If we've reached the leaf node but haven't yet processed the entire resource,
// we've reached an invalid scenario since we can't go any deeper
to := len(res)
if n.isLeaf && from < to {
return
}
// Once from passes to, we've processed the entire resource
if from >= to {
if n.isLeaf {
out = append(out, n.rule)
return
}
}
// Get the next / delimiter.
// Clamp the index to the length of the resource.
// Adjust the index to account the from (the start index of the remaining resource)
nextDelim := strings.Index(res[from:to], "/")
if nextDelim < 0 {
nextDelim = len(res)
} else {
nextDelim += from
}
// Get RBAC rules down the actual path
pathBit := res[from:nextDelim]
if n.children[pathBit] != nil {
out = append(out, n.children[pathBit].get(res, nextDelim+1)...)
}
// Get RBAC rules down the wildcard path
if n.children[wildcard] != nil {
out = append(out, n.children[wildcard].get(res, nextDelim+1)...)
}
return
}
// empty returns true if the index is empty
func (t *ruleIndex) empty() bool {
return t == nil || t.children == nil || len(t.children) == 0
}
func (t *ruleIndex) matchingRule(role uint64, op, res string) (out *Rule) {
set := RuleSet(t.get(role, op, res))
sort.Sort(set)
for _, s := range set {
if s.Access == Inherit {
continue
}
return s
}
return nil
}