snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
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
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
This commit is contained in:
165
server/pkg/rbac/rule_index.go
Normal file
165
server/pkg/rbac/rule_index.go
Normal file
@@ -0,0 +1,165 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user