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 34297a1 feat: add grouping, batch update and filtered remove APIs to
Transaction
34297a1 is described below
commit 34297a19bd65cd52d40d146452c20aea7b45c205
Author: Yang Luo <[email protected]>
AuthorDate: Fri Aug 21 16:53:50 2026 +0800
feat: add grouping, batch update and filtered remove APIs to Transaction
---
transaction.go | 401 ++++++++++++++++++++++++++++------------------------
transaction_test.go | 238 +++++++++++++++++++++++++++++++
2 files changed, 451 insertions(+), 188 deletions(-)
diff --git a/transaction.go b/transaction.go
index 387432c..eb08512 100644
--- a/transaction.go
+++ b/transaction.go
@@ -20,6 +20,7 @@ package casbin
import (
"context"
"errors"
+ "fmt"
"sync"
"time"
@@ -70,12 +71,6 @@ func (tx *Transaction) GetAdapter() persist.Adapter {
return tx.txContext.GetAdapter()
}
-// AddPolicy adds a policy within the transaction.
-// The policy is buffered and will be applied when the transaction is
committed.
-func (tx *Transaction) AddPolicy(params ...interface{}) (bool, error) {
- return tx.AddNamedPolicy("p", params...)
-}
-
// buildRuleFromParams converts parameters to a rule slice.
func (tx *Transaction) buildRuleFromParams(params ...interface{}) []string {
if len(params) == 1 {
@@ -101,72 +96,86 @@ func (tx *Transaction) checkTransactionStatus() error {
return nil
}
-// AddNamedPolicy adds a named policy within the transaction.
-// The policy is buffered and will be applied when the transaction is
committed.
-func (tx *Transaction) AddNamedPolicy(ptype string, params ...interface{})
(bool, error) {
+// getBufferedModel returns the model with all buffered operations applied.
+// The caller must hold tx.mutex.
+func (tx *Transaction) getBufferedModel() (model.Model, error) {
+ if err := tx.checkTransactionStatus(); err != nil {
+ return nil, err
+ }
+
+ return tx.buffer.ApplyOperationsToModel(tx.buffer.GetModelSnapshot())
+}
+
+// bufferAdd buffers an add operation for the rules that are not present yet.
+// Rules that already exist in the buffered model are skipped, and false is
+// returned when nothing is left to add.
+func (tx *Transaction) bufferAdd(sec string, ptype string, rules [][]string)
(bool, error) {
tx.mutex.Lock()
defer tx.mutex.Unlock()
- if err := tx.checkTransactionStatus(); err != nil {
- return false, err
+ if len(rules) == 0 {
+ if err := tx.checkTransactionStatus(); err != nil {
+ return false, err
+ }
+ return false, nil
}
- rule := tx.buildRuleFromParams(params...)
-
- // Check if policy already exists in the buffered model.
- bufferedModel, err :=
tx.buffer.ApplyOperationsToModel(tx.buffer.GetModelSnapshot())
+ bufferedModel, err := tx.getBufferedModel()
if err != nil {
return false, err
}
- hasPolicy, err := bufferedModel.HasPolicy("p", ptype, rule)
- if hasPolicy || err != nil {
- return false, err
+ var validRules [][]string
+ for _, rule := range rules {
+ hasPolicy, err := bufferedModel.HasPolicy(sec, ptype, rule)
+ if err != nil {
+ return false, err
+ }
+ if !hasPolicy {
+ validRules = append(validRules, rule)
+ }
+ }
+
+ if len(validRules) == 0 {
+ return false, nil
}
- // Add operation to buffer.
- op := persist.PolicyOperation{
+ tx.buffer.AddOperation(persist.PolicyOperation{
Type: persist.OperationAdd,
- Section: "p",
+ Section: sec,
PolicyType: ptype,
- Rules: [][]string{rule},
- }
- tx.buffer.AddOperation(op)
+ Rules: validRules,
+ })
return true, nil
}
-// AddPolicies adds multiple policies within the transaction.
-func (tx *Transaction) AddPolicies(rules [][]string) (bool, error) {
- return tx.AddNamedPolicies("p", rules)
-}
-
-// AddNamedPolicies adds multiple named policies within the transaction.
-func (tx *Transaction) AddNamedPolicies(ptype string, rules [][]string) (bool,
error) {
+// bufferRemove buffers a remove operation for the rules that are present.
+// Rules that are missing from the buffered model are skipped, and false is
+// returned when nothing is left to remove.
+func (tx *Transaction) bufferRemove(sec string, ptype string, rules
[][]string) (bool, error) {
tx.mutex.Lock()
defer tx.mutex.Unlock()
- if err := tx.checkTransactionStatus(); err != nil {
- return false, err
- }
-
if len(rules) == 0 {
+ if err := tx.checkTransactionStatus(); err != nil {
+ return false, err
+ }
return false, nil
}
- // Check if any policies already exist in the buffered model.
- bufferedModel, err :=
tx.buffer.ApplyOperationsToModel(tx.buffer.GetModelSnapshot())
+ bufferedModel, err := tx.getBufferedModel()
if err != nil {
return false, err
}
var validRules [][]string
for _, rule := range rules {
- hasPolicy, err := bufferedModel.HasPolicy("p", ptype, rule)
+ hasPolicy, err := bufferedModel.HasPolicy(sec, ptype, rule)
if err != nil {
return false, err
}
- if !hasPolicy {
+ if hasPolicy {
validRules = append(validRules, rule)
}
}
@@ -175,108 +184,158 @@ func (tx *Transaction) AddNamedPolicies(ptype string,
rules [][]string) (bool, e
return false, nil
}
- // Add operation to buffer.
- op := persist.PolicyOperation{
- Type: persist.OperationAdd,
- Section: "p",
+ tx.buffer.AddOperation(persist.PolicyOperation{
+ Type: persist.OperationRemove,
+ Section: sec,
PolicyType: ptype,
Rules: validRules,
- }
- tx.buffer.AddOperation(op)
+ })
return true, nil
}
-// RemovePolicy removes a policy within the transaction.
-func (tx *Transaction) RemovePolicy(params ...interface{}) (bool, error) {
- return tx.RemoveNamedPolicy("p", params...)
-}
-
-// RemoveNamedPolicy removes a named policy within the transaction.
-func (tx *Transaction) RemoveNamedPolicy(ptype string, params ...interface{})
(bool, error) {
+// bufferRemoveFiltered resolves the filter against the buffered model and
+// buffers the matched rules for removal. Resolving the filter here keeps the
+// operation a plain remove, so it works with every adapter and takes part in
+// conflict detection like any other remove.
+func (tx *Transaction) bufferRemoveFiltered(sec string, ptype string,
fieldIndex int, fieldValues []string) (bool, error) {
tx.mutex.Lock()
defer tx.mutex.Unlock()
- if err := tx.checkTransactionStatus(); err != nil {
+ bufferedModel, err := tx.getBufferedModel()
+ if err != nil {
return false, err
}
- rule := tx.buildRuleFromParams(params...)
-
- // Check if policy exists in the buffered model.
- bufferedModel, err :=
tx.buffer.ApplyOperationsToModel(tx.buffer.GetModelSnapshot())
+ matched, err := bufferedModel.GetFilteredPolicy(sec, ptype, fieldIndex,
fieldValues...)
if err != nil {
return false, err
}
+ if len(matched) == 0 {
+ return false, nil
+ }
- hasPolicy, err := bufferedModel.HasPolicy("p", ptype, rule)
- if !hasPolicy || err != nil {
- return false, err
+ // The matched rules belong to a throwaway model copy, so keep our own
copy.
+ rules := make([][]string, 0, len(matched))
+ for _, rule := range matched {
+ rules = append(rules, append([]string(nil), rule...))
}
- // Add operation to buffer.
- op := persist.PolicyOperation{
+ tx.buffer.AddOperation(persist.PolicyOperation{
Type: persist.OperationRemove,
- Section: "p",
+ Section: sec,
PolicyType: ptype,
- Rules: [][]string{rule},
- }
- tx.buffer.AddOperation(op)
+ Rules: rules,
+ })
return true, nil
}
-// RemovePolicies removes multiple policies within the transaction.
-func (tx *Transaction) RemovePolicies(rules [][]string) (bool, error) {
- return tx.RemoveNamedPolicies("p", rules)
-}
-
-// RemoveNamedPolicies removes multiple named policies within the transaction.
-func (tx *Transaction) RemoveNamedPolicies(ptype string, rules [][]string)
(bool, error) {
+// bufferUpdate buffers an update operation. The update is all-or-nothing: it
is
+// buffered only if every old rule exists and no new rule exists yet.
+func (tx *Transaction) bufferUpdate(sec string, ptype string, oldRules
[][]string, newRules [][]string) (bool, error) {
tx.mutex.Lock()
defer tx.mutex.Unlock()
- if err := tx.checkTransactionStatus(); err != nil {
- return false, err
+ if len(oldRules) != len(newRules) {
+ 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))
}
- if len(rules) == 0 {
+ if len(oldRules) == 0 {
+ if err := tx.checkTransactionStatus(); err != nil {
+ return false, err
+ }
return false, nil
}
- // Check if policies exist in the buffered model.
- bufferedModel, err :=
tx.buffer.ApplyOperationsToModel(tx.buffer.GetModelSnapshot())
+ bufferedModel, err := tx.getBufferedModel()
if err != nil {
return false, err
}
- var validRules [][]string
- for _, rule := range rules {
- hasPolicy, err := bufferedModel.HasPolicy("p", ptype, rule)
+ for i, oldRule := range oldRules {
+ hasOldPolicy, err := bufferedModel.HasPolicy(sec, ptype,
oldRule)
if err != nil {
return false, err
}
- if hasPolicy {
- validRules = append(validRules, rule)
+ if !hasOldPolicy {
+ return false, nil
}
- }
- if len(validRules) == 0 {
- return false, nil
+ hasNewPolicy, err := bufferedModel.HasPolicy(sec, ptype,
newRules[i])
+ if err != nil {
+ return false, err
+ }
+ if hasNewPolicy {
+ return false, nil
+ }
}
- // Add operation to buffer.
- op := persist.PolicyOperation{
- Type: persist.OperationRemove,
- Section: "p",
+ tx.buffer.AddOperation(persist.PolicyOperation{
+ Type: persist.OperationUpdate,
+ Section: sec,
PolicyType: ptype,
- Rules: validRules,
- }
- tx.buffer.AddOperation(op)
+ Rules: newRules,
+ OldRules: oldRules,
+ })
return true, nil
}
+// AddPolicy adds a policy within the transaction.
+// The policy is buffered and will be applied when the transaction is
committed.
+func (tx *Transaction) AddPolicy(params ...interface{}) (bool, error) {
+ return tx.AddNamedPolicy("p", params...)
+}
+
+// AddNamedPolicy adds a named policy within the transaction.
+// The policy is buffered and will be applied when the transaction is
committed.
+func (tx *Transaction) AddNamedPolicy(ptype string, params ...interface{})
(bool, error) {
+ return tx.bufferAdd("p", ptype,
[][]string{tx.buildRuleFromParams(params...)})
+}
+
+// AddPolicies adds multiple policies within the transaction.
+func (tx *Transaction) AddPolicies(rules [][]string) (bool, error) {
+ return tx.AddNamedPolicies("p", rules)
+}
+
+// AddNamedPolicies adds multiple named policies within the transaction.
+func (tx *Transaction) AddNamedPolicies(ptype string, rules [][]string) (bool,
error) {
+ return tx.bufferAdd("p", ptype, rules)
+}
+
+// RemovePolicy removes a policy within the transaction.
+func (tx *Transaction) RemovePolicy(params ...interface{}) (bool, error) {
+ return tx.RemoveNamedPolicy("p", params...)
+}
+
+// RemoveNamedPolicy removes a named policy within the transaction.
+func (tx *Transaction) RemoveNamedPolicy(ptype string, params ...interface{})
(bool, error) {
+ return tx.bufferRemove("p", ptype,
[][]string{tx.buildRuleFromParams(params...)})
+}
+
+// RemovePolicies removes multiple policies within the transaction.
+func (tx *Transaction) RemovePolicies(rules [][]string) (bool, error) {
+ return tx.RemoveNamedPolicies("p", rules)
+}
+
+// RemoveNamedPolicies removes multiple named policies within the transaction.
+func (tx *Transaction) RemoveNamedPolicies(ptype string, rules [][]string)
(bool, error) {
+ return tx.bufferRemove("p", ptype, rules)
+}
+
+// RemoveFilteredPolicy removes the policies that match the filter within the
transaction.
+// An empty string in fieldValues means "match any value" for that field.
+func (tx *Transaction) RemoveFilteredPolicy(fieldIndex int, fieldValues
...string) (bool, error) {
+ return tx.RemoveFilteredNamedPolicy("p", fieldIndex, fieldValues...)
+}
+
+// RemoveFilteredNamedPolicy removes the named policies that match the filter
within the transaction.
+// An empty string in fieldValues means "match any value" for that field.
+func (tx *Transaction) RemoveFilteredNamedPolicy(ptype string, fieldIndex int,
fieldValues ...string) (bool, error) {
+ return tx.bufferRemoveFiltered("p", ptype, fieldIndex, fieldValues)
+}
+
// UpdatePolicy updates a policy within the transaction.
func (tx *Transaction) UpdatePolicy(oldPolicy []string, newPolicy []string)
(bool, error) {
return tx.UpdateNamedPolicy("p", oldPolicy, newPolicy)
@@ -284,46 +343,19 @@ func (tx *Transaction) UpdatePolicy(oldPolicy []string,
newPolicy []string) (boo
// UpdateNamedPolicy updates a named policy within the transaction.
func (tx *Transaction) UpdateNamedPolicy(ptype string, oldPolicy []string,
newPolicy []string) (bool, error) {
- tx.mutex.Lock()
- defer tx.mutex.Unlock()
-
- if err := tx.checkTransactionStatus(); err != nil {
- return false, err
- }
-
- // Check if old policy exists and new policy doesn't exist.
- bufferedModel, err :=
tx.buffer.ApplyOperationsToModel(tx.buffer.GetModelSnapshot())
- if err != nil {
- return false, err
- }
-
- hasOldPolicy, err := bufferedModel.HasPolicy("p", ptype, oldPolicy)
- if err != nil {
- return false, err
- }
- if !hasOldPolicy {
- return false, nil
- }
-
- hasNewPolicy, errNew := bufferedModel.HasPolicy("p", ptype, newPolicy)
- if errNew != nil {
- return false, errNew
- }
- if hasNewPolicy {
- return false, nil
- }
+ return tx.bufferUpdate("p", ptype, [][]string{oldPolicy},
[][]string{newPolicy})
+}
- // Add operation to buffer.
- op := persist.PolicyOperation{
- Type: persist.OperationUpdate,
- Section: "p",
- PolicyType: ptype,
- Rules: [][]string{newPolicy},
- OldRules: [][]string{oldPolicy},
- }
- tx.buffer.AddOperation(op)
+// UpdatePolicies updates multiple policies within the transaction.
+// Nothing is buffered unless every old policy exists and no new policy exists
yet.
+func (tx *Transaction) UpdatePolicies(oldPolicies [][]string, newPolicies
[][]string) (bool, error) {
+ return tx.UpdateNamedPolicies("p", oldPolicies, newPolicies)
+}
- return true, nil
+// UpdateNamedPolicies updates multiple named policies within the transaction.
+// Nothing is buffered unless every old policy exists and no new policy exists
yet.
+func (tx *Transaction) UpdateNamedPolicies(ptype string, oldPolicies
[][]string, newPolicies [][]string) (bool, error) {
+ return tx.bufferUpdate("p", ptype, oldPolicies, newPolicies)
}
// AddGroupingPolicy adds a grouping policy within the transaction.
@@ -333,36 +365,17 @@ func (tx *Transaction) AddGroupingPolicy(params
...interface{}) (bool, error) {
// AddNamedGroupingPolicy adds a named grouping policy within the transaction.
func (tx *Transaction) AddNamedGroupingPolicy(ptype string, params
...interface{}) (bool, error) {
- tx.mutex.Lock()
- defer tx.mutex.Unlock()
-
- if err := tx.checkTransactionStatus(); err != nil {
- return false, err
- }
-
- rule := tx.buildRuleFromParams(params...)
-
- // Check if grouping policy already exists in the buffered model.
- bufferedModel, err :=
tx.buffer.ApplyOperationsToModel(tx.buffer.GetModelSnapshot())
- if err != nil {
- return false, err
- }
-
- hasPolicy, err := bufferedModel.HasPolicy("g", ptype, rule)
- if hasPolicy || err != nil {
- return false, err
- }
+ return tx.bufferAdd("g", ptype,
[][]string{tx.buildRuleFromParams(params...)})
+}
- // Add operation to buffer.
- op := persist.PolicyOperation{
- Type: persist.OperationAdd,
- Section: "g",
- PolicyType: ptype,
- Rules: [][]string{rule},
- }
- tx.buffer.AddOperation(op)
+// AddGroupingPolicies adds multiple grouping policies within the transaction.
+func (tx *Transaction) AddGroupingPolicies(rules [][]string) (bool, error) {
+ return tx.AddNamedGroupingPolicies("g", rules)
+}
- return true, nil
+// AddNamedGroupingPolicies adds multiple named grouping policies within the
transaction.
+func (tx *Transaction) AddNamedGroupingPolicies(ptype string, rules
[][]string) (bool, error) {
+ return tx.bufferAdd("g", ptype, rules)
}
// RemoveGroupingPolicy removes a grouping policy within the transaction.
@@ -372,36 +385,52 @@ func (tx *Transaction) RemoveGroupingPolicy(params
...interface{}) (bool, error)
// RemoveNamedGroupingPolicy removes a named grouping policy within the
transaction.
func (tx *Transaction) RemoveNamedGroupingPolicy(ptype string, params
...interface{}) (bool, error) {
- tx.mutex.Lock()
- defer tx.mutex.Unlock()
+ return tx.bufferRemove("g", ptype,
[][]string{tx.buildRuleFromParams(params...)})
+}
- if err := tx.checkTransactionStatus(); err != nil {
- return false, err
- }
+// RemoveGroupingPolicies removes multiple grouping policies within the
transaction.
+func (tx *Transaction) RemoveGroupingPolicies(rules [][]string) (bool, error) {
+ return tx.RemoveNamedGroupingPolicies("g", rules)
+}
- rule := tx.buildRuleFromParams(params...)
+// RemoveNamedGroupingPolicies removes multiple named grouping policies within
the transaction.
+func (tx *Transaction) RemoveNamedGroupingPolicies(ptype string, rules
[][]string) (bool, error) {
+ return tx.bufferRemove("g", ptype, rules)
+}
- // Check if grouping policy exists in the buffered model.
- bufferedModel, err :=
tx.buffer.ApplyOperationsToModel(tx.buffer.GetModelSnapshot())
- if err != nil {
- return false, err
- }
+// RemoveFilteredGroupingPolicy removes the grouping policies that match the
filter within the transaction.
+// An empty string in fieldValues means "match any value" for that field.
+func (tx *Transaction) RemoveFilteredGroupingPolicy(fieldIndex int,
fieldValues ...string) (bool, error) {
+ return tx.RemoveFilteredNamedGroupingPolicy("g", fieldIndex,
fieldValues...)
+}
- hasPolicy, err := bufferedModel.HasPolicy("g", ptype, rule)
- if !hasPolicy || err != nil {
- return false, err
- }
+// RemoveFilteredNamedGroupingPolicy removes the named grouping policies that
match
+// the filter within the transaction.
+// An empty string in fieldValues means "match any value" for that field.
+func (tx *Transaction) RemoveFilteredNamedGroupingPolicy(ptype string,
fieldIndex int, fieldValues ...string) (bool, error) {
+ return tx.bufferRemoveFiltered("g", ptype, fieldIndex, fieldValues)
+}
- // Add operation to buffer.
- op := persist.PolicyOperation{
- Type: persist.OperationRemove,
- Section: "g",
- PolicyType: ptype,
- Rules: [][]string{rule},
- }
- tx.buffer.AddOperation(op)
+// UpdateGroupingPolicy updates a grouping policy within the transaction.
+func (tx *Transaction) UpdateGroupingPolicy(oldRule []string, newRule
[]string) (bool, error) {
+ return tx.UpdateNamedGroupingPolicy("g", oldRule, newRule)
+}
- return true, nil
+// UpdateNamedGroupingPolicy updates a named grouping policy within the
transaction.
+func (tx *Transaction) UpdateNamedGroupingPolicy(ptype string, oldRule
[]string, newRule []string) (bool, error) {
+ return tx.bufferUpdate("g", ptype, [][]string{oldRule},
[][]string{newRule})
+}
+
+// UpdateGroupingPolicies updates multiple grouping policies within the
transaction.
+// Nothing is buffered unless every old rule exists and no new rule exists yet.
+func (tx *Transaction) UpdateGroupingPolicies(oldRules [][]string, newRules
[][]string) (bool, error) {
+ return tx.UpdateNamedGroupingPolicies("g", oldRules, newRules)
+}
+
+// UpdateNamedGroupingPolicies updates multiple named grouping policies within
the transaction.
+// Nothing is buffered unless every old rule exists and no new rule exists yet.
+func (tx *Transaction) UpdateNamedGroupingPolicies(ptype string, oldRules
[][]string, newRules [][]string) (bool, error) {
+ return tx.bufferUpdate("g", ptype, oldRules, newRules)
}
// GetBufferedModel returns the model as it would look after applying all
buffered operations.
@@ -410,11 +439,7 @@ func (tx *Transaction) GetBufferedModel() (model.Model,
error) {
tx.mutex.RLock()
defer tx.mutex.RUnlock()
- if err := tx.checkTransactionStatus(); err != nil {
- return nil, err
- }
-
- return tx.buffer.ApplyOperationsToModel(tx.buffer.GetModelSnapshot())
+ return tx.getBufferedModel()
}
// HasOperations returns true if the transaction has any buffered operations.
diff --git a/transaction_test.go b/transaction_test.go
index 555699f..466d65c 100644
--- a/transaction_test.go
+++ b/transaction_test.go
@@ -338,3 +338,241 @@ func TestTransactionBuffer(t *testing.T) {
tx.Rollback()
}
+
+// newTestTransactionalEnforcer creates a transactional enforcer backed by the
mock adapter.
+func newTestTransactionalEnforcer(t *testing.T) *TransactionalEnforcer {
+ t.Helper()
+
+ adapter := NewMockTransactionalAdapter()
+ e, err := NewTransactionalEnforcer("examples/rbac_model.conf", adapter)
+ if err != nil {
+ t.Fatalf("Failed to create transactional enforcer: %v", err)
+ }
+ adapter.Enforcer = e.Enforcer
+
+ return e
+}
+
+// hasPolicy reports whether the enforcer model holds the given policy rule.
+func hasPolicy(t *testing.T, e *TransactionalEnforcer, params ...interface{})
bool {
+ t.Helper()
+
+ ok, err := e.HasPolicy(params...)
+ if err != nil {
+ t.Fatalf("Failed to check policy %v: %v", params, err)
+ }
+ return ok
+}
+
+// hasGroupingPolicy reports whether the enforcer model holds the given
grouping policy rule.
+func hasGroupingPolicy(t *testing.T, e *TransactionalEnforcer, params
...interface{}) bool {
+ t.Helper()
+
+ ok, err := e.HasGroupingPolicy(params...)
+ if err != nil {
+ t.Fatalf("Failed to check grouping policy %v: %v", params, err)
+ }
+ return ok
+}
+
+// Test batch grouping policy operations within a transaction.
+func TestTransactionGroupingPolicies(t *testing.T) {
+ e := newTestTransactionalEnforcer(t)
+ ctx := context.Background()
+
+ err := e.WithTransaction(ctx, func(tx *Transaction) error {
+ if ok, err := tx.AddPolicy("admin", "data1", "read"); !ok ||
err != nil {
+ t.Fatalf("Failed to add policy in transaction: %v", err)
+ }
+ if ok, err := tx.AddGroupingPolicies([][]string{{"alice",
"admin"}, {"bob", "admin"}}); !ok || err != nil {
+ t.Fatalf("Failed to add grouping policies in
transaction: %v", err)
+ }
+ return nil
+ })
+ if err != nil {
+ t.Fatalf("Failed to commit transaction: %v", err)
+ }
+
+ if !hasGroupingPolicy(t, e, "alice", "admin") || !hasGroupingPolicy(t,
e, "bob", "admin") {
+ t.Fatal("Both grouping policies should have been added")
+ }
+
+ // Role links must have been rebuilt for the committed grouping
policies.
+ if allowed, enforceErr := e.Enforce("alice", "data1", "read");
enforceErr != nil || !allowed {
+ t.Fatalf("alice should inherit the admin permission, got %v
(err %v)", allowed, enforceErr)
+ }
+
+ // Removing them again must take effect too.
+ err = e.WithTransaction(ctx, func(tx *Transaction) error {
+ removed, removeErr :=
tx.RemoveGroupingPolicies([][]string{{"alice", "admin"}, {"bob", "admin"}})
+ if !removed || removeErr != nil {
+ t.Fatalf("Failed to remove grouping policies in
transaction: %v", removeErr)
+ }
+ return nil
+ })
+ if err != nil {
+ t.Fatalf("Failed to commit transaction: %v", err)
+ }
+
+ if hasGroupingPolicy(t, e, "alice", "admin") || hasGroupingPolicy(t, e,
"bob", "admin") {
+ t.Fatal("Both grouping policies should have been removed")
+ }
+ if ok, _ := e.Enforce("alice", "data1", "read"); ok {
+ t.Fatal("alice should no longer inherit the admin permission")
+ }
+}
+
+// Test updating a grouping policy within a transaction.
+func TestTransactionUpdateGroupingPolicy(t *testing.T) {
+ e := newTestTransactionalEnforcer(t)
+ ctx := context.Background()
+
+ if err := e.WithTransaction(ctx, func(tx *Transaction) error {
+ _, err := tx.AddGroupingPolicy("alice", "admin")
+ return err
+ }); err != nil {
+ t.Fatalf("Failed to commit transaction: %v", err)
+ }
+
+ // Updating a rule that does not exist changes nothing.
+ if err := e.WithTransaction(ctx, func(tx *Transaction) error {
+ ok, err := tx.UpdateGroupingPolicy([]string{"carol", "admin"},
[]string{"carol", "user"})
+ if err != nil {
+ return err
+ }
+ if ok {
+ t.Fatal("Updating a missing grouping policy should
return false")
+ }
+ return nil
+ }); err != nil {
+ t.Fatalf("Failed to commit transaction: %v", err)
+ }
+
+ // Mismatched batch lengths are rejected.
+ if err := e.WithTransaction(ctx, func(tx *Transaction) error {
+ if _, err := tx.UpdateGroupingPolicies([][]string{{"alice",
"admin"}}, [][]string{}); err == nil {
+ t.Fatal("Mismatched old/new rule counts should return
an error")
+ }
+ return nil
+ }); err != nil {
+ t.Fatalf("Failed to commit transaction: %v", err)
+ }
+
+ if err := e.WithTransaction(ctx, func(tx *Transaction) error {
+ ok, err := tx.UpdateGroupingPolicy([]string{"alice", "admin"},
[]string{"alice", "user"})
+ if !ok || err != nil {
+ t.Fatalf("Failed to update grouping policy in
transaction: %v", err)
+ }
+ return nil
+ }); err != nil {
+ t.Fatalf("Failed to commit transaction: %v", err)
+ }
+
+ if hasGroupingPolicy(t, e, "alice", "admin") {
+ t.Fatal("The old grouping policy should have been removed")
+ }
+ if !hasGroupingPolicy(t, e, "alice", "user") {
+ t.Fatal("The new grouping policy should have been added")
+ }
+}
+
+// Test filtered removal within a transaction.
+func TestTransactionRemoveFilteredPolicy(t *testing.T) {
+ e := newTestTransactionalEnforcer(t)
+ ctx := context.Background()
+
+ if err := e.WithTransaction(ctx, func(tx *Transaction) error {
+ if _, err := tx.AddPolicies([][]string{
+ {"alice", "data1", "read"},
+ {"alice", "data2", "write"},
+ {"bob", "data2", "write"},
+ }); err != nil {
+ return err
+ }
+ _, err := tx.AddGroupingPolicies([][]string{{"alice", "admin"},
{"bob", "admin"}})
+ return err
+ }); err != nil {
+ t.Fatalf("Failed to commit transaction: %v", err)
+ }
+
+ // A filter that matches nothing is a no-op.
+ if err := e.WithTransaction(ctx, func(tx *Transaction) error {
+ ok, err := tx.RemoveFilteredPolicy(0, "carol")
+ if err != nil {
+ return err
+ }
+ if ok || tx.HasOperations() {
+ t.Fatal("A filter matching no rule should buffer
nothing")
+ }
+ return nil
+ }); err != nil {
+ t.Fatalf("Failed to commit transaction: %v", err)
+ }
+
+ if err := e.WithTransaction(ctx, func(tx *Transaction) error {
+ ok, err := tx.RemoveFilteredPolicy(0, "alice")
+ if !ok || err != nil {
+ t.Fatalf("Failed to remove filtered policy in
transaction: %v", err)
+ }
+
+ // The removal is visible inside the transaction before
committing.
+ bufferedModel, err := tx.GetBufferedModel()
+ if err != nil {
+ return err
+ }
+ if has, _ := bufferedModel.HasPolicy("p", "p",
[]string{"alice", "data1", "read"}); has {
+ t.Fatal("The buffered model should no longer contain
alice's policy")
+ }
+
+ // But not outside of it.
+ if !hasPolicy(t, e, "alice", "data1", "read") {
+ t.Fatal("The enforcer model should still contain
alice's policy before commit")
+ }
+
+ ok, err = tx.RemoveFilteredGroupingPolicy(1, "admin")
+ if !ok || err != nil {
+ t.Fatalf("Failed to remove filtered grouping policy in
transaction: %v", err)
+ }
+ return nil
+ }); err != nil {
+ t.Fatalf("Failed to commit transaction: %v", err)
+ }
+
+ if hasPolicy(t, e, "alice", "data1", "read") || hasPolicy(t, e,
"alice", "data2", "write") {
+ t.Fatal("All of alice's policies should have been removed")
+ }
+ if !hasPolicy(t, e, "bob", "data2", "write") {
+ t.Fatal("bob's policy should have been kept")
+ }
+ if hasGroupingPolicy(t, e, "alice", "admin") || hasGroupingPolicy(t, e,
"bob", "admin") {
+ t.Fatal("All grouping policies for admin should have been
removed")
+ }
+}
+
+// Test that a rolled back filtered removal leaves the model untouched.
+func TestTransactionRemoveFilteredPolicyRollback(t *testing.T) {
+ e := newTestTransactionalEnforcer(t)
+ ctx := context.Background()
+
+ if err := e.WithTransaction(ctx, func(tx *Transaction) error {
+ _, err := tx.AddPolicy("alice", "data1", "read")
+ return err
+ }); err != nil {
+ t.Fatalf("Failed to commit transaction: %v", err)
+ }
+
+ wantErr := errors.New("business operation failed")
+ err := e.WithTransaction(ctx, func(tx *Transaction) error {
+ if _, err := tx.RemoveFilteredPolicy(0, "alice"); err != nil {
+ return err
+ }
+ return wantErr
+ })
+ if !errors.Is(err, wantErr) {
+ t.Fatalf("Expected the business error to be returned, got %v",
err)
+ }
+
+ if !hasPolicy(t, e, "alice", "data1", "read") {
+ t.Fatal("The policy should still exist after a rollback")
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]