Skip to content

Commit 74322db

Browse files
committed
fix: add gFunctionCache boolean option to enforcer
1 parent 017ae0e commit 74322db

3 files changed

Lines changed: 51 additions & 24 deletions

File tree

enforcer.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ type Enforcer struct {
5656
autoNotifyWatcher bool
5757
autoNotifyDispatcher bool
5858
acceptJsonRequest bool
59+
gFunctionCache bool
5960

6061
aiConfig AIConfig
6162
}
@@ -193,6 +194,7 @@ func (e *Enforcer) initialize() {
193194
e.autoBuildRoleLinks = true
194195
e.autoNotifyWatcher = true
195196
e.autoNotifyDispatcher = true
197+
e.gFunctionCache = true
196198
e.initRmMap()
197199

198200
// Initialize detectors with default detector if not already set
@@ -637,6 +639,14 @@ func (e *Enforcer) EnableAcceptJsonRequest(acceptJsonRequest bool) {
637639
e.acceptJsonRequest = acceptJsonRequest
638640
}
639641

642+
// EnableGFunctionCache controls whether to cache g() function results.
643+
// Disable this when inputs are high-cardinality (e.g. UUIDs or dynamic paths)
644+
// to prevent unbounded memory growth.
645+
func (e *Enforcer) EnableGFunctionCache(enabled bool) {
646+
e.gFunctionCache = enabled
647+
e.invalidateMatcherMap()
648+
}
649+
640650
// BuildRoleLinks manually rebuild the role inheritance relations.
641651
func (e *Enforcer) BuildRoleLinks() error {
642652
e.invalidateMatcherMap()
@@ -704,7 +714,7 @@ func (e *Enforcer) enforce(matcher string, explains *[]string, rvals ...interfac
704714
// or a conditional role definition (ast.CondRM != nil)
705715
// ast.RM and ast.CondRM shouldn't be nil at the same time
706716
if ast.RM != nil {
707-
functions[key] = util.GenerateGFunction(ast.RM)
717+
functions[key] = util.GenerateGFunction(ast.RM, e.gFunctionCache)
708718
}
709719
if ast.CondRM != nil {
710720
functions[key] = util.GenerateConditionalGFunction(ast.CondRM)

management_api.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ func (e *Enforcer) GetFilteredNamedPolicyWithMatcher(ptype string, matcher strin
145145
// or a conditional role definition (ast.CondRM != nil)
146146
// ast.RM and ast.CondRM shouldn't be nil at the same time
147147
if ast.RM != nil {
148-
functions[key] = util.GenerateGFunction(ast.RM)
148+
functions[key] = util.GenerateGFunction(ast.RM, e.gFunctionCache)
149149
}
150150
if ast.CondRM != nil {
151151
functions[key] = util.GenerateConditionalGFunction(ast.CondRM)

util/builtin_operators.go

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -402,34 +402,53 @@ func GlobMatchFunc(args ...interface{}) (interface{}, error) {
402402
}
403403

404404
// GenerateGFunction is the factory method of the g(_, _[, _]) function.
405-
func GenerateGFunction(rm rbac.RoleManager) govaluate.ExpressionFunction {
405+
// If useCache is true, results are memoized per unique argument combination for performance.
406+
// Set useCache to false when inputs are high-cardinality (e.g. UUIDs) to avoid unbounded memory growth.
407+
func GenerateGFunction(rm rbac.RoleManager, useCache bool) govaluate.ExpressionFunction {
406408
memorized := sync.Map{}
407409
return func(args ...interface{}) (interface{}, error) {
408410
// Like all our other govaluate functions, all args are strings.
409411

410-
// Allocate and generate a cache key from the arguments...
411-
total := len(args)
412-
for _, a := range args {
413-
aStr := a.(string)
414-
total += len(aStr)
415-
}
416-
builder := strings.Builder{}
417-
builder.Grow(total)
418-
for _, arg := range args {
419-
builder.WriteByte(0)
420-
builder.WriteString(arg.(string))
421-
}
422-
key := builder.String()
423-
424-
// ...and see if we've already calculated this.
425-
v, found := memorized.Load(key)
426-
if found {
412+
if useCache {
413+
// Allocate and generate a cache key from the arguments...
414+
total := len(args)
415+
for _, a := range args {
416+
aStr := a.(string)
417+
total += len(aStr)
418+
}
419+
builder := strings.Builder{}
420+
builder.Grow(total)
421+
for _, arg := range args {
422+
builder.WriteByte(0)
423+
builder.WriteString(arg.(string))
424+
}
425+
key := builder.String()
426+
427+
// ...and see if we've already calculated this.
428+
if v, found := memorized.Load(key); found {
429+
return v, nil
430+
}
431+
432+
// If not, do the calculation.
433+
// There are guaranteed to be exactly 2 or 3 arguments.
434+
name1, name2 := args[0].(string), args[1].(string)
435+
var v interface{}
436+
if rm == nil {
437+
v = name1 == name2
438+
} else if len(args) == 2 {
439+
v, _ = rm.HasLink(name1, name2)
440+
} else {
441+
domain := args[2].(string)
442+
v, _ = rm.HasLink(name1, name2, domain)
443+
}
444+
445+
memorized.Store(key, v)
427446
return v, nil
428447
}
429448

430-
// If not, do the calculation.
431-
// There are guaranteed to be exactly 2 or 3 arguments.
449+
// No caching path.
432450
name1, name2 := args[0].(string), args[1].(string)
451+
var v interface{}
433452
if rm == nil {
434453
v = name1 == name2
435454
} else if len(args) == 2 {
@@ -438,8 +457,6 @@ func GenerateGFunction(rm rbac.RoleManager) govaluate.ExpressionFunction {
438457
domain := args[2].(string)
439458
v, _ = rm.HasLink(name1, name2, domain)
440459
}
441-
442-
memorized.Store(key, v)
443460
return v, nil
444461
}
445462
}

0 commit comments

Comments
 (0)