Repository: casbin/casbin
Pull request #714 (2021-02-21 07:16:29)

feat: add priority support(#550)
Signed-off-by: closetool <c299999999@qq.com>

Fix: #550 

method `UpdatePolicy` not support priority yet

hsluoyz: @closetool plz fix semantic PR:

![image](https://user-images.githubusercontent.com/3787410/108620124-d12ef800-7464-11eb-8177-b9beb23cb883.png)

closetool: @hsluoyz plz review
hsluoyz: @wndrr101 please review.

Patch:
diff --git a/enforcer.go b/enforcer.go
index ec6130c..d83d4dc 100644
--- a/enforcer.go
+++ b/enforcer.go
@@ -276,6 +276,10 @@ func (e *Enforcer) LoadPolicy() error {
 		return err
 	}
 
+	if err := e.model.SortPoliciesByPriority(); err != nil {
+		return err
+	}
+
 	e.initRmMap()
 
 	if e.autoBuildRoleLinks {
@@ -301,6 +305,10 @@ func (e *Enforcer) loadFilteredPolicy(filter interface{}) error {
 		return err
 	}
 
+	if err := e.model.SortPoliciesByPriority(); err != nil {
+		return err
+	}
+
 	e.initRmMap()
 	e.model.PrintPolicy()
 	if e.autoBuildRoleLinks {
diff --git a/examples/priority_model_explicit.conf b/examples/priority_model_explicit.conf
new file mode 100644
index 0000000..5df75b2
--- /dev/null
+++ b/examples/priority_model_explicit.conf
@@ -0,0 +1,14 @@
+[request_definition]
+r = sub, obj, act
+
+[policy_definition]
+p = priority, sub, obj, act, eft
+
+[role_definition]
+g = _, _
+
+[policy_effect]
+e = priority(p.eft) || deny
+
+[matchers]
+m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
\ No newline at end of file
diff --git a/examples/priority_policy_explicit.csv b/examples/priority_policy_explicit.csv
new file mode 100644
index 0000000..0fec82c
--- /dev/null
+++ b/examples/priority_policy_explicit.csv
@@ -0,0 +1,12 @@
+p, 10, data1_deny_group, data1, read, deny
+p, 10, data1_deny_group, data1, write, deny
+p, 10, data2_allow_group, data2, read, allow
+p, 10, data2_allow_group, data2, write, allow
+
+
+p, 1, alice, data1, write, allow
+p, 1, alice, data1, read, allow
+p, 1, bob, data2, read, deny
+
+g, bob, data2_allow_group
+g, alice, data1_deny_group
diff --git a/internal_api.go b/internal_api.go
index a02a0ad..53ae50c 100644
--- a/internal_api.go
+++ b/internal_api.go
@@ -186,11 +186,11 @@ 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) {
+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 {
diff --git a/model/model.go b/model/model.go
index c297f48..a50ad95 100644
--- a/model/model.go
+++ b/model/model.go
@@ -16,6 +16,7 @@ package model
 
 import (
 	"fmt"
+	"sort"
 	"strconv"
 	"strings"
 
@@ -202,3 +203,27 @@ func (model Model) PrintModel() {
 
 	model.GetLogger().LogModel(modelInfo)
 }
+
+func (model Model) SortPoliciesByPriority() error {
+	for ptype, assertion := range model["p"] {
+		if assertion.Tokens[0] != fmt.Sprintf("%s_priority", ptype) {
+			continue
+		}
+		policies := assertion.Policy
+		sort.SliceStable(policies, func(i, j int) bool {
+			p1, err := strconv.ParseUint(policies[i][0], 10, 32)
+			if err != nil {
+				return true
+			}
+			p2, err := strconv.ParseUint(policies[j][0], 10, 32)
+			if err != nil {
+				return true
+			}
+			return p1 < p2
+		})
+		for i, policy := range assertion.Policy {
+			assertion.PolicyMap[strings.Join(policy, ",")] = i
+		}
+	}
+	return nil
+}
diff --git a/model/policy.go b/model/policy.go
index 713b4f6..f49f2f6 100644
--- a/model/policy.go
+++ b/model/policy.go
@@ -15,6 +15,8 @@
 package model
 
 import (
+	"fmt"
+	"strconv"
 	"strings"
 
 	"github.com/casbin/casbin/v2/rbac"
@@ -143,8 +145,26 @@ func (model Model) HasPolicies(sec string, ptype string, rules [][]string) bool
 
 // AddPolicy adds a policy rule to the model.
 func (model Model) AddPolicy(sec string, ptype string, rule []string) {
-	model[sec][ptype].Policy = append(model[sec][ptype].Policy, rule)
-	model[sec][ptype].PolicyMap[strings.Join(rule, DefaultSep)] = len(model[sec][ptype].Policy) - 1
+	assertion := model[sec][ptype]
+	assertion.Policy = append(assertion.Policy, rule)
+	if idxInsert, err := strconv.ParseUint(rule[0], 10, 32); sec == "p" && assertion.Tokens[0] == fmt.Sprintf("%s_priority", ptype) && err == nil {
+		i := len(assertion.Policy) - 1
+		for ; i > 0; i-- {
+			idx, err := strconv.ParseUint(assertion.Policy[i-1][0], 10, 32)
+			if err != nil {
+				break
+			}
+			if idx > idxInsert {
+				assertion.Policy[i] = assertion.Policy[i-1]
+			} else {
+				break
+			}
+		}
+		assertion.Policy[i] = rule
+		assertion.PolicyMap[strings.Join(rule, DefaultSep)] = i
+	} else {
+		assertion.PolicyMap[strings.Join(rule, DefaultSep)] = len(model[sec][ptype].Policy) - 1
+	}
 }
 
 // AddPolicies adds policy rules to the model.
@@ -162,8 +182,7 @@ func (model Model) AddPoliciesWithAffected(sec string, ptype string, rules [][]s
 			continue
 		}
 		effected = append(effected, rule)
-		model[sec][ptype].Policy = append(model[sec][ptype].Policy, rule)
-		model[sec][ptype].PolicyMap[hashKey] = len(model[sec][ptype].Policy) - 1
+		model.AddPolicy(sec, ptype, rule)
 	}
 	return effected
 }
diff --git a/util/util.go b/util/util.go
index 4707893..be1b8b2 100644
--- a/util/util.go
+++ b/util/util.go
@@ -187,13 +187,13 @@ func GetEvalValue(s string) []string {
 }
 
 func RemoveDuplicateElement(s []string) []string {
-    result := make([]string, 0, len(s))
-    temp := map[string]struct{}{}
-    for _, item := range s {
-        if _, ok := temp[item]; !ok {
-            temp[item] = struct{}{}
-            result = append(result, item)
-        }
-    }
-    return result
-}
\ No newline at end of file
+	result := make([]string, 0, len(s))
+	temp := map[string]struct{}{}
+	for _, item := range s {
+		if _, ok := temp[item]; !ok {
+			temp[item] = struct{}{}
+			result = append(result, item)
+		}
+	}
+	return result
+}


Test patch:
diff --git a/enforcer_test.go b/enforcer_test.go
index e796294..b73bf36 100644
--- a/enforcer_test.go
+++ b/enforcer_test.go
@@ -499,3 +499,36 @@ func TestBatchEnforce(t *testing.T) {
 	testBatchEnforce(t, e, [][]interface{}{{"alice", "data1", "read"}, {"bob", "data2", "write"}, {"jack", "data3", "read"}}, results)
 }
 
+func TestPriorityExplicit(t *testing.T) {
+	e, _ := NewEnforcer("examples/priority_model_explicit.conf", "examples/priority_policy_explicit.csv")
+	testBatchEnforce(t, e, [][]interface{}{
+		{"alice", "data1", "write"},
+		{"alice", "data1", "read"},
+		{"bob", "data2", "read"},
+		{"bob", "data2", "write"},
+		{"data1_deny_group", "data1", "read"},
+		{"data1_deny_group", "data1", "write"},
+		{"data2_allow_group", "data2", "read"},
+		{"data2_allow_group", "data2", "write"},
+	}, []bool{
+		true, true, false, true, false, false, true, true,
+	})
+
+	_, err := e.AddPolicy("1", "bob", "data2", "write", "deny")
+	if err != nil {
+		t.Fatalf("Add Policy: %v", err)
+	}
+
+	testBatchEnforce(t, e, [][]interface{}{
+		{"alice", "data1", "write"},
+		{"alice", "data1", "read"},
+		{"bob", "data2", "read"},
+		{"bob", "data2", "write"},
+		{"data1_deny_group", "data1", "read"},
+		{"data1_deny_group", "data1", "write"},
+		{"data2_allow_group", "data2", "read"},
+		{"data2_allow_group", "data2", "write"},
+	}, []bool{
+		true, true, false, false, false, false, true, true,
+	})
+}
diff --git a/rbac/default-role-manager/role_manager_test.go b/rbac/default-role-manager/role_manager_test.go
index 68c41b2..8dd4bea 100644
--- a/rbac/default-role-manager/role_manager_test.go
+++ b/rbac/default-role-manager/role_manager_test.go
@@ -295,4 +295,3 @@ func TestMatchingFuncOrder(t *testing.T) {
 	testRole(t, rm, "u1", "g1", true)
 	testRole(t, rm, "u1", "g2", true)
 }
-