Repository: casbin/casbin
Pull request #691 (2021-01-28 04:12:22)

feat: implement atomic UpdatePolicies() API
Signed-off-by: RobotHuang <1183598761@qq.com>
fix: #690 


Patch:
diff --git a/README.md b/README.md
index b2c7bb7..142ed62 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@ Casbin
 ====
 
 [![Go Report Card](https://goreportcard.com/badge/github.com/casbin/casbin)](https://goreportcard.com/report/github.com/casbin/casbin)
-[![Build Status](https://travis-ci.com/casbin/casbin.svg?branch=master)](https://travis-ci.com/casbin/casbin)
+[![Build Status](https://travis-ci.org/casbin/casbin.svg?branch=master)](https://travis-ci.org/casbin/casbin)
 [![Coverage Status](https://coveralls.io/repos/github/casbin/casbin/badge.svg?branch=master)](https://coveralls.io/github/casbin/casbin?branch=master)
 [![Godoc](https://godoc.org/github.com/casbin/casbin?status.svg)](https://pkg.go.dev/github.com/casbin/casbin/v2)
 [![Release](https://img.shields.io/github/release/casbin/casbin.svg)](https://github.com/casbin/casbin/releases/latest)
diff --git a/internal_api.go b/internal_api.go
index 303c5e9..a02a0ad 100644
--- a/internal_api.go
+++ b/internal_api.go
@@ -186,6 +186,48 @@ func (e *Enforcer) updatePolicy(sec string, ptype string, oldRule []string, newR
 	return ruleUpdated, nil
 }
 
+func (e *Enforcer) updatePolicies(sec string, ptype string , oldRules [][]string, newRules [][]string) (bool, error) {
+	if e.dispatcher != nil && e.autoNotifyDispatcher {
+		return true, e.dispatcher.UpdatePolicies(sec, ptype, oldRules, newRules)
+	}
+	
+	if e.shouldPersist() {
+		if err := e.adapter.(persist.UpdatableAdapter).UpdatePolicies(sec, ptype, oldRules, newRules); err != nil {
+			if err.Error() != notImplemented {
+				return false, err
+			}
+		}
+	}
+
+	ruleUpdated := e.model.UpdatePolicies(sec, ptype, oldRules, newRules)
+	if !ruleUpdated {
+		return ruleUpdated, nil
+	}
+
+	if sec == "g" {
+		err := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, oldRules) // remove the old rules
+		if err != nil {
+			return ruleUpdated, err
+		}
+		err = e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, newRules) // add the new rules
+		if err != nil {
+			return ruleUpdated, err
+		}
+	}
+
+	if e.watcher != nil && e.autoNotifyWatcher {
+		var err error
+		if watcher, ok := e.watcher.(persist.WatcherUpdatable); ok {
+			err = watcher.UpdateForUpdatePolicies(oldRules, newRules)
+		} else {
+			err = e.watcher.Update()
+		}
+		return ruleUpdated, err
+	}
+
+	return ruleUpdated, nil
+}
+
 // removePolicies removes rules from the current policy.
 func (e *Enforcer) removePolicies(sec string, ptype string, rules [][]string) (bool, error) {
 	if !e.model.HasPolicies(sec, ptype, rules) {
diff --git a/management_api.go b/management_api.go
index 5c85d01..d1ddf24 100644
--- a/management_api.go
+++ b/management_api.go
@@ -166,6 +166,15 @@ func (e *Enforcer) UpdateNamedPolicy(ptype string, p1 []string, p2 []string) (bo
 	return e.updatePolicy("p", ptype, p1, p2)
 }
 
+// UpdatePolicies updates authorization rules from the current policies.
+func (e *Enforcer) UpdatePolicies(oldPolices [][]string, newPolicies [][]string) (bool, error) {
+	return e.UpdateNamedPolicies("p", oldPolices, newPolicies)
+}
+
+func (e *Enforcer) UpdateNamedPolicies(ptype string, p1 [][]string, p2 [][]string) (bool, error) {
+	return e.updatePolicies("p", ptype, p1, p2)
+}
+
 // RemovePolicies removes authorization rules from the current policy.
 func (e *Enforcer) RemovePolicies(rules [][]string) (bool, error) {
 	return e.RemoveNamedPolicies("p", rules)
diff --git a/model/policy.go b/model/policy.go
index bfce803..713b4f6 100644
--- a/model/policy.go
+++ b/model/policy.go
@@ -200,6 +200,43 @@ func (model Model) UpdatePolicy(sec string, ptype string, oldRule []string, newR
 	return true
 }
 
+// UpdatePolicies updates a policy rule from the model.
+func (model Model) UpdatePolicies(sec string, ptype string, oldRules, newRules [][]string) bool {
+	rollbackFlag := false
+	// index -> []{oldIndex, newIndex}
+	modifiedRuleIndex := make(map[int][]int)
+	// rollback
+	defer func() {
+		if rollbackFlag {
+			for index, oldNewIndex := range modifiedRuleIndex {
+				model[sec][ptype].Policy[index] = oldRules[oldNewIndex[0]]
+				oldPolicy := strings.Join(oldRules[oldNewIndex[0]], DefaultSep)
+				newPolicy := strings.Join(newRules[oldNewIndex[1]], DefaultSep)
+				delete(model[sec][ptype].PolicyMap, newPolicy)
+				model[sec][ptype].PolicyMap[oldPolicy] = index
+			}
+		}
+	}()
+
+	newIndex := 0
+	for oldIndex, oldRule := range oldRules {
+		oldPolicy := strings.Join(oldRule, DefaultSep)
+		index, ok := model[sec][ptype].PolicyMap[oldPolicy]
+		if !ok {
+			rollbackFlag = true
+			return false
+		}
+
+		model[sec][ptype].Policy[index] = newRules[newIndex]
+		delete(model[sec][ptype].PolicyMap, oldPolicy)
+		model[sec][ptype].PolicyMap[strings.Join(newRules[newIndex], DefaultSep)] = index
+		modifiedRuleIndex[index] = []int{oldIndex, newIndex}
+		newIndex++
+	}
+
+	return true
+}
+
 // RemovePolicies removes policy rules from the model.
 func (model Model) RemovePolicies(sec string, ptype string, rules [][]string) bool {
 	effected := model.RemovePoliciesWithEffected(sec, ptype, rules)
diff --git a/persist/dispatcher.go b/persist/dispatcher.go
index 671154b..f0e6d71 100644
--- a/persist/dispatcher.go
+++ b/persist/dispatcher.go
@@ -26,4 +26,6 @@ type Dispatcher interface {
 	ClearPolicy() error
 	// UpdatePolicy updates policy rule from all instance.
 	UpdatePolicy(sec string, ptype string, oldRule, newRule []string) error
+	// UpdatePolicies updates some policy rules from all instance
+	UpdatePolicies(sec string, ptype string, oldrules, newRules [][]string) error
 }
diff --git a/persist/file-adapter/adapter.go b/persist/file-adapter/adapter.go
index 8f243da..52aba20 100644
--- a/persist/file-adapter/adapter.go
+++ b/persist/file-adapter/adapter.go
@@ -36,6 +36,10 @@ func (a *Adapter) UpdatePolicy(sec string, ptype string, oldRule, newPolicy []st
 	return errors.New("not implemented")
 }
 
+func (a *Adapter) UpdatePolicies(sec string, ptype string, oldRules, newRules [][]string) error {
+	return errors.New("not implemented")
+}
+
 // NewAdapter is the constructor for Adapter.
 func NewAdapter(filePath string) *Adapter {
 	return &Adapter{filePath: filePath}
diff --git a/persist/file-adapter/adapter_mock.go b/persist/file-adapter/adapter_mock.go
index 5512e35..55dd131 100644
--- a/persist/file-adapter/adapter_mock.go
+++ b/persist/file-adapter/adapter_mock.go
@@ -109,6 +109,10 @@ func (a *AdapterMock) UpdatePolicy(sec string, ptype string, oldRule, newPolicy
 	return a.GetMockErr()
 }
 
+func (a *AdapterMock) UpdatePolicies(sec string, ptype string, oldRules, newRules [][]string) error {
+	return a.GetMockErr()
+}
+
 // RemoveFilteredPolicy removes policy rules that match the filter from the storage.
 func (a *AdapterMock) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error {
 	return a.GetMockErr()
diff --git a/persist/update_adapter.go b/persist/update_adapter.go
index fb0487b..00c1b51 100644
--- a/persist/update_adapter.go
+++ b/persist/update_adapter.go
@@ -20,4 +20,6 @@ type UpdatableAdapter interface {
 	// UpdatePolicy updates a policy rule from storage.
 	// This is part of the Auto-Save feature.
 	UpdatePolicy(sec string, ptype string, oldRule, newPolicy []string) error
+	// UpdatePolicies updates some policy rules to storage, like db, redis.
+	UpdatePolicies(sec string, ptype string, oldRules, newRules [][]string) error
 }
diff --git a/persist/watcher_update.go b/persist/watcher_update.go
index c7dd216..4f2ae63 100644
--- a/persist/watcher_update.go
+++ b/persist/watcher_update.go
@@ -20,4 +20,7 @@ type WatcherUpdatable interface {
 	// UPdateForUpdaticy calls the update callback of other instances to synchronize their policy.
 	// It is called after Enforcer.UpdatePolicy()
 	UpdateForUpdatePolicy(oldRule, newRule []string) error
+	// UPdateForUpdaticies calls the update callback of other instances to synchronize their policy.
+	// It is called after Enforcer.UpdatePolicies()
+	UpdateForUpdatePolicies(oldRules, newRules [][]string) error
 }


Test patch:
diff --git a/management_api_test.go b/management_api_test.go
index 378fb06..bbd0e5b 100644
--- a/management_api_test.go
+++ b/management_api_test.go
@@ -196,6 +196,15 @@ func TestModifyPolicyAPI(t *testing.T) {
 	_, _ = e.UpdatePolicy([]string{"eve", "data3", "read"}, []string{"eve", "data3", "write"})
 
 	testGetPolicy(t, e, [][]string{{"eve", "data3", "write"}})
+
+	// This test shows a rollback effect.
+	// _, _ = e.UpdatePolicies([][]string{{"eve", "data3", "write"}, {"jack", "data4", "read"}}, [][]string{{"eve", "data3", "read"}, {"jack", "data4", "write"}})
+	// testGetPolicy(t, e, [][]string{{"eve", "data3", "read"}, {"jack", "data4", "write"}})
+
+	_, _ = e.AddPolicies(rules)
+	_, _ = e.UpdatePolicies([][]string{{"eve", "data3", "write"}, {"leyo", "data4", "read"}, {"katy", "data4", "write"}},
+		[][]string{{"eve", "data3", "read"}, {"leyo", "data4", "write"}, {"katy", "data1", "write"}})
+	testGetPolicy(t, e, [][]string{{"eve", "data3", "read"}, {"jack", "data4", "read"}, {"katy", "data1", "write"}, {"leyo", "data4", "write"}, {"ham", "data4", "write"}})
 }
 
 func TestModifyGroupingPolicyAPI(t *testing.T) {