This is an automated email from the ASF dual-hosted git repository.
hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin.git
The following commit(s) were added to refs/heads/master by this push:
new 27d2b2d fix: invalidate decision cache on policy change
27d2b2d is described below
commit 27d2b2d3663f536a3466d16b5ce00878fc9cd968
Author: Yang Luo <[email protected]>
AuthorDate: Sat Oct 10 02:26:54 2026 +0800
fix: invalidate decision cache on policy change
---
enforcer.go | 13 +++++++++++++
enforcer_cached.go | 46 +++++++++++++++++++++++++++++++++++-----------
enforcer_cached_synced.go | 41 ++++++++++++++++++++++++++++++++++++-----
enforcer_context.go | 16 ++++++++++++++++
enforcer_distributed.go | 14 ++++++++++++++
internal_api.go | 16 ++++++++++++++++
6 files changed, 130 insertions(+), 16 deletions(-)
diff --git a/enforcer.go b/enforcer.go
index c575534..b2417c8 100644
--- a/enforcer.go
+++ b/enforcer.go
@@ -23,6 +23,7 @@ import (
"runtime/debug"
"strings"
"sync"
+ "sync/atomic"
"github.com/casbin/casbin/v3/detector"
"github.com/casbin/casbin/v3/effector"
@@ -39,6 +40,8 @@ import (
// Enforcer is the main interface for authorization enforcement and policy
management.
type Enforcer struct {
+ policyVersion uint64
+
modelPath string
model model.Model
fm model.FunctionMap
@@ -197,6 +200,7 @@ func (e *Enforcer) initialize() {
e.eft = effector.NewDefaultEffector()
e.watcher = nil
e.matcherMap = sync.Map{}
+ e.bumpPolicyVersion()
e.enabled = true
e.autoSave = true
@@ -696,6 +700,15 @@ func NewEnforceContext(suffix string) EnforceContext {
func (e *Enforcer) invalidateMatcherMap() {
e.matcherMap = sync.Map{}
+ e.bumpPolicyVersion()
+}
+
+func (e *Enforcer) getPolicyVersion() uint64 {
+ return atomic.LoadUint64(&e.policyVersion)
+}
+
+func (e *Enforcer) bumpPolicyVersion() {
+ atomic.AddUint64(&e.policyVersion, 1)
}
// enforce use a custom matcher to decides whether a "subject" can access a
"object" with the operation "action", input parameters are usually: (matcher,
sub, obj, act), use model matcher by default when matcher is "".
diff --git a/enforcer_cached.go b/enforcer_cached.go
index a9b4be4..deff218 100644
--- a/enforcer_cached.go
+++ b/enforcer_cached.go
@@ -29,10 +29,11 @@ import (
// CachedEnforcer wraps Enforcer and provides decision cache.
type CachedEnforcer struct {
*Enforcer
- expireTime time.Duration
- cache cache.Cache
- enableCache int32
- locker *sync.RWMutex
+ cacheVersion uint64
+ expireTime time.Duration
+ cache cache.Cache
+ enableCache int32
+ locker *sync.RWMutex
}
type CacheableParam interface {
@@ -51,6 +52,7 @@ func NewCachedEnforcer(params ...interface{})
(*CachedEnforcer, error) {
e.enableCache = 1
e.cache, _ = cache.NewDefaultCache()
e.locker = new(sync.RWMutex)
+ e.cacheVersion = e.getPolicyVersion()
return e, nil
}
@@ -75,6 +77,11 @@ func (e *CachedEnforcer) Enforce(rvals ...interface{})
(bool, error) {
return e.Enforcer.Enforce(rvals...)
}
+ version := e.getPolicyVersion()
+ if err := e.syncCacheVersion(version); err != nil {
+ return false, err
+ }
+
if res, err := e.getCachedResult(key); err == nil {
return res, nil
} else if err != cache.ErrNoSuchKey {
@@ -86,7 +93,7 @@ func (e *CachedEnforcer) Enforce(rvals ...interface{}) (bool,
error) {
return false, err
}
- err = e.setCachedResult(key, res, e.expireTime)
+ err = e.setCachedResult(version, key, res, e.expireTime)
return res, err
}
@@ -143,12 +150,6 @@ func (e *CachedEnforcer) SetCache(c cache.Cache) {
e.cache = c
}
-func (e *CachedEnforcer) setCachedResult(key string, res bool, extra
...interface{}) error {
- e.locker.Lock()
- defer e.locker.Unlock()
- return e.cache.Set(key, res, extra...)
-}
-
func (e *CachedEnforcer) getKey(params ...interface{}) (string, bool) {
return GetCacheKey(params...)
}
@@ -186,3 +187,26 @@ func (e *CachedEnforcer) ClearPolicy() {
}
e.Enforcer.ClearPolicy()
}
+
+// syncCacheVersion drops all cached decisions when the policy has changed
since they were cached.
+func (e *CachedEnforcer) syncCacheVersion(version uint64) error {
+ e.locker.Lock()
+ defer e.locker.Unlock()
+ if version <= e.cacheVersion {
+ return nil
+ }
+ if err := e.cache.Clear(); err != nil {
+ return err
+ }
+ e.cacheVersion = version
+ return nil
+}
+
+func (e *CachedEnforcer) setCachedResult(version uint64, key string, res bool,
extra ...interface{}) error {
+ e.locker.Lock()
+ defer e.locker.Unlock()
+ if version != e.cacheVersion {
+ return nil
+ }
+ return e.cache.Set(key, res, extra...)
+}
diff --git a/enforcer_cached_synced.go b/enforcer_cached_synced.go
index 3ae2613..9301c95 100644
--- a/enforcer_cached_synced.go
+++ b/enforcer_cached_synced.go
@@ -27,6 +27,8 @@ import (
// SyncedCachedEnforcer wraps Enforcer and provides decision sync cache.
type SyncedCachedEnforcer struct {
+ cacheVersion uint64
+
*SyncedEnforcer
expireTime time.Duration
cache cache.Cache
@@ -46,6 +48,7 @@ func NewSyncedCachedEnforcer(params ...interface{})
(*SyncedCachedEnforcer, erro
e.enableCache = 1
e.cache, _ = cache.NewSyncCache()
e.locker = new(sync.RWMutex)
+ e.cacheVersion = e.getPolicyVersion()
return e, nil
}
@@ -70,6 +73,11 @@ func (e *SyncedCachedEnforcer) Enforce(rvals ...interface{})
(bool, error) {
return e.SyncedEnforcer.Enforce(rvals...)
}
+ version := e.getPolicyVersion()
+ if err := e.syncCacheVersion(version); err != nil {
+ return false, err
+ }
+
if res, err := e.getCachedResult(key); err == nil {
return res, nil
} else if err != cache.ErrNoSuchKey {
@@ -81,7 +89,7 @@ func (e *SyncedCachedEnforcer) Enforce(rvals ...interface{})
(bool, error) {
return false, err
}
- err = e.setCachedResult(key, res, e.expireTime)
+ err = e.setCachedResult(version, key, res, e.expireTime)
return res, err
}
@@ -139,10 +147,6 @@ func (e *SyncedCachedEnforcer) SetCache(c cache.Cache) {
e.cache = c
}
-func (e *SyncedCachedEnforcer) setCachedResult(key string, res bool, extra
...interface{}) error {
- return e.cache.Set(key, res, extra...)
-}
-
func (e *SyncedCachedEnforcer) getKey(params ...interface{}) (string, bool) {
return GetCacheKey(params...)
}
@@ -181,3 +185,30 @@ func (e *SyncedCachedEnforcer)
checkManyAndRemoveCache(rules [][]string) (bool,
}
return true, nil
}
+
+// syncCacheVersion drops all cached decisions when the policy has changed
since they were cached.
+func (e *SyncedCachedEnforcer) syncCacheVersion(version uint64) error {
+ if version <= atomic.LoadUint64(&e.cacheVersion) {
+ return nil
+ }
+
+ e.locker.Lock()
+ defer e.locker.Unlock()
+ if version <= e.cacheVersion {
+ return nil
+ }
+ if err := e.cache.Clear(); err != nil {
+ return err
+ }
+ atomic.StoreUint64(&e.cacheVersion, version)
+ return nil
+}
+
+func (e *SyncedCachedEnforcer) setCachedResult(version uint64, key string, res
bool, extra ...interface{}) error {
+ e.locker.RLock()
+ defer e.locker.RUnlock()
+ if version != e.cacheVersion {
+ return nil
+ }
+ return e.cache.Set(key, res, extra...)
+}
diff --git a/enforcer_context.go b/enforcer_context.go
index b7e4f8a..cf885d3 100644
--- a/enforcer_context.go
+++ b/enforcer_context.go
@@ -416,6 +416,8 @@ func (e *ContextEnforcer) SelfUpdatePoliciesCtx(ctx
context.Context, sec string,
// addPolicyWithoutNotifyCtx adds a rule to the current policy with context.
func (e *ContextEnforcer) addPolicyWithoutNotifyCtx(ctx context.Context, sec
string, ptype string, rule []string) (bool, error) {
+ defer e.bumpPolicyVersion()
+
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.AddPolicies(sec, ptype,
[][]string{rule})
}
@@ -450,6 +452,8 @@ func (e *ContextEnforcer) addPolicyWithoutNotifyCtx(ctx
context.Context, sec str
// addPoliciesWithoutNotifyCtx adds rules to the current policy with context.
func (e *ContextEnforcer) addPoliciesWithoutNotifyCtx(ctx context.Context, sec
string, ptype string, rules [][]string, autoRemoveRepeat bool) (bool, error) {
+ defer e.bumpPolicyVersion()
+
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.AddPolicies(sec, ptype, rules)
}
@@ -491,6 +495,8 @@ func (e *ContextEnforcer) addPoliciesWithoutNotifyCtx(ctx
context.Context, sec s
// removePolicyWithoutNotifyCtx removes a rule from the current policy with
context.
func (e *ContextEnforcer) removePolicyWithoutNotifyCtx(ctx context.Context,
sec string, ptype string, rule []string) (bool, error) {
+ defer e.bumpPolicyVersion()
+
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.RemovePolicies(sec, ptype,
[][]string{rule})
}
@@ -520,6 +526,8 @@ func (e *ContextEnforcer) removePolicyWithoutNotifyCtx(ctx
context.Context, sec
// removePoliciesWithoutNotifyCtx removes rules from the current policy with
context.
func (e *ContextEnforcer) removePoliciesWithoutNotifyCtx(ctx context.Context,
sec string, ptype string, rules [][]string) (bool, error) {
+ defer e.bumpPolicyVersion()
+
if hasPolicies, err := e.model.HasPolicies(sec, ptype, rules);
!hasPolicies || err != nil {
return hasPolicies, err
}
@@ -552,6 +560,8 @@ func (e *ContextEnforcer)
removePoliciesWithoutNotifyCtx(ctx context.Context, se
// removeFilteredPolicyWithoutNotifyCtx removes policy rules that match the
filter from the current policy with context.
func (e *ContextEnforcer) removeFilteredPolicyWithoutNotifyCtx(ctx
context.Context, sec string, ptype string, fieldIndex int, fieldValues
[]string) (bool, error) {
+ defer e.bumpPolicyVersion()
+
if len(fieldValues) == 0 {
return false, Err.ErrInvalidFieldValuesParameter
}
@@ -585,6 +595,8 @@ func (e *ContextEnforcer)
removeFilteredPolicyWithoutNotifyCtx(ctx context.Conte
// updatePolicyWithoutNotifyCtx updates a policy rule in the current policy
with context.
func (e *ContextEnforcer) updatePolicyWithoutNotifyCtx(ctx context.Context,
sec string, ptype string, oldRule, newRule []string) (bool, error) {
+ defer e.bumpPolicyVersion()
+
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.UpdatePolicy(sec, ptype, oldRule,
newRule)
}
@@ -616,6 +628,8 @@ func (e *ContextEnforcer) updatePolicyWithoutNotifyCtx(ctx
context.Context, sec
}
func (e *ContextEnforcer) updatePoliciesWithoutNotifyCtx(ctx context.Context,
sec string, ptype string, oldRules [][]string, newRules [][]string) (bool,
error) {
+ defer e.bumpPolicyVersion()
+
if len(newRules) != len(oldRules) {
return false, fmt.Errorf("the length of oldRules should be
equal to the length of newRules, but got the length of oldRules is %d, the
length of newRules is %d", len(oldRules), len(newRules))
}
@@ -806,6 +820,8 @@ func (e *ContextEnforcer) updateFilteredPoliciesCtx(ctx
context.Context, sec str
}
func (e *ContextEnforcer) updateFilteredPoliciesWithoutNotifyCtx(ctx
context.Context, sec string, ptype string, newRules [][]string, fieldIndex int,
fieldValues ...string) ([][]string, error) {
+ defer e.bumpPolicyVersion()
+
var (
oldRules [][]string
err error
diff --git a/enforcer_distributed.go b/enforcer_distributed.go
index fe73a30..361520f 100644
--- a/enforcer_distributed.go
+++ b/enforcer_distributed.go
@@ -46,6 +46,8 @@ func (d *DistributedEnforcer) SetDispatcher(dispatcher
persist.Dispatcher) {
// AddPoliciesSelf provides a method for dispatcher to add authorization rules
to the current policy.
// The function returns the rules affected and error.
func (d *DistributedEnforcer) AddPoliciesSelf(shouldPersist func() bool, sec
string, ptype string, rules [][]string) (affected [][]string, err error) {
+ defer d.bumpPolicyVersion()
+
d.m.Lock()
defer d.m.Unlock()
if shouldPersist != nil && shouldPersist() {
@@ -84,6 +86,8 @@ func (d *DistributedEnforcer) AddPoliciesSelf(shouldPersist
func() bool, sec str
// RemovePoliciesSelf provides a method for dispatcher to remove a set of
rules from current policy.
// The function returns the rules affected and error.
func (d *DistributedEnforcer) RemovePoliciesSelf(shouldPersist func() bool,
sec string, ptype string, rules [][]string) (affected [][]string, err error) {
+ defer d.bumpPolicyVersion()
+
d.m.Lock()
defer d.m.Unlock()
if shouldPersist != nil && shouldPersist() {
@@ -112,6 +116,8 @@ func (d *DistributedEnforcer)
RemovePoliciesSelf(shouldPersist func() bool, sec
// RemoveFilteredPolicySelf provides a method for dispatcher to remove an
authorization rule from the current policy, field filters can be specified.
// The function returns the rules affected and error.
func (d *DistributedEnforcer) RemoveFilteredPolicySelf(shouldPersist func()
bool, sec string, ptype string, fieldIndex int, fieldValues ...string)
(affected [][]string, err error) {
+ defer d.bumpPolicyVersion()
+
d.m.Lock()
defer d.m.Unlock()
if shouldPersist != nil && shouldPersist() {
@@ -139,6 +145,8 @@ func (d *DistributedEnforcer)
RemoveFilteredPolicySelf(shouldPersist func() bool
// ClearPolicySelf provides a method for dispatcher to clear all rules from
the current policy.
func (d *DistributedEnforcer) ClearPolicySelf(shouldPersist func() bool) error
{
+ defer d.bumpPolicyVersion()
+
d.m.Lock()
defer d.m.Unlock()
if shouldPersist != nil && shouldPersist() {
@@ -155,6 +163,8 @@ func (d *DistributedEnforcer) ClearPolicySelf(shouldPersist
func() bool) error {
// UpdatePolicySelf provides a method for dispatcher to update an
authorization rule from the current policy.
func (d *DistributedEnforcer) UpdatePolicySelf(shouldPersist func() bool, sec
string, ptype string, oldRule, newRule []string) (affected bool, err error) {
+ defer d.bumpPolicyVersion()
+
d.m.Lock()
defer d.m.Unlock()
if shouldPersist != nil && shouldPersist() {
@@ -185,6 +195,8 @@ func (d *DistributedEnforcer)
UpdatePolicySelf(shouldPersist func() bool, sec st
// UpdatePoliciesSelf provides a method for dispatcher to update a set of
authorization rules from the current policy.
func (d *DistributedEnforcer) UpdatePoliciesSelf(shouldPersist func() bool,
sec string, ptype string, oldRules, newRules [][]string) (affected bool, err
error) {
+ defer d.bumpPolicyVersion()
+
d.m.Lock()
defer d.m.Unlock()
if shouldPersist != nil && shouldPersist() {
@@ -215,6 +227,8 @@ func (d *DistributedEnforcer)
UpdatePoliciesSelf(shouldPersist func() bool, sec
// UpdateFilteredPoliciesSelf provides a method for dispatcher to update a set
of authorization rules from the current policy.
func (d *DistributedEnforcer) UpdateFilteredPoliciesSelf(shouldPersist func()
bool, sec string, ptype string, newRules [][]string, fieldIndex int,
fieldValues ...string) (bool, error) {
+ defer d.bumpPolicyVersion()
+
d.m.Lock()
defer d.m.Unlock()
var (
diff --git a/internal_api.go b/internal_api.go
index 04883af..74ae54f 100644
--- a/internal_api.go
+++ b/internal_api.go
@@ -46,6 +46,8 @@ func (e *Enforcer) validateConstraintsForGroupingPolicy()
error {
// addPolicy adds a rule to the current policy.
func (e *Enforcer) addPolicyWithoutNotify(sec string, ptype string, rule
[]string) (bool, error) {
+ defer e.bumpPolicyVersion()
+
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.AddPolicies(sec, ptype,
[][]string{rule})
}
@@ -87,6 +89,8 @@ func (e *Enforcer) addPolicyWithoutNotify(sec string, ptype
string, rule []strin
// If autoRemoveRepeat == true, existing rules are automatically filtered
// Otherwise, false is returned directly.
func (e *Enforcer) addPoliciesWithoutNotify(sec string, ptype string, rules
[][]string, autoRemoveRepeat bool) (bool, error) {
+ defer e.bumpPolicyVersion()
+
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.AddPolicies(sec, ptype, rules)
}
@@ -133,6 +137,8 @@ func (e *Enforcer) addPoliciesWithoutNotify(sec string,
ptype string, rules [][]
// removePolicy removes a rule from the current policy.
func (e *Enforcer) removePolicyWithoutNotify(sec string, ptype string, rule
[]string) (bool, error) {
+ defer e.bumpPolicyVersion()
+
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.RemovePolicies(sec, ptype,
[][]string{rule})
}
@@ -166,6 +172,8 @@ func (e *Enforcer) removePolicyWithoutNotify(sec string,
ptype string, rule []st
}
func (e *Enforcer) updatePolicyWithoutNotify(sec string, ptype string, oldRule
[]string, newRule []string) (bool, error) {
+ defer e.bumpPolicyVersion()
+
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.UpdatePolicy(sec, ptype, oldRule,
newRule)
}
@@ -202,6 +210,8 @@ func (e *Enforcer) updatePolicyWithoutNotify(sec string,
ptype string, oldRule [
}
func (e *Enforcer) updatePoliciesWithoutNotify(sec string, ptype string,
oldRules [][]string, newRules [][]string) (bool, error) {
+ defer e.bumpPolicyVersion()
+
if len(newRules) != len(oldRules) {
return false, fmt.Errorf("the length of oldRules should be
equal to the length of newRules, but got the length of oldRules is %d, the
length of newRules is %d", len(oldRules), len(newRules))
}
@@ -244,6 +254,8 @@ func (e *Enforcer) updatePoliciesWithoutNotify(sec string,
ptype string, oldRule
// removePolicies removes rules from the current policy.
func (e *Enforcer) removePoliciesWithoutNotify(sec string, ptype string, rules
[][]string) (bool, error) {
+ defer e.bumpPolicyVersion()
+
if hasPolicies, err := e.model.HasPolicies(sec, ptype, rules);
!hasPolicies || err != nil {
return hasPolicies, err
}
@@ -281,6 +293,8 @@ func (e *Enforcer) removePoliciesWithoutNotify(sec string,
ptype string, rules [
// removeFilteredPolicy removes rules based on field filters from the current
policy.
func (e *Enforcer) removeFilteredPolicyWithoutNotify(sec string, ptype string,
fieldIndex int, fieldValues []string) (bool, error) {
+ defer e.bumpPolicyVersion()
+
if len(fieldValues) == 0 {
return false, Err.ErrInvalidFieldValuesParameter
}
@@ -318,6 +332,8 @@ func (e *Enforcer) removeFilteredPolicyWithoutNotify(sec
string, ptype string, f
}
func (e *Enforcer) updateFilteredPoliciesWithoutNotify(sec string, ptype
string, newRules [][]string, fieldIndex int, fieldValues ...string)
([][]string, error) {
+ defer e.bumpPolicyVersion()
+
var (
oldRules [][]string
err error
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]