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 75f29b7 fix: don't write to adapter in Self* APIs
75f29b7 is described below
commit 75f29b71985344323dc2db744a40c8c4e949084d
Author: Yang Luo <[email protected]>
AuthorDate: Fri Aug 7 00:31:05 2026 +0800
fix: don't write to adapter in Self* APIs
---
enforcer_context.go | 58 +++++-----
internal_api.go | 46 ++++----
management_api.go | 34 ++++--
model/model_test.go | 28 +++++
transaction_test.go | 45 ++++++++
watcher_self_sync_test.go | 275 ++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 428 insertions(+), 58 deletions(-)
diff --git a/enforcer_context.go b/enforcer_context.go
index b7e4f8a..56252c8 100644
--- a/enforcer_context.go
+++ b/enforcer_context.go
@@ -375,47 +375,47 @@ func (e *ContextEnforcer)
UpdateNamedGroupingPoliciesCtx(ctx context.Context, pt
// SelfAddPolicyCtx adds a policy rule to the current policy with context.
func (e *ContextEnforcer) SelfAddPolicyCtx(ctx context.Context, sec string,
ptype string, rule []string) (bool, error) {
- return e.addPolicyWithoutNotifyCtx(ctx, sec, ptype, rule)
+ return e.addPolicyWithoutNotifyCtx(ctx, nil, sec, ptype, rule)
}
// SelfAddPoliciesCtx adds policy rules to the current policy with context.
func (e *ContextEnforcer) SelfAddPoliciesCtx(ctx context.Context, sec string,
ptype string, rules [][]string) (bool, error) {
- return e.addPoliciesWithoutNotifyCtx(ctx, sec, ptype, rules, false)
+ return e.addPoliciesWithoutNotifyCtx(ctx, nil, sec, ptype, rules, false)
}
func (e *ContextEnforcer) SelfAddPoliciesExCtx(ctx context.Context, sec
string, ptype string, rules [][]string) (bool, error) {
- return e.addPoliciesWithoutNotifyCtx(ctx, sec, ptype, rules, true)
+ return e.addPoliciesWithoutNotifyCtx(ctx, nil, sec, ptype, rules, true)
}
// SelfRemovePolicyCtx removes a policy rule from the current policy with
context.
func (e *ContextEnforcer) SelfRemovePolicyCtx(ctx context.Context, sec string,
ptype string, rule []string) (bool, error) {
- return e.removePolicyWithoutNotifyCtx(ctx, sec, ptype, rule)
+ return e.removePolicyWithoutNotifyCtx(ctx, nil, sec, ptype, rule)
}
// SelfRemovePoliciesCtx removes policy rules from the current policy with
context.
func (e *ContextEnforcer) SelfRemovePoliciesCtx(ctx context.Context, sec
string, ptype string, rules [][]string) (bool, error) {
- return e.removePoliciesWithoutNotifyCtx(ctx, sec, ptype, rules)
+ return e.removePoliciesWithoutNotifyCtx(ctx, nil, sec, ptype, rules)
}
// SelfRemoveFilteredPolicyCtx removes policy rules that match the filter from
the current policy with context.
func (e *ContextEnforcer) SelfRemoveFilteredPolicyCtx(ctx context.Context, sec
string, ptype string, fieldIndex int, fieldValues ...string) (bool, error) {
- return e.removeFilteredPolicyWithoutNotifyCtx(ctx, sec, ptype,
fieldIndex, fieldValues)
+ return e.removeFilteredPolicyWithoutNotifyCtx(ctx, nil, sec, ptype,
fieldIndex, fieldValues)
}
// SelfUpdatePolicyCtx updates a policy rule in the current policy with
context.
func (e *ContextEnforcer) SelfUpdatePolicyCtx(ctx context.Context, sec string,
ptype string, oldRule, newRule []string) (bool, error) {
- return e.updatePolicyWithoutNotifyCtx(ctx, sec, ptype, oldRule, newRule)
+ return e.updatePolicyWithoutNotifyCtx(ctx, nil, sec, ptype, oldRule,
newRule)
}
// SelfUpdatePoliciesCtx updates policy rules in the current policy with
context.
func (e *ContextEnforcer) SelfUpdatePoliciesCtx(ctx context.Context, sec
string, ptype string, oldRules, newRules [][]string) (bool, error) {
- return e.updatePoliciesWithoutNotifyCtx(ctx, sec, ptype, oldRules,
newRules)
+ return e.updatePoliciesWithoutNotifyCtx(ctx, nil, sec, ptype, oldRules,
newRules)
}
// Internal API methods with context support
// 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) {
+func (e *ContextEnforcer) addPolicyWithoutNotifyCtx(ctx context.Context,
shouldPersist func() bool, sec string, ptype string, rule []string) (bool,
error) {
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.AddPolicies(sec, ptype,
[][]string{rule})
}
@@ -425,7 +425,7 @@ func (e *ContextEnforcer) addPolicyWithoutNotifyCtx(ctx
context.Context, sec str
return false, err
}
- if e.shouldPersist() {
+ if shouldPersist != nil && shouldPersist() {
if err = e.adapterCtx.AddPolicyCtx(ctx, sec, ptype, rule); err
!= nil {
if err.Error() != notImplemented {
return false, err
@@ -449,7 +449,7 @@ 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) {
+func (e *ContextEnforcer) addPoliciesWithoutNotifyCtx(ctx context.Context,
shouldPersist func() bool, sec string, ptype string, rules [][]string,
autoRemoveRepeat bool) (bool, error) {
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.AddPolicies(sec, ptype, rules)
}
@@ -461,7 +461,7 @@ func (e *ContextEnforcer) addPoliciesWithoutNotifyCtx(ctx
context.Context, sec s
}
}
- if e.shouldPersist() {
+ if shouldPersist != nil && shouldPersist() {
if err :=
e.adapterCtx.(persist.ContextBatchAdapter).AddPoliciesCtx(ctx, sec, ptype,
rules); err != nil {
if err.Error() != notImplemented {
return false, err
@@ -490,12 +490,12 @@ 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) {
+func (e *ContextEnforcer) removePolicyWithoutNotifyCtx(ctx context.Context,
shouldPersist func() bool, sec string, ptype string, rule []string) (bool,
error) {
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.RemovePolicies(sec, ptype,
[][]string{rule})
}
- if e.shouldPersist() {
+ if shouldPersist != nil && shouldPersist() {
if err := e.adapterCtx.RemovePolicyCtx(ctx, sec, ptype, rule);
err != nil {
if err.Error() != notImplemented {
return false, err
@@ -519,7 +519,7 @@ 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) {
+func (e *ContextEnforcer) removePoliciesWithoutNotifyCtx(ctx context.Context,
shouldPersist func() bool, sec string, ptype string, rules [][]string) (bool,
error) {
if hasPolicies, err := e.model.HasPolicies(sec, ptype, rules);
!hasPolicies || err != nil {
return hasPolicies, err
}
@@ -528,7 +528,7 @@ func (e *ContextEnforcer)
removePoliciesWithoutNotifyCtx(ctx context.Context, se
return true, e.dispatcher.RemovePolicies(sec, ptype, rules)
}
- if e.shouldPersist() {
+ if shouldPersist != nil && shouldPersist() {
if err :=
e.adapterCtx.(persist.ContextBatchAdapter).RemovePoliciesCtx(ctx, sec, ptype,
rules); err != nil {
if err.Error() != notImplemented {
return false, err
@@ -551,7 +551,7 @@ 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) {
+func (e *ContextEnforcer) removeFilteredPolicyWithoutNotifyCtx(ctx
context.Context, shouldPersist func() bool, sec string, ptype string,
fieldIndex int, fieldValues []string) (bool, error) {
if len(fieldValues) == 0 {
return false, Err.ErrInvalidFieldValuesParameter
}
@@ -560,7 +560,7 @@ func (e *ContextEnforcer)
removeFilteredPolicyWithoutNotifyCtx(ctx context.Conte
return true, e.dispatcher.RemoveFilteredPolicy(sec, ptype,
fieldIndex, fieldValues...)
}
- if e.shouldPersist() {
+ if shouldPersist != nil && shouldPersist() {
if err := e.adapterCtx.RemoveFilteredPolicyCtx(ctx, sec, ptype,
fieldIndex, fieldValues...); err != nil {
if err.Error() != notImplemented {
return false, err
@@ -584,12 +584,12 @@ 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) {
+func (e *ContextEnforcer) updatePolicyWithoutNotifyCtx(ctx context.Context,
shouldPersist func() bool, sec string, ptype string, oldRule, newRule []string)
(bool, error) {
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.UpdatePolicy(sec, ptype, oldRule,
newRule)
}
- if e.shouldPersist() {
+ if shouldPersist != nil && shouldPersist() {
if err :=
e.adapterCtx.(persist.ContextUpdatableAdapter).UpdatePolicyCtx(ctx, sec, ptype,
oldRule, newRule); err != nil {
if err.Error() != notImplemented {
return false, err
@@ -615,7 +615,7 @@ func (e *ContextEnforcer) updatePolicyWithoutNotifyCtx(ctx
context.Context, sec
return ruleUpdated, nil
}
-func (e *ContextEnforcer) updatePoliciesWithoutNotifyCtx(ctx context.Context,
sec string, ptype string, oldRules [][]string, newRules [][]string) (bool,
error) {
+func (e *ContextEnforcer) updatePoliciesWithoutNotifyCtx(ctx context.Context,
shouldPersist func() bool, sec string, ptype string, oldRules [][]string,
newRules [][]string) (bool, error) {
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))
}
@@ -624,7 +624,7 @@ func (e *ContextEnforcer)
updatePoliciesWithoutNotifyCtx(ctx context.Context, se
return true, e.dispatcher.UpdatePolicies(sec, ptype, oldRules,
newRules)
}
- if e.shouldPersist() {
+ if shouldPersist != nil && shouldPersist() {
if err :=
e.adapterCtx.(persist.ContextUpdatableAdapter).UpdatePoliciesCtx(ctx, sec,
ptype, oldRules, newRules); err != nil {
if err.Error() != notImplemented {
return false, err
@@ -652,7 +652,7 @@ func (e *ContextEnforcer)
updatePoliciesWithoutNotifyCtx(ctx context.Context, se
}
func (e *ContextEnforcer) addPolicyCtx(ctx context.Context, sec string, ptype
string, rule []string) (bool, error) {
- ok, err := e.addPolicyWithoutNotifyCtx(ctx, sec, ptype, rule)
+ ok, err := e.addPolicyWithoutNotifyCtx(ctx, e.shouldPersist, sec,
ptype, rule)
if !ok || err != nil {
return ok, err
}
@@ -671,7 +671,7 @@ func (e *ContextEnforcer) addPolicyCtx(ctx context.Context,
sec string, ptype st
}
func (e *ContextEnforcer) addPoliciesCtx(ctx context.Context, sec string,
ptype string, rules [][]string, autoRemoveRepeat bool) (bool, error) {
- ok, err := e.addPoliciesWithoutNotifyCtx(ctx, sec, ptype, rules,
autoRemoveRepeat)
+ ok, err := e.addPoliciesWithoutNotifyCtx(ctx, e.shouldPersist, sec,
ptype, rules, autoRemoveRepeat)
if !ok || err != nil {
return ok, err
}
@@ -690,7 +690,7 @@ func (e *ContextEnforcer) addPoliciesCtx(ctx
context.Context, sec string, ptype
}
func (e *ContextEnforcer) updatePolicyCtx(ctx context.Context, sec string,
ptype string, oldRule []string, newRule []string) (bool, error) {
- ok, err := e.updatePolicyWithoutNotifyCtx(ctx, sec, ptype, oldRule,
newRule)
+ ok, err := e.updatePolicyWithoutNotifyCtx(ctx, e.shouldPersist, sec,
ptype, oldRule, newRule)
if !ok || err != nil {
return ok, err
}
@@ -709,7 +709,7 @@ func (e *ContextEnforcer) updatePolicyCtx(ctx
context.Context, sec string, ptype
}
func (e *ContextEnforcer) updatePoliciesCtx(ctx context.Context, sec string,
ptype string, oldRules [][]string, newRules [][]string) (bool, error) {
- ok, err := e.updatePoliciesWithoutNotifyCtx(ctx, sec, ptype, oldRules,
newRules)
+ ok, err := e.updatePoliciesWithoutNotifyCtx(ctx, e.shouldPersist, sec,
ptype, oldRules, newRules)
if !ok || err != nil {
return ok, err
}
@@ -728,7 +728,7 @@ func (e *ContextEnforcer) updatePoliciesCtx(ctx
context.Context, sec string, pty
}
func (e *ContextEnforcer) removePolicyCtx(ctx context.Context, sec string,
ptype string, rule []string) (bool, error) {
- ok, err := e.removePolicyWithoutNotifyCtx(ctx, sec, ptype, rule)
+ ok, err := e.removePolicyWithoutNotifyCtx(ctx, e.shouldPersist, sec,
ptype, rule)
if !ok || err != nil {
return ok, err
}
@@ -747,7 +747,7 @@ func (e *ContextEnforcer) removePolicyCtx(ctx
context.Context, sec string, ptype
}
func (e *ContextEnforcer) removePoliciesCtx(ctx context.Context, sec string,
ptype string, rules [][]string) (bool, error) {
- ok, err := e.removePoliciesWithoutNotifyCtx(ctx, sec, ptype, rules)
+ ok, err := e.removePoliciesWithoutNotifyCtx(ctx, e.shouldPersist, sec,
ptype, rules)
if !ok || err != nil {
return ok, err
}
@@ -767,7 +767,7 @@ func (e *ContextEnforcer) removePoliciesCtx(ctx
context.Context, sec string, pty
// removeFilteredPolicy removes rules based on field filters from the current
policy.
func (e *ContextEnforcer) removeFilteredPolicyCtx(ctx context.Context, sec
string, ptype string, fieldIndex int, fieldValues []string) (bool, error) {
- ok, err := e.removeFilteredPolicyWithoutNotifyCtx(ctx, sec, ptype,
fieldIndex, fieldValues)
+ ok, err := e.removeFilteredPolicyWithoutNotifyCtx(ctx, e.shouldPersist,
sec, ptype, fieldIndex, fieldValues)
if !ok || err != nil {
return ok, err
}
diff --git a/internal_api.go b/internal_api.go
index 04883af..1381ccf 100644
--- a/internal_api.go
+++ b/internal_api.go
@@ -45,7 +45,11 @@ 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) {
+// shouldPersist reports whether the change must also be written to the
adapter.
+// It is nil when the change originates from another instance (a watcher
callback
+// through the Self* APIs), in which case the rule is already in the storage
and
+// writing it again would fail on databases with a unique constraint.
+func (e *Enforcer) addPolicyWithoutNotify(shouldPersist func() bool, sec
string, ptype string, rule []string) (bool, error) {
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.AddPolicies(sec, ptype,
[][]string{rule})
}
@@ -55,7 +59,7 @@ func (e *Enforcer) addPolicyWithoutNotify(sec string, ptype
string, rule []strin
return false, err
}
- if e.shouldPersist() {
+ if shouldPersist != nil && shouldPersist() {
if err = e.adapter.AddPolicy(sec, ptype, rule); err != nil {
if err.Error() != notImplemented {
return false, err
@@ -86,7 +90,7 @@ func (e *Enforcer) addPolicyWithoutNotify(sec string, ptype
string, rule []strin
// addPoliciesWithoutNotify adds rules to the current policy without notify
// 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) {
+func (e *Enforcer) addPoliciesWithoutNotify(shouldPersist func() bool, sec
string, ptype string, rules [][]string, autoRemoveRepeat bool) (bool, error) {
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.AddPolicies(sec, ptype, rules)
}
@@ -98,7 +102,7 @@ func (e *Enforcer) addPoliciesWithoutNotify(sec string,
ptype string, rules [][]
}
}
- if e.shouldPersist() {
+ if shouldPersist != nil && shouldPersist() {
if err := e.adapter.(persist.BatchAdapter).AddPolicies(sec,
ptype, rules); err != nil {
if err.Error() != notImplemented {
return false, err
@@ -132,12 +136,12 @@ 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) {
+func (e *Enforcer) removePolicyWithoutNotify(shouldPersist func() bool, sec
string, ptype string, rule []string) (bool, error) {
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.RemovePolicies(sec, ptype,
[][]string{rule})
}
- if e.shouldPersist() {
+ if shouldPersist != nil && shouldPersist() {
if err := e.adapter.RemovePolicy(sec, ptype, rule); err != nil {
if err.Error() != notImplemented {
return false, err
@@ -165,12 +169,12 @@ func (e *Enforcer) removePolicyWithoutNotify(sec string,
ptype string, rule []st
return ruleRemoved, nil
}
-func (e *Enforcer) updatePolicyWithoutNotify(sec string, ptype string, oldRule
[]string, newRule []string) (bool, error) {
+func (e *Enforcer) updatePolicyWithoutNotify(shouldPersist func() bool, sec
string, ptype string, oldRule []string, newRule []string) (bool, error) {
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.UpdatePolicy(sec, ptype, oldRule,
newRule)
}
- if e.shouldPersist() {
+ if shouldPersist != nil && shouldPersist() {
if err :=
e.adapter.(persist.UpdatableAdapter).UpdatePolicy(sec, ptype, oldRule,
newRule); err != nil {
if err.Error() != notImplemented {
return false, err
@@ -201,7 +205,7 @@ func (e *Enforcer) updatePolicyWithoutNotify(sec string,
ptype string, oldRule [
return ruleUpdated, nil
}
-func (e *Enforcer) updatePoliciesWithoutNotify(sec string, ptype string,
oldRules [][]string, newRules [][]string) (bool, error) {
+func (e *Enforcer) updatePoliciesWithoutNotify(shouldPersist func() bool, sec
string, ptype string, oldRules [][]string, newRules [][]string) (bool, error) {
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))
}
@@ -210,7 +214,7 @@ func (e *Enforcer) updatePoliciesWithoutNotify(sec string,
ptype string, oldRule
return true, e.dispatcher.UpdatePolicies(sec, ptype, oldRules,
newRules)
}
- if e.shouldPersist() {
+ if shouldPersist != nil && shouldPersist() {
if err :=
e.adapter.(persist.UpdatableAdapter).UpdatePolicies(sec, ptype, oldRules,
newRules); err != nil {
if err.Error() != notImplemented {
return false, err
@@ -243,7 +247,7 @@ 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) {
+func (e *Enforcer) removePoliciesWithoutNotify(shouldPersist func() bool, sec
string, ptype string, rules [][]string) (bool, error) {
if hasPolicies, err := e.model.HasPolicies(sec, ptype, rules);
!hasPolicies || err != nil {
return hasPolicies, err
}
@@ -252,7 +256,7 @@ func (e *Enforcer) removePoliciesWithoutNotify(sec string,
ptype string, rules [
return true, e.dispatcher.RemovePolicies(sec, ptype, rules)
}
- if e.shouldPersist() {
+ if shouldPersist != nil && shouldPersist() {
if err := e.adapter.(persist.BatchAdapter).RemovePolicies(sec,
ptype, rules); err != nil {
if err.Error() != notImplemented {
return false, err
@@ -280,7 +284,7 @@ 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) {
+func (e *Enforcer) removeFilteredPolicyWithoutNotify(shouldPersist func()
bool, sec string, ptype string, fieldIndex int, fieldValues []string) (bool,
error) {
if len(fieldValues) == 0 {
return false, Err.ErrInvalidFieldValuesParameter
}
@@ -289,7 +293,7 @@ func (e *Enforcer) removeFilteredPolicyWithoutNotify(sec
string, ptype string, f
return true, e.dispatcher.RemoveFilteredPolicy(sec, ptype,
fieldIndex, fieldValues...)
}
- if e.shouldPersist() {
+ if shouldPersist != nil && shouldPersist() {
if err := e.adapter.RemoveFilteredPolicy(sec, ptype,
fieldIndex, fieldValues...); err != nil {
if err.Error() != notImplemented {
return false, err
@@ -380,7 +384,7 @@ func (e *Enforcer) updateFilteredPoliciesWithoutNotify(sec
string, ptype string,
// addPolicy adds a rule to the current policy.
func (e *Enforcer) addPolicy(sec string, ptype string, rule []string) (bool,
error) {
ok, err := e.logPolicyOperation(log.EventAddPolicy, sec, rule, func()
(bool, error) {
- return e.addPolicyWithoutNotify(sec, ptype, rule)
+ return e.addPolicyWithoutNotify(e.shouldPersist, sec, ptype,
rule)
})
if !ok || err != nil {
@@ -404,7 +408,7 @@ func (e *Enforcer) addPolicy(sec string, ptype string, rule
[]string) (bool, err
// If autoRemoveRepeat == true, existing rules are automatically filtered
// Otherwise, false is returned directly.
func (e *Enforcer) addPolicies(sec string, ptype string, rules [][]string,
autoRemoveRepeat bool) (bool, error) {
- ok, err := e.addPoliciesWithoutNotify(sec, ptype, rules,
autoRemoveRepeat)
+ ok, err := e.addPoliciesWithoutNotify(e.shouldPersist, sec, ptype,
rules, autoRemoveRepeat)
if !ok || err != nil {
return ok, err
}
@@ -425,7 +429,7 @@ func (e *Enforcer) addPolicies(sec string, ptype string,
rules [][]string, autoR
// removePolicy removes a rule from the current policy.
func (e *Enforcer) removePolicy(sec string, ptype string, rule []string)
(bool, error) {
ok, err := e.logPolicyOperation(log.EventRemovePolicy, sec, rule,
func() (bool, error) {
- return e.removePolicyWithoutNotify(sec, ptype, rule)
+ return e.removePolicyWithoutNotify(e.shouldPersist, sec, ptype,
rule)
})
if !ok || err != nil {
@@ -446,7 +450,7 @@ func (e *Enforcer) removePolicy(sec string, ptype string,
rule []string) (bool,
}
func (e *Enforcer) updatePolicy(sec string, ptype string, oldRule []string,
newRule []string) (bool, error) {
- ok, err := e.updatePolicyWithoutNotify(sec, ptype, oldRule, newRule)
+ ok, err := e.updatePolicyWithoutNotify(e.shouldPersist, sec, ptype,
oldRule, newRule)
if !ok || err != nil {
return ok, err
}
@@ -465,7 +469,7 @@ func (e *Enforcer) updatePolicy(sec string, ptype string,
oldRule []string, newR
}
func (e *Enforcer) updatePolicies(sec string, ptype string, oldRules
[][]string, newRules [][]string) (bool, error) {
- ok, err := e.updatePoliciesWithoutNotify(sec, ptype, oldRules, newRules)
+ ok, err := e.updatePoliciesWithoutNotify(e.shouldPersist, sec, ptype,
oldRules, newRules)
if !ok || err != nil {
return ok, err
}
@@ -485,7 +489,7 @@ func (e *Enforcer) updatePolicies(sec string, ptype string,
oldRules [][]string,
// removePolicies removes rules from the current policy.
func (e *Enforcer) removePolicies(sec string, ptype string, rules [][]string)
(bool, error) {
- ok, err := e.removePoliciesWithoutNotify(sec, ptype, rules)
+ ok, err := e.removePoliciesWithoutNotify(e.shouldPersist, sec, ptype,
rules)
if !ok || err != nil {
return ok, err
}
@@ -505,7 +509,7 @@ func (e *Enforcer) removePolicies(sec string, ptype string,
rules [][]string) (b
// removeFilteredPolicy removes rules based on field filters from the current
policy.
func (e *Enforcer) removeFilteredPolicy(sec string, ptype string, fieldIndex
int, fieldValues []string) (bool, error) {
- ok, err := e.removeFilteredPolicyWithoutNotify(sec, ptype, fieldIndex,
fieldValues)
+ ok, err := e.removeFilteredPolicyWithoutNotify(e.shouldPersist, sec,
ptype, fieldIndex, fieldValues)
if !ok || err != nil {
return ok, err
}
diff --git a/management_api.go b/management_api.go
index 0491ee5..5279c9f 100644
--- a/management_api.go
+++ b/management_api.go
@@ -486,34 +486,52 @@ func (e *Enforcer) AddFunction(name string, function
govaluate.ExpressionFunctio
e.fm.AddFunction(name, function)
}
+// The Self* APIs apply a change that has already been made by another Casbin
+// instance to this instance's in-memory policy. They neither notify the
watcher
+// nor write to the adapter: the originating instance already persisted the
+// change, so persisting it again is at best a redundant write and at worst a
+// failure, e.g. a duplicate INSERT rejected by a unique constraint, which
would
+// leave this instance out of sync. Use AddPolicy() and friends to change both
+// the model and the storage.
+
+// SelfAddPolicy adds a rule to the current policy without notifying the
watcher or persisting it.
func (e *Enforcer) SelfAddPolicy(sec string, ptype string, rule []string)
(bool, error) {
- return e.addPolicyWithoutNotify(sec, ptype, rule)
+ return e.addPolicyWithoutNotify(nil, sec, ptype, rule)
}
+// SelfAddPolicies adds rules to the current policy without notifying the
watcher or persisting them.
func (e *Enforcer) SelfAddPolicies(sec string, ptype string, rules [][]string)
(bool, error) {
- return e.addPoliciesWithoutNotify(sec, ptype, rules, false)
+ return e.addPoliciesWithoutNotify(nil, sec, ptype, rules, false)
}
+// SelfAddPoliciesEx adds rules to the current policy without notifying the
watcher or persisting them,
+// existing rules are automatically filtered.
func (e *Enforcer) SelfAddPoliciesEx(sec string, ptype string, rules
[][]string) (bool, error) {
- return e.addPoliciesWithoutNotify(sec, ptype, rules, true)
+ return e.addPoliciesWithoutNotify(nil, sec, ptype, rules, true)
}
+// SelfRemovePolicy removes a rule from the current policy without notifying
the watcher or persisting it.
func (e *Enforcer) SelfRemovePolicy(sec string, ptype string, rule []string)
(bool, error) {
- return e.removePolicyWithoutNotify(sec, ptype, rule)
+ return e.removePolicyWithoutNotify(nil, sec, ptype, rule)
}
+// SelfRemovePolicies removes rules from the current policy without notifying
the watcher or persisting them.
func (e *Enforcer) SelfRemovePolicies(sec string, ptype string, rules
[][]string) (bool, error) {
- return e.removePoliciesWithoutNotify(sec, ptype, rules)
+ return e.removePoliciesWithoutNotify(nil, sec, ptype, rules)
}
+// SelfRemoveFilteredPolicy removes rules based on field filters from the
current policy
+// without notifying the watcher or persisting the change.
func (e *Enforcer) SelfRemoveFilteredPolicy(sec string, ptype string,
fieldIndex int, fieldValues ...string) (bool, error) {
- return e.removeFilteredPolicyWithoutNotify(sec, ptype, fieldIndex,
fieldValues)
+ return e.removeFilteredPolicyWithoutNotify(nil, sec, ptype, fieldIndex,
fieldValues)
}
+// SelfUpdatePolicy updates a rule in the current policy without notifying the
watcher or persisting it.
func (e *Enforcer) SelfUpdatePolicy(sec string, ptype string, oldRule, newRule
[]string) (bool, error) {
- return e.updatePolicyWithoutNotify(sec, ptype, oldRule, newRule)
+ return e.updatePolicyWithoutNotify(nil, sec, ptype, oldRule, newRule)
}
+// SelfUpdatePolicies updates rules in the current policy without notifying
the watcher or persisting them.
func (e *Enforcer) SelfUpdatePolicies(sec string, ptype string, oldRules,
newRules [][]string) (bool, error) {
- return e.updatePoliciesWithoutNotify(sec, ptype, oldRules, newRules)
+ return e.updatePoliciesWithoutNotify(nil, sec, ptype, oldRules,
newRules)
}
diff --git a/model/model_test.go b/model/model_test.go
index 1b3c8c1..3ba32a9 100644
--- a/model/model_test.go
+++ b/model/model_test.go
@@ -25,6 +25,8 @@ import (
"github.com/casbin/casbin/v3/config"
"github.com/casbin/casbin/v3/constant"
+ "github.com/casbin/casbin/v3/rbac"
+ defaultrolemanager
"github.com/casbin/casbin/v3/rbac/default-role-manager"
)
var (
@@ -162,3 +164,29 @@ func testModelToText(t *testing.T, mData, mExpected
string) {
}
}
}
+
+// TestModelCopyKeepsRoleManager is a regression test for
+// https://github.com/casbin/casbin/issues/1656 ("Undefined function g").
+//
+// Copy() must carry the role manager over to the new assertions. The enforcer
+// derives the g() matcher function from Assertion.RM, so a copy that loses it
+// makes the matcher fail to compile with "Undefined function g".
+func TestModelCopyKeepsRoleManager(t *testing.T) {
+ m, err := NewModelFromFile(filepath.Join("..", "examples",
"rbac_model.conf"))
+ if err != nil {
+ t.Fatalf("Failed to load model: %v", err)
+ }
+
+ rm := defaultrolemanager.NewRoleManagerImpl(10)
+ m["g"]["g"].RM = rm
+ m["g"]["g"].ParamsTokens = []string{"p_param"}
+
+ newModel := m.Copy()
+
+ if newModel["g"]["g"].RM != rbac.RoleManager(rm) {
+ t.Fatal("Copy() should carry the role manager over to the
copied assertion")
+ }
+ if len(newModel["g"]["g"].ParamsTokens) != 1 ||
newModel["g"]["g"].ParamsTokens[0] != "p_param" {
+ t.Fatalf("Copy() should carry ParamsTokens over, got %v",
newModel["g"]["g"].ParamsTokens)
+ }
+}
diff --git a/transaction_test.go b/transaction_test.go
index 555699f..9dcd518 100644
--- a/transaction_test.go
+++ b/transaction_test.go
@@ -338,3 +338,48 @@ func TestTransactionBuffer(t *testing.T) {
tx.Rollback()
}
+
+// TestTransactionKeepsRoleManagerAfterCommit is a regression test for
+// https://github.com/casbin/casbin/issues/1656 ("Undefined function g").
+//
+// Committing a transaction replaces the enforcer's model with a copy. Role
+// links are only rebuilt when the transaction touched the "g" section, so a
+// transaction that only changes "p" rules relies on the copy carrying over the
+// role manager. When Assertion.copy() dropped RM, the g() function disappeared
+// from the matcher's function map and Enforce() failed to even parse the
+// matcher, until LoadPolicy() was called by hand.
+func TestTransactionKeepsRoleManagerAfterCommit(t *testing.T) {
+ adapter := NewMockTransactionalAdapter()
+ e, err := NewTransactionalEnforcer("examples/rbac_model.conf", adapter)
+ if err != nil {
+ t.Fatalf("Failed to create transactional enforcer: %v", err)
+ }
+
+ if _, err := e.AddGroupingPolicy("alice", "admin"); err != nil {
+ t.Fatalf("Failed to add grouping policy: %v", err)
+ }
+
+ // A transaction that only touches the "p" section, so no role link
rebuild.
+ tx, err := e.BeginTransaction(context.Background())
+ if err != nil {
+ t.Fatalf("Failed to begin transaction: %v", err)
+ }
+ if _, err := tx.AddPolicy("admin", "data1", "read"); err != nil {
+ t.Fatalf("Failed to add policy in transaction: %v", err)
+ }
+ if err := tx.Commit(); err != nil {
+ t.Fatalf("Failed to commit transaction: %v", err)
+ }
+
+ if e.GetModel()["g"]["g"].RM == nil {
+ t.Fatal("The role manager should survive the model copy made on
commit")
+ }
+
+ ok, err := e.Enforce("alice", "data1", "read")
+ if err != nil {
+ t.Fatalf("Enforce failed after commit: %v", err)
+ }
+ if !ok {
+ t.Fatal("alice should inherit read access to data1 from the
admin role")
+ }
+}
diff --git a/watcher_self_sync_test.go b/watcher_self_sync_test.go
new file mode 100644
index 0000000..d70e079
--- /dev/null
+++ b/watcher_self_sync_test.go
@@ -0,0 +1,275 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package casbin
+
+import (
+ "errors"
+ "strings"
+ "sync"
+ "testing"
+
+ "github.com/casbin/casbin/v3/model"
+ "github.com/casbin/casbin/v3/persist"
+)
+
+// uniqueConstraintAdapter mimics a SQL adapter whose policy table carries a
unique
+// index over (ptype, v0, v1, ...), i.e. what a user gets by adding a unique
index to
+// the casbin_rule table of gorm-adapter. Inserting a row that is already
stored fails
+// instead of silently succeeding.
+type uniqueConstraintAdapter struct {
+ mutex sync.Mutex
+ rows []string
+}
+
+var errDuplicateEntry = errors.New("Error 1062: Duplicate entry for key
'idx_casbin_rule'")
+
+func (a *uniqueConstraintAdapter) key(ptype string, rule []string) string {
+ return ptype + "," + strings.Join(rule, ",")
+}
+
+func (a *uniqueConstraintAdapter) LoadPolicy(m model.Model) error {
+ a.mutex.Lock()
+ defer a.mutex.Unlock()
+ for _, row := range a.rows {
+ if err := persist.LoadPolicyLine(row, m); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (a *uniqueConstraintAdapter) SavePolicy(model.Model) error {
+ return errors.New("not implemented")
+}
+
+func (a *uniqueConstraintAdapter) AddPolicy(_ string, ptype string, rule
[]string) error {
+ a.mutex.Lock()
+ defer a.mutex.Unlock()
+ key := a.key(ptype, rule)
+ for _, row := range a.rows {
+ if row == key {
+ return errDuplicateEntry
+ }
+ }
+ a.rows = append(a.rows, key)
+ return nil
+}
+
+func (a *uniqueConstraintAdapter) AddPolicies(sec string, ptype string, rules
[][]string) error {
+ for _, rule := range rules {
+ if err := a.AddPolicy(sec, ptype, rule); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (a *uniqueConstraintAdapter) RemovePolicy(_ string, ptype string, rule
[]string) error {
+ a.mutex.Lock()
+ defer a.mutex.Unlock()
+ key := a.key(ptype, rule)
+ for i, row := range a.rows {
+ if row == key {
+ a.rows = append(a.rows[:i], a.rows[i+1:]...)
+ return nil
+ }
+ }
+ return nil
+}
+
+func (a *uniqueConstraintAdapter) RemovePolicies(sec string, ptype string,
rules [][]string) error {
+ for _, rule := range rules {
+ if err := a.RemovePolicy(sec, ptype, rule); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (a *uniqueConstraintAdapter) RemoveFilteredPolicy(string, string, int,
...string) error {
+ return errors.New("not implemented")
+}
+
+func (a *uniqueConstraintAdapter) UpdatePolicy(sec string, ptype string,
oldRule, newRule []string) error {
+ if err := a.RemovePolicy(sec, ptype, oldRule); err != nil {
+ return err
+ }
+ return a.AddPolicy(sec, ptype, newRule)
+}
+
+func (a *uniqueConstraintAdapter) UpdatePolicies(sec string, ptype string,
oldRules, newRules [][]string) error {
+ for i := range oldRules {
+ if err := a.UpdatePolicy(sec, ptype, oldRules[i], newRules[i]);
err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (a *uniqueConstraintAdapter) UpdateFilteredPolicies(string, string,
[][]string, int, ...string) ([][]string, error) {
+ return nil, errors.New("not implemented")
+}
+
+func (a *uniqueConstraintAdapter) rowCount() int {
+ a.mutex.Lock()
+ defer a.mutex.Unlock()
+ return len(a.rows)
+}
+
+// newInstance builds an enforcer that shares the given storage, standing in
for one
+// process of a multi-instance deployment.
+func newInstance(t *testing.T, adapter *uniqueConstraintAdapter) *Enforcer {
+ t.Helper()
+ e, err := NewEnforcer("examples/rbac_model.conf", adapter)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return e
+}
+
+// TestSelfAddPolicyDoesNotPersist covers
https://github.com/casbin/casbin/issues/1581:
+// instance A adds a rule and notifies the cluster, then instance B applies
the very
+// same rule through the watcher callback. B must not write the rule to the
shared
+// storage a second time, otherwise the unique index rejects the insert and B
silently
+// keeps an outdated model.
+func TestSelfAddPolicyDoesNotPersist(t *testing.T) {
+ adapter := &uniqueConstraintAdapter{}
+ instanceA := newInstance(t, adapter)
+ instanceB := newInstance(t, adapter)
+
+ if _, err := instanceA.AddPolicy("alice", "data1", "read"); err != nil {
+ t.Fatal(err)
+ }
+
+ ok, err := instanceB.SelfAddPolicy("p", "p", []string{"alice", "data1",
"read"})
+ if err != nil {
+ t.Fatalf("SelfAddPolicy should not hit the storage, got: %v",
err)
+ }
+ if !ok {
+ t.Fatal("SelfAddPolicy should report the rule as added")
+ }
+
+ if hasPolicy, err := instanceB.HasPolicy("alice", "data1", "read"); err
!= nil {
+ t.Fatal(err)
+ } else if !hasPolicy {
+ t.Fatal("the rule should be in the model of instance B")
+ }
+ if allowed, err := instanceB.Enforce("alice", "data1", "read"); err !=
nil {
+ t.Fatal(err)
+ } else if !allowed {
+ t.Fatal("instance B should enforce the rule it was notified
about")
+ }
+ if count := adapter.rowCount(); count != 1 {
+ t.Fatalf("the storage should hold exactly the row written by
instance A, got %d rows", count)
+ }
+}
+
+func TestSelfAddPoliciesDoesNotPersist(t *testing.T) {
+ adapter := &uniqueConstraintAdapter{}
+ instanceA := newInstance(t, adapter)
+ instanceB := newInstance(t, adapter)
+
+ rules := [][]string{{"alice", "data1", "read"}, {"bob", "data2",
"write"}}
+ if _, err := instanceA.AddPolicies(rules); err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err := instanceB.SelfAddPolicies("p", "p", rules); err != nil {
+ t.Fatalf("SelfAddPolicies should not hit the storage, got: %v",
err)
+ }
+ if _, err := instanceB.SelfAddPoliciesEx("p", "p", rules); err != nil {
+ t.Fatalf("SelfAddPoliciesEx should not hit the storage, got:
%v", err)
+ }
+
+ for _, rule := range rules {
+ if hasPolicy, err := instanceB.HasPolicy(rule); err != nil {
+ t.Fatal(err)
+ } else if !hasPolicy {
+ t.Fatalf("the rule %v should be in the model of
instance B", rule)
+ }
+ }
+ if count := adapter.rowCount(); count != 2 {
+ t.Fatalf("the storage should hold exactly the rows written by
instance A, got %d rows", count)
+ }
+}
+
+// TestSelfRemoveAndUpdateDoNotPersist makes sure the whole Self* family
leaves the
+// shared storage alone, not only the add path that issue #1581 reported.
+func TestSelfRemoveAndUpdateDoNotPersist(t *testing.T) {
+ adapter := &uniqueConstraintAdapter{}
+ instanceA := newInstance(t, adapter)
+ instanceB := newInstance(t, adapter)
+
+ if _, err := instanceA.AddPolicy("alice", "data1", "read"); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := instanceB.SelfAddPolicy("p", "p", []string{"alice",
"data1", "read"}); err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err := instanceA.UpdatePolicy([]string{"alice", "data1", "read"},
[]string{"alice", "data1", "write"}); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := instanceB.SelfUpdatePolicy("p", "p", []string{"alice",
"data1", "read"}, []string{"alice", "data1", "write"}); err != nil {
+ t.Fatal(err)
+ }
+ if count := adapter.rowCount(); count != 1 {
+ t.Fatalf("expected 1 row after the update, got %d", count)
+ }
+
+ if _, err := instanceA.RemovePolicy("alice", "data1", "write"); err !=
nil {
+ t.Fatal(err)
+ }
+ if _, err := instanceB.SelfRemovePolicy("p", "p", []string{"alice",
"data1", "write"}); err != nil {
+ t.Fatal(err)
+ }
+ if hasPolicy, err := instanceB.HasPolicy("alice", "data1", "write");
err != nil {
+ t.Fatal(err)
+ } else if hasPolicy {
+ t.Fatal("the rule should be gone from the model of instance B")
+ }
+ if count := adapter.rowCount(); count != 0 {
+ t.Fatalf("expected an empty storage, got %d rows", count)
+ }
+}
+
+// TestAddPolicyStillPersists guards the regular path: only the Self* APIs
skip the
+// adapter, AddPolicy() must keep writing through and keep reporting storage
errors.
+func TestAddPolicyStillPersists(t *testing.T) {
+ adapter := &uniqueConstraintAdapter{}
+ instanceA := newInstance(t, adapter)
+ instanceB := newInstance(t, adapter)
+
+ if _, err := instanceA.AddPolicy("alice", "data1", "read"); err != nil {
+ t.Fatal(err)
+ }
+ if count := adapter.rowCount(); count != 1 {
+ t.Fatalf("AddPolicy should have written 1 row, got %d", count)
+ }
+
+ // A rule that another instance already inserted is still a storage
error for the
+ // regular path, since the caller asked for the rule to be written.
+ if _, err := instanceB.AddPolicy("alice", "data1", "read");
!errors.Is(err, errDuplicateEntry) {
+ t.Fatalf("AddPolicy should surface the storage error, got: %v",
err)
+ }
+ if hasPolicy, err := instanceB.HasPolicy("alice", "data1", "read"); err
!= nil {
+ t.Fatal(err)
+ } else if hasPolicy {
+ t.Fatal("a rule that failed to persist must not end up in the
model")
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]