Repository: casbin/casbin
Pull request #620 (2020-10-12 18:21:16)

fix: multiple eval() in matcher
Signed-off-by: Zixuan Liu <nodeces@gmail.com>

Fix: https://github.com/casbin/casbin/issues/619


Patch:
diff --git a/enforcer.go b/enforcer.go
index 12a9d15..88e3fd6 100644
--- a/enforcer.go
+++ b/enforcer.go
@@ -441,21 +441,20 @@ func (e *Enforcer) enforce(matcher string, explains *[]string, rvals ...interfac
 
 			if hasEval {
 				ruleNames := util.GetEvalValue(expString)
-				var expWithRule = expString
+				replacements := make(map[string]string)
 				for _, ruleName := range ruleNames {
 					if j, ok := parameters.pTokens[ruleName]; ok {
 						rule := util.EscapeAssertion(pvals[j])
-						expWithRule = util.ReplaceEval(expWithRule, rule)
+						replacements[ruleName] = rule
 					} else {
 						return false, errors.New("please make sure rule exists in policy when using eval() in matcher")
 					}
-
-					expression, err = govaluate.NewEvaluableExpressionWithFunctions(expWithRule, functions)
-					if err != nil {
-						return false, fmt.Errorf("p.sub_rule should satisfy the syntax of matcher: %s", err)
-					}
 				}
-
+				expWithRule := util.ReplaceEvalWithMap(expString, replacements)
+				expression, err = govaluate.NewEvaluableExpressionWithFunctions(expWithRule, functions)
+				if err != nil {
+					return false, fmt.Errorf("p.sub_rule should satisfy the syntax of matcher: %s", err)
+				}
 			}
 
 			result, err := expression.Eval(parameters)
diff --git a/util/util.go b/util/util.go
index c7e0c06..d17548a 100644
--- a/util/util.go
+++ b/util/util.go
@@ -160,6 +160,22 @@ func ReplaceEval(s string, rule string) string {
 	return evalReg.ReplaceAllString(s, "("+rule+")")
 }
 
+// ReplaceEvalWithMap replace function eval with the value of its parameters via given sets.
+func ReplaceEvalWithMap(src string, sets map[string]string) string {
+	return evalReg.ReplaceAllStringFunc(src, func(s string) string {
+		subs := evalReg.FindStringSubmatch(s)
+		if subs == nil {
+			return s
+		}
+		key := subs[1]
+		value, found := sets[key]
+		if !found {
+			return s
+		}
+		return evalReg.ReplaceAllString(s, value)
+	})
+}
+
 // GetEvalValue returns the parameters of function eval
 func GetEvalValue(s string) []string {
 	subMatch := evalReg.FindAllStringSubmatch(s, -1)


Test patch:
diff --git a/util/util_test.go b/util/util_test.go
index 39732cd..8971124 100644
--- a/util/util_test.go
+++ b/util/util_test.go
@@ -157,3 +157,27 @@ func TestGetEvalValue(t *testing.T) {
 	testGetEvalValue(t, "eval(a) && eval(b) && a && b && c", []string{"a", "b"})
 	testGetEvalValue(t, "a && eval(a) && eval(b) && b && c", []string{"a", "b"})
 }
+
+func testReplaceEvalWithMap(t *testing.T, s string, sets map[string]string, res string) {
+	t.Helper()
+	myRes := ReplaceEvalWithMap(s, sets)
+
+	if myRes != res {
+		t.Errorf("%s: %s supposed to be %s", s, myRes, res)
+	}
+}
+
+func TestReplaceEvalWithMap(t *testing.T) {
+	testReplaceEvalWithMap(t, "eval(rule1)", map[string]string{"rule1": "a == b"}, "a == b")
+	testReplaceEvalWithMap(t, "eval(rule1) && c && d", map[string]string{"rule1": "a == b"}, "a == b && c && d")
+	testReplaceEvalWithMap(t, "eval(rule1)", nil, "eval(rule1)")
+	testReplaceEvalWithMap(t, "eval(rule1) && c && d", nil, "eval(rule1) && c && d")
+	testReplaceEvalWithMap(t, "eval(rule1) || eval(rule2)", map[string]string{"rule1": "a == b", "rule2": "a == c"}, "a == b || a == c")
+	testReplaceEvalWithMap(t, "eval(rule1) || eval(rule2) && c && d", map[string]string{"rule1": "a == b", "rule2": "a == c"}, "a == b || a == c && c && d")
+	testReplaceEvalWithMap(t, "eval(rule1) || eval(rule2)", map[string]string{"rule1": "a == b"}, "a == b || eval(rule2)")
+	testReplaceEvalWithMap(t, "eval(rule1) || eval(rule2) && c && d", map[string]string{"rule1": "a == b"}, "a == b || eval(rule2) && c && d")
+	testReplaceEvalWithMap(t, "eval(rule1) || eval(rule2)", map[string]string{"rule2": "a == b"}, "eval(rule1) || a == b")
+	testReplaceEvalWithMap(t, "eval(rule1) || eval(rule2) && c && d", map[string]string{"rule2": "a == b"}, "eval(rule1) || a == b && c && d")
+	testReplaceEvalWithMap(t, "eval(rule1) || eval(rule2)", nil, "eval(rule1) || eval(rule2)")
+	testReplaceEvalWithMap(t, "eval(rule1) || eval(rule2) && c && d", nil, "eval(rule1) || eval(rule2) && c && d")
+}