Repository: casbin/casbin
Pull request #732 (2021-03-11 14:29:32)

feat: add updatefilteredpolicies
Signed-off-by: closetool <c299999999@qq.com>

Fix: https://github.com/casbin/casbin/issues/728

Fix: https://github.com/casbin/casbin/issues/731

hsluoyz: @arafat-java @dahu33 plz review.

Patch:
diff --git a/enforcer.go b/enforcer.go
index 2cbe8a5..3d35a8f 100644
--- a/enforcer.go
+++ b/enforcer.go
@@ -365,7 +365,11 @@ func (e *Enforcer) SavePolicy() error {
 
 func (e *Enforcer) initRmMap() {
 	for ptype := range e.model["g"] {
-		e.rmMap[ptype] = defaultrolemanager.NewRoleManager(10)
+		if rm, ok := e.rmMap[ptype]; ok {
+			_ = rm.Clear()
+		} else {
+			e.rmMap[ptype] = defaultrolemanager.NewRoleManager(10)
+		}
 	}
 }
 
diff --git a/enforcer_distributed.go b/enforcer_distributed.go
index 51952a3..f917037 100644
--- a/enforcer_distributed.go
+++ b/enforcer_distributed.go
@@ -171,3 +171,37 @@ func (d *DistributedEnforcer) UpdatePoliciesSelf(shouldPersist func() bool, sec
 
 	return ruleUpdated, nil
 }
+
+// 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) {
+	var (
+		oldRules [][]string
+		err      error
+	)
+	if shouldPersist != nil && shouldPersist() {
+		oldRules, err = d.adapter.(persist.UpdatableAdapter).UpdateFilteredPolicies(sec, ptype, newRules, fieldIndex, fieldValues...)
+		if err != nil {
+			return false, err
+		}
+	}
+
+	ruleChanged := !d.model.RemovePolicies(sec, ptype, oldRules)
+	d.model.AddPolicies(sec, ptype, newRules)
+	ruleChanged = ruleChanged && len(newRules) != 0
+	if !ruleChanged {
+		return ruleChanged, nil
+	}
+
+	if sec == "g" {
+		err := d.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, oldRules) // remove the old rule
+		if err != nil {
+			return ruleChanged, err
+		}
+		err = d.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, newRules) // add the new rule
+		if err != nil {
+			return ruleChanged, err
+		}
+	}
+
+	return true, nil
+}
diff --git a/enforcer_interface.go b/enforcer_interface.go
index 560504c..12e6c07 100644
--- a/enforcer_interface.go
+++ b/enforcer_interface.go
@@ -131,6 +131,7 @@ type IEnforcer interface {
 
 	UpdatePolicy(oldPolicy []string, newPolicy []string) (bool, error)
 	UpdatePolicies(oldPolicies [][]string, newPolicies [][]string) (bool, error)
+	UpdateFilteredPolicies(newPolicies [][]string, fieldIndex int, fieldValues ...string) (bool, error)
 }
 
 var _ IDistributedEnforcer = &DistributedEnforcer{}
@@ -146,4 +147,5 @@ type IDistributedEnforcer interface {
 	ClearPolicySelf(shouldPersist func() bool) error
 	UpdatePolicySelf(shouldPersist func() bool, sec string, ptype string, oldRule, newRule []string) (effected bool, err error)
 	UpdatePoliciesSelf(shouldPersist func() bool, sec string, ptype string, oldRules, newRules [][]string) (effected bool, err error)
+	UpdateFilteredPoliciesSelf(shouldPersist func() bool, sec string, ptype string, newRules [][]string, fieldIndex int, fieldValues ...string) (bool, error)
 }
diff --git a/enforcer_synced.go b/enforcer_synced.go
index c3f9d30..c0d7a22 100644
--- a/enforcer_synced.go
+++ b/enforcer_synced.go
@@ -357,6 +357,18 @@ func (e *SyncedEnforcer) UpdateNamedPolicies(ptype string, p1 [][]string, p2 [][
 	return e.Enforcer.UpdateNamedPolicies(ptype, p1, p2)
 }
 
+func (e *SyncedEnforcer) UpdateFilteredPolicies(newPolicies [][]string, fieldIndex int, fieldValues ...string) (bool, error) {
+	e.m.Lock()
+	defer e.m.Unlock()
+	return e.Enforcer.UpdateFilteredPolicies(newPolicies, fieldIndex, fieldValues...)
+}
+
+func (e *SyncedEnforcer) UpdateFilteredNamedPolicies(ptype string, newPolicies [][]string, fieldIndex int, fieldValues ...string) (bool, error) {
+	e.m.Lock()
+	defer e.m.Unlock()
+	return e.Enforcer.UpdateFilteredNamedPolicies(ptype, newPolicies, fieldIndex, fieldValues...)
+}
+
 // RemovePolicies removes authorization rules from the current policy.
 func (e *SyncedEnforcer) RemovePolicies(rules [][]string) (bool, error) {
 	e.m.Lock()
diff --git a/frontend.go b/frontend.go
index e077886..c53ca46 100644
--- a/frontend.go
+++ b/frontend.go
@@ -15,25 +15,18 @@
 package casbin
 
 import (
-	"bytes"
 	"encoding/json"
 )
 
 func CasbinJsGetPermissionForUser(e IEnforcer, user string) ([]byte, error) {
-	model := e.GetModel()
-	m := map[string]interface{}{}
-	m["m"] = model.ToText()
-	policies := make([][]string, 0)
-	for ptype := range model["p"] {
-		policies = model.GetPolicy("p", ptype)
-		for i := range policies {
-			policies[i] = append([]string{ptype}, policies[i]...)
-		}
+	policy, err := e.GetImplicitPermissionsForUser(user)
+	if err != nil {
+		return nil, err
 	}
-	m["p"] = policies
-	result := bytes.NewBuffer([]byte{})
-	encoder := json.NewEncoder(result)
-	encoder.SetEscapeHTML(false)
-	err := encoder.Encode(m)
-	return result.Bytes(), err
+	permission := make(map[string][]string)
+	for i := 0; i < len(policy); i++ {
+		permission[policy[i][2]] = append(permission[policy[i][2]], policy[i][1])
+	}
+	b, _ := json.Marshal(permission)
+	return b, nil
 }
diff --git a/internal_api.go b/internal_api.go
index 53ae50c..e75f31a 100644
--- a/internal_api.go
+++ b/internal_api.go
@@ -310,3 +310,52 @@ func (e *Enforcer) removeFilteredPolicy(sec string, ptype string, fieldIndex int
 
 	return ruleRemoved, nil
 }
+
+func (e *Enforcer) updateFilteredPolicies(sec string, ptype string, newRules [][]string, fieldIndex int, fieldValues ...string) (bool, error) {
+	var (
+		oldRules [][]string
+		err      error
+	)
+
+	if e.shouldPersist() {
+		if oldRules, err = e.adapter.(persist.UpdatableAdapter).UpdateFilteredPolicies(sec, ptype, newRules, fieldIndex, fieldValues...); err != nil {
+			if err.Error() != notImplemented {
+				return false, err
+			}
+		}
+	}
+
+	if e.dispatcher != nil && e.autoNotifyDispatcher {
+		return true, e.dispatcher.UpdateFilteredPolicies(sec, ptype, oldRules, newRules)
+	}
+
+	ruleChanged := e.model.RemovePolicies(sec, ptype, oldRules)
+	e.model.AddPolicies(sec, ptype, newRules)
+	ruleChanged = ruleChanged && len(newRules) != 0
+	if !ruleChanged {
+		return ruleChanged, nil
+	}
+
+	if sec == "g" {
+		err := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, oldRules) // remove the old rules
+		if err != nil {
+			return ruleChanged, err
+		}
+		err = e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, newRules) // add the new rules
+		if err != nil {
+			return ruleChanged, 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 ruleChanged, err
+	}
+
+	return ruleChanged, nil
+}
diff --git a/management_api.go b/management_api.go
index d1ddf24..ddef0ae 100644
--- a/management_api.go
+++ b/management_api.go
@@ -175,6 +175,14 @@ func (e *Enforcer) UpdateNamedPolicies(ptype string, p1 [][]string, p2 [][]strin
 	return e.updatePolicies("p", ptype, p1, p2)
 }
 
+func (e *Enforcer) UpdateFilteredPolicies(newPolicies [][]string, fieldIndex int, fieldValues ...string) (bool, error) {
+	return e.UpdateFilteredNamedPolicies("p", newPolicies, fieldIndex, fieldValues...)
+}
+
+func (e *Enforcer) UpdateFilteredNamedPolicies(ptype string, newPolicies [][]string, fieldIndex int, fieldValues ...string) (bool, error) {
+	return e.updateFilteredPolicies("p", ptype, newPolicies, fieldIndex, fieldValues...)
+}
+
 // 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/model.go b/model/model.go
index 2b9ed8f..a50ad95 100644
--- a/model/model.go
+++ b/model/model.go
@@ -227,27 +227,3 @@ func (model Model) SortPoliciesByPriority() error {
 	}
 	return nil
 }
-
-func (model Model) ToText() string {
-	s := strings.Builder{}
-	writeString := func(sec string) {
-		for ptype := range model[sec] {
-			s.WriteString(fmt.Sprintf("%s = %s\n", ptype, strings.Replace(model[sec][ptype].Value, "_", ".", -1)))
-		}
-	}
-	s.WriteString("[request_definition]\n")
-	writeString("r")
-	s.WriteString("[policy_definition]\n")
-	writeString("p")
-	if _, ok := model["g"]; ok {
-		s.WriteString("[role_definition]\n")
-		for ptype := range model["g"] {
-			s.WriteString(fmt.Sprintf("%s = %s\n", ptype, model["g"][ptype].Value))
-		}
-	}
-	s.WriteString("[policy_effect]\n")
-	writeString("e")
-	s.WriteString("[matchers]\n")
-	writeString("m")
-	return s.String()
-}
diff --git a/persist/dispatcher.go b/persist/dispatcher.go
index f0e6d71..3eb4605 100644
--- a/persist/dispatcher.go
+++ b/persist/dispatcher.go
@@ -28,4 +28,6 @@ type Dispatcher interface {
 	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
+	// UpdateFilteredPolicies deletes old rules and adds new rules.
+	UpdateFilteredPolicies(sec string, ptype string, oldRules [][]string, newRules [][]string) error
 }
diff --git a/persist/file-adapter/adapter.go b/persist/file-adapter/adapter.go
index 52aba20..3c4f1b1 100644
--- a/persist/file-adapter/adapter.go
+++ b/persist/file-adapter/adapter.go
@@ -40,6 +40,10 @@ func (a *Adapter) UpdatePolicies(sec string, ptype string, oldRules, newRules []
 	return errors.New("not implemented")
 }
 
+func (a *Adapter) UpdateFilteredPolicies(sec string, ptype string, newPolicies [][]string, fieldIndex int, fieldValues ...string) ([][]string, error) {
+	return nil, errors.New("not implemented")
+}
+
 // NewAdapter is the constructor for Adapter.
 func NewAdapter(filePath string) *Adapter {
 	return &Adapter{filePath: filePath}
diff --git a/persist/update_adapter.go b/persist/update_adapter.go
index 00c1b51..3dd59f7 100644
--- a/persist/update_adapter.go
+++ b/persist/update_adapter.go
@@ -22,4 +22,6 @@ type UpdatableAdapter interface {
 	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
+	// UpdateFilteredPolicies deletes old rules and adds new rules.
+	UpdateFilteredPolicies(sec string, ptype string, newPolicies [][]string, fieldIndex int, fieldValues ...string) ([][]string, error)
 }


Test patch:
diff --git a/frontend_test.go b/frontend_test.go
index 2b9ccb1..92c6186 100644
--- a/frontend_test.go
+++ b/frontend_test.go
@@ -15,9 +15,83 @@
 package casbin
 
 import (
+	"encoding/json"
 	"testing"
 )
 
+func contains(arr []string, target string) bool {
+	for _, item := range arr {
+		if item == target {
+			return true
+		}
+	}
+	return false
+}
+
+func TestCasbinJsGetPermissionForUser(t *testing.T) {
+	e, err := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
+	if err != nil {
+		panic(err)
+	}
+	target_str, _ := CasbinJsGetPermissionForUser(e, "alice")
+	t.Log("GetPermissionForUser Alice", string(target_str))
+	alice_target := make(map[string][]string)
+	err = json.Unmarshal(target_str, &alice_target)
+	if err != nil {
+		t.Errorf("Test error: %s", err)
+	}
+	perm, ok := alice_target["read"]
+	if !ok {
+		t.Errorf("Test error: Alice doesn't have read permission")
+	}
+	if !contains(perm, "data1") {
+		t.Errorf("Test error: Alice cannot read data1")
+	}
+	if !contains(perm, "data2") {
+		t.Errorf("Test error: Alice cannot read data2")
+	}
+	perm, ok = alice_target["write"]
+	if !ok {
+		t.Errorf("Test error: Alice doesn't have write permission")
+	}
+	if contains(perm, "data1") {
+		t.Errorf("Test error: Alice can write data1")
+	}
+	if !contains(perm, "data2") {
+		t.Errorf("Test error: Alice cannot write data2")
+	}
+
+	target_str, _ = CasbinJsGetPermissionForUser(e, "bob")
+	t.Log("GetPermissionForUser Bob", string(target_str))
+	bob_target := make(map[string][]string)
+	err = json.Unmarshal(target_str, &bob_target)
+	if err != nil {
+		t.Errorf("Test error: %s", err)
+	}
+	_, ok = bob_target["read"]
+	if ok {
+		t.Errorf("Test error: Bob has read permission")
+	}
+	perm, ok = bob_target["write"]
+	if !ok {
+		t.Errorf("Test error: Bob doesn't have permission")
+	}
+	if !contains(perm, "data2") {
+		t.Errorf("Test error: Bob cannot write data2")
+	}
+	if contains(perm, "data1") {
+		t.Errorf("Test error: Bob can write data1")
+	}
+	if contains(perm, "data_not_exist") {
+		t.Errorf("Test error: Bob can access a non-existing data")
+	}
+
+	_, ok = bob_target["rm_rf"]
+	if ok {
+		t.Errorf("Someone can have a non-existing action (rm -rf)")
+	}
+}
+
 func TestCasbinJsGetPermissionForUser2(t *testing.T) {
 	e, err := NewSyncedEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
 	if err != nil {