Repository: casbin/casbin
Pull request #838 (2021-07-12 13:09:53)

feat: multiple request, policy, effect, matcher type support
Signed-off-by: tangyang9464 <tangyang9464@163.com>

fix: #835

tangyang9464: @closetool  @hsluoyz plz review
sagilio: @tangyang9464 We may need to resolve conflicts first.
![image](https://user-images.githubusercontent.com/42855245/125812786-bc38ea47-aeb8-438a-a012-50fdd30bd39b.png)

tangyang9464: @sagilio 
I did not update my fork repository, which caused the conflict. Now the conflict is resolved after merging, but there is an additional commit and some file changes. I don’t know if there are other ways to solve this
hsluoyz: @wgarunap plz review
hsluoyz: @tangyang9464 don't show:

![image](https://user-images.githubusercontent.com/3787410/125889455-68f38076-4c55-4b41-8928-a1faa459416f.png)

sagilio: https://github.com/casbin/casbin/blob/1fa27006ed7fcfe2d42475cdcdbeae36ac044eb6/enforcer.go#L564-L579

@tangyang9464  The `p_eft` here, maybe `p2_eft` and so on? I think we need to consider other cases.
tangyang9464: @sagilio fixed and add some test

Patch:
diff --git a/enforcer.go b/enforcer.go
index 7719e1e..b25d212 100644
--- a/enforcer.go
+++ b/enforcer.go
@@ -50,6 +50,14 @@ type Enforcer struct {
 	logger log.Logger
 }
 
+// EnforceContext is used as the first element of the parameter "rvals" in method "enforce"
+type EnforceContext struct {
+	rType string
+	pType string
+	eType string
+	mType string
+}
+
 // NewEnforcer creates an enforcer via file or DB.
 //
 // File:
@@ -440,6 +448,16 @@ func (e *Enforcer) BuildIncrementalRoleLinks(op model.PolicyOp, ptype string, ru
 	return e.model.BuildIncrementalRoleLinks(e.rmMap, op, "g", ptype, rules)
 }
 
+// NewEnforceContext Create a default structure based on the suffix
+func NewEnforceContext(suffix string) EnforceContext {
+	return EnforceContext{
+		rType: "r" + suffix,
+		pType: "p" + suffix,
+		eType: "e" + suffix,
+		mType: "m" + suffix,
+	}
+}
+
 // enforce use a custom matcher to decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (matcher, sub, obj, act), use model matcher by default when matcher is "".
 func (e *Enforcer) enforce(matcher string, explains *[]string, rvals ...interface{}) (ok bool, err error) {
 	defer func() {
@@ -459,9 +477,30 @@ func (e *Enforcer) enforce(matcher string, explains *[]string, rvals ...interfac
 			functions[key] = util.GenerateGFunction(rm)
 		}
 	}
+
+	var (
+		rType = "r"
+		pType = "p"
+		eType = "e"
+		mType = "m"
+	)
+	if len(rvals) != 0 {
+		switch rvals[0].(type) {
+		case EnforceContext:
+			enforceContext := rvals[0].(EnforceContext)
+			rType = enforceContext.rType
+			pType = enforceContext.pType
+			eType = enforceContext.eType
+			mType = enforceContext.mType
+			rvals = rvals[1:]
+		default:
+			break
+		}
+	}
+
 	var expString string
 	if matcher == "" {
-		expString = e.model["m"]["m"].Value
+		expString = e.model["m"][mType].Value
 	} else {
 		expString = util.RemoveComments(util.EscapeAssertion(matcher))
 	}
@@ -476,12 +515,12 @@ func (e *Enforcer) enforce(matcher string, explains *[]string, rvals ...interfac
 		}
 	}
 
-	rTokens := make(map[string]int, len(e.model["r"]["r"].Tokens))
-	for i, token := range e.model["r"]["r"].Tokens {
+	rTokens := make(map[string]int, len(e.model["r"][rType].Tokens))
+	for i, token := range e.model["r"][rType].Tokens {
 		rTokens[token] = i
 	}
-	pTokens := make(map[string]int, len(e.model["p"]["p"].Tokens))
-	for i, token := range e.model["p"]["p"].Tokens {
+	pTokens := make(map[string]int, len(e.model["p"][pType].Tokens))
+	for i, token := range e.model["p"][pType].Tokens {
 		pTokens[token] = i
 	}
 
@@ -492,10 +531,10 @@ func (e *Enforcer) enforce(matcher string, explains *[]string, rvals ...interfac
 		pTokens: pTokens,
 	}
 
-	if len(e.model["r"]["r"].Tokens) != len(rvals) {
+	if len(e.model["r"][rType].Tokens) != len(rvals) {
 		return false, fmt.Errorf(
 			"invalid request size: expected %d, got %d, rvals: %v",
-			len(e.model["r"]["r"].Tokens),
+			len(e.model["r"][rType].Tokens),
 			len(rvals),
 			rvals)
 	}
@@ -505,16 +544,17 @@ func (e *Enforcer) enforce(matcher string, explains *[]string, rvals ...interfac
 
 	var effect effector.Effect
 	var explainIndex int
-	if policyLen := len(e.model["p"]["p"].Policy); policyLen != 0 {
+
+	if policyLen := len(e.model["p"][pType].Policy); policyLen != 0 {
 		policyEffects = make([]effector.Effect, policyLen)
 		matcherResults = make([]float64, policyLen)
 
-		for policyIndex, pvals := range e.model["p"]["p"].Policy {
+		for policyIndex, pvals := range e.model["p"][pType].Policy {
 			// log.LogPrint("Policy Rule: ", pvals)
-			if len(e.model["p"]["p"].Tokens) != len(pvals) {
+			if len(e.model["p"][pType].Tokens) != len(pvals) {
 				return false, fmt.Errorf(
 					"invalid policy size: expected %d, got %d, pvals: %v",
-					len(e.model["p"]["p"].Tokens),
+					len(e.model["p"][pType].Tokens),
 					len(pvals),
 					pvals)
 			}
@@ -561,7 +601,7 @@ func (e *Enforcer) enforce(matcher string, explains *[]string, rvals ...interfac
 				return false, errors.New("matcher result should be bool, int or float")
 			}
 
-			if j, ok := parameters.pTokens["p_eft"]; ok {
+			if j, ok := parameters.pTokens[pType+"_eft"]; ok {
 				eft := parameters.pVals[j]
 				if eft == "allow" {
 					policyEffects[policyIndex] = effector.Allow
@@ -578,7 +618,7 @@ func (e *Enforcer) enforce(matcher string, explains *[]string, rvals ...interfac
 			//	break
 			//}
 
-			effect, explainIndex, err = e.eft.MergeEffects(e.model["e"]["e"].Value, policyEffects[:policyIndex+1], matcherResults[:policyIndex+1], policyIndex, policyLen)
+			effect, explainIndex, err = e.eft.MergeEffects(e.model["e"][eType].Value, policyEffects[:policyIndex+1], matcherResults[:policyIndex+1], policyIndex, policyLen)
 			if err != nil {
 				return false, err
 			}
@@ -587,7 +627,8 @@ func (e *Enforcer) enforce(matcher string, explains *[]string, rvals ...interfac
 			}
 		}
 	} else {
-		if hasEval && len(e.model["p"]["p"].Policy) == 0 {
+
+		if hasEval && len(e.model["p"][pType].Policy) == 0 {
 			return false, errors.New("please make sure rule exists in policy when using eval() in matcher")
 		}
 
@@ -609,7 +650,7 @@ func (e *Enforcer) enforce(matcher string, explains *[]string, rvals ...interfac
 			policyEffects[0] = effector.Indeterminate
 		}
 
-		effect, explainIndex, err = e.eft.MergeEffects(e.model["e"]["e"].Value, policyEffects, matcherResults, 0, 1)
+		effect, explainIndex, err = e.eft.MergeEffects(e.model["e"][eType].Value, policyEffects, matcherResults, 0, 1)
 		if err != nil {
 			return false, err
 		}
@@ -619,8 +660,9 @@ func (e *Enforcer) enforce(matcher string, explains *[]string, rvals ...interfac
 
 	if explains != nil {
 		logExplains = append(logExplains, *explains)
-		if explainIndex != -1 && len(e.model["p"]["p"].Policy) > explainIndex {
-			*explains = e.model["p"]["p"].Policy[explainIndex]
+
+		if explainIndex != -1 && len(e.model["p"][pType].Policy) > explainIndex {
+			*explains = e.model["p"][pType].Policy[explainIndex]
 		}
 	}
 
diff --git a/examples/multiple_policy_definitions_model.conf b/examples/multiple_policy_definitions_model.conf
new file mode 100644
index 0000000..b619097
--- /dev/null
+++ b/examples/multiple_policy_definitions_model.conf
@@ -0,0 +1,19 @@
+[request_definition]
+r = sub, obj, act
+r2 = sub, obj, act
+
+[policy_definition]
+p = sub, obj, act
+p2= sub_rule, obj, act, eft
+
+[role_definition]
+g = _, _
+
+[policy_effect]
+e = some(where (p.eft == allow))
+
+[matchers]
+#RABC
+m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
+#ABAC
+m2 = eval(p2.sub_rule) && r2.obj == p2.obj && r2.act == p2.act
diff --git a/examples/multiple_policy_definitions_policy.csv b/examples/multiple_policy_definitions_policy.csv
new file mode 100644
index 0000000..66498ab
--- /dev/null
+++ b/examples/multiple_policy_definitions_policy.csv
@@ -0,0 +1,5 @@
+p, data2_admin, data2, read
+p2, r2.sub.Age > 18 && r2.sub.Age < 60, /data1, read, allow
+p2, r2.sub.Age > 60 && r2.sub.Age < 100, /data1, read, deny
+
+g, alice, data2_admin
\ No newline at end of file
diff --git a/internal_api.go b/internal_api.go
index 10d1710..3659e18 100644
--- a/internal_api.go
+++ b/internal_api.go
@@ -98,13 +98,10 @@ func (e *Enforcer) addPolicies(sec string, ptype string, rules [][]string) (bool
 	}
 
 	if e.watcher != nil && e.autoNotifyWatcher {
-		var err error
-		if watcher, ok := e.watcher.(persist.WatcherEx); ok {
-			err = watcher.UpdateForAddPolicies(sec, ptype, rules...)
-		} else {
-			err = e.watcher.Update()
+		err := e.watcher.Update()
+		if err != nil {
+			return true, err
 		}
-		return true, err
 	}
 
 	return true, nil
@@ -264,13 +261,10 @@ func (e *Enforcer) removePolicies(sec string, ptype string, rules [][]string) (b
 	}
 
 	if e.watcher != nil && e.autoNotifyWatcher {
-		var err error
-		if watcher, ok := e.watcher.(persist.WatcherEx); ok {
-			err = watcher.UpdateForRemovePolicies(sec, ptype, rules...)
-		} else {
-			err = e.watcher.Update()
+		err := e.watcher.Update()
+		if err != nil {
+			return rulesRemoved, err
 		}
-		return true, err
 	}
 
 	return rulesRemoved, nil
diff --git a/persist/watcher_ex.go b/persist/watcher_ex.go
index c928224..8b89e36 100644
--- a/persist/watcher_ex.go
+++ b/persist/watcher_ex.go
@@ -22,7 +22,7 @@ type WatcherEx interface {
 	// UpdateForAddPolicy calls the update callback of other instances to synchronize their policy.
 	// It is called after Enforcer.AddPolicy()
 	UpdateForAddPolicy(sec, ptype string, params ...string) error
-	// UpdateForRemovePolicy calls the update callback of other instances to synchronize their policy.
+	// UPdateForRemovePolicy calls the update callback of other instances to synchronize their policy.
 	// It is called after Enforcer.RemovePolicy()
 	UpdateForRemovePolicy(sec, ptype string, params ...string) error
 	// UpdateForRemoveFilteredPolicy calls the update callback of other instances to synchronize their policy.
@@ -31,10 +31,4 @@ type WatcherEx interface {
 	// UpdateForSavePolicy calls the update callback of other instances to synchronize their policy.
 	// It is called after Enforcer.RemoveFilteredNamedGroupingPolicy()
 	UpdateForSavePolicy(model model.Model) error
-	// UpdateForAddPolicies calls the update callback of other instances to synchronize their policy.
-	// It is called after Enforcer.AddPolicies()
-	UpdateForAddPolicies(sec string, ptype string, rules ...[]string) error
-	// UpdateForRemovePolicies calls the update callback of other instances to synchronize their policy.
-	// It is called after Enforcer.RemovePolicies()
-	UpdateForRemovePolicies(sec string, ptype string, rules ...[]string) error
 }
diff --git a/persist/watcher_update.go b/persist/watcher_update.go
index 6743316..4f2ae63 100644
--- a/persist/watcher_update.go
+++ b/persist/watcher_update.go
@@ -17,10 +17,10 @@ package persist
 // WatcherUpdatable is the strengthen for Casbin watchers.
 type WatcherUpdatable interface {
 	Watcher
-	// UpdateForUpdatePolicy calls the update callback of other instances to synchronize their policy.
+	// UPdateForUpdaticy calls the update callback of other instances to synchronize their policy.
 	// It is called after Enforcer.UpdatePolicy()
 	UpdateForUpdatePolicy(oldRule, newRule []string) error
-	// UpdateForUpdatePolicies calls the update callback of other instances to synchronize their policy.
+	// UPdateForUpdaticies calls the update callback of other instances to synchronize their policy.
 	// It is called after Enforcer.UpdatePolicies()
 	UpdateForUpdatePolicies(oldRules, newRules [][]string) error
 }
diff --git a/util/util.go b/util/util.go
index be1b8b2..b167b27 100644
--- a/util/util.go
+++ b/util/util.go
@@ -28,7 +28,7 @@ func EscapeAssertion(s string) string {
 	if strings.HasPrefix(s, "r") || strings.HasPrefix(s, "p") {
 		s = strings.Replace(s, ".", "_", 1)
 	}
-	var regex = regexp.MustCompile(`(\|| |=|\)|\(|&|<|>|,|\+|-|!|\*|\/)(r|p)\.`)
+	var regex = regexp.MustCompile(`(\|| |=|\)|\(|&|<|>|,|\+|-|!|\*|\/)((r|p)[0-9]*)\.`)
 	s = regex.ReplaceAllStringFunc(s, func(m string) string {
 		return strings.Replace(m, ".", "_", 1)
 	})


Test patch:
diff --git a/enforcer_test.go b/enforcer_test.go
index 251c23b..1b8c8a0 100644
--- a/enforcer_test.go
+++ b/enforcer_test.go
@@ -519,6 +519,19 @@ func TestSubjectPriorityWithDomain(t *testing.T) {
 	})
 }
 
+func TestMultiplePolicyDefinitions(t *testing.T) {
+	e, _ := NewEnforcer("examples/multiple_policy_definitions_model.conf", "examples/multiple_policy_definitions_policy.csv")
+	enforceContext := NewEnforceContext("2")
+	enforceContext.eType = "e"
+	testBatchEnforce(t, e, [][]interface{}{
+		{"alice", "data2", "read"},
+		{enforceContext, struct{ Age int }{Age: 70}, "/data1", "read"},
+		{enforceContext, struct{ Age int }{Age: 30}, "/data1", "read"},
+	}, []bool{
+		true, false, true,
+	})
+}
+
 func TestPriorityExplicit(t *testing.T) {
 	e, _ := NewEnforcer("examples/priority_model_explicit.conf", "examples/priority_policy_explicit.csv")
 	testBatchEnforce(t, e, [][]interface{}{
diff --git a/watcher_ex_test.go b/watcher_ex_test.go
index 66596d0..8622c32 100644
--- a/watcher_ex_test.go
+++ b/watcher_ex_test.go
@@ -39,14 +39,6 @@ func (w SampleWatcherEx) UpdateForSavePolicy(model model.Model) error {
 	return nil
 }
 
-func (w SampleWatcherEx) UpdateForAddPolicies(sec string, ptype string, rules ...[]string) error {
-	return nil
-}
-
-func (w SampleWatcherEx) UpdateForRemovePolicies(sec string, ptype string, rules ...[]string) error {
-	return nil
-}
-
 func TestSetWatcherEx(t *testing.T) {
 	e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
 
@@ -60,11 +52,8 @@ func TestSetWatcherEx(t *testing.T) {
 	_, _ = e.AddPolicy("admin", "data1", "read")    // calls watcherEx.UpdateForAddPolicy()
 	_, _ = e.RemovePolicy("admin", "data1", "read") // calls watcherEx.UpdateForRemovePolicy()
 	_, _ = e.RemoveFilteredPolicy(1, "data1")       // calls watcherEx.UpdateForRemoveFilteredPolicy()
-	_, _ = e.RemovePolicy("admin", "data1", "read") // calls watcherEx.UpdateForRemovePolicy()
 	_, _ = e.AddGroupingPolicy("g:admin", "data1")
 	_, _ = e.RemoveGroupingPolicy("g:admin", "data1")
 	_, _ = e.AddGroupingPolicy("g:admin", "data1")
 	_, _ = e.RemoveFilteredGroupingPolicy(1, "data1")
-	_, _ = e.AddPolicies([][]string{{"admin", "data1", "read"}, {"admin", "data2", "read"}})    // calls watcherEx.UpdateForAddPolicies()
-	_, _ = e.RemovePolicies([][]string{{"admin", "data1", "read"}, {"admin", "data2", "read"}}) // calls watcherEx.UpdateForRemovePolicies()
 }