Repository: casbin/casbin
Pull request #1009 (2022-05-12 07:23:07)

feat: support GetFilteredNamedPolicyWithMatcher
Signed-off-by: tangyang9464 <tangyang9464@163.com>

Fix: https://github.com/casbin/casbin/issues/1007

casbin-bot:  @closetool @sagilio please review
hsluoyz: @medghaim plz review
medghaim: This is awesome, thanks for the quick PR! I think this will be a nice addition to the management API, as there's definitely a use-case for it.

One question - does the required formatting of the matcher string (single quotes, etc) resemble the required formatting of the matcher string in `EnforceWithMatcher`?
tangyang9464: > This is awesome, thanks for the quick PR! I think this will be a nice addition to the management API, as there's definitely a use-case for it.
> 
> One question - does the required formatting of the matcher string (single quotes, etc) resemble the required formatting of the matcher string in `EnforceWithMatcher`?

@medghaim In fact, they are in the same format. Single quotation marks are recognized as string constants, and vice versa. In this PR, it is obvious that without single quotation marks there will be an error

Patch:
diff --git a/management_api.go b/management_api.go
index f3f62b9..a8456b7 100644
--- a/management_api.go
+++ b/management_api.go
@@ -14,7 +14,14 @@
 
 package casbin
 
-import "github.com/Knetic/govaluate"
+import (
+	"errors"
+	"fmt"
+	"strings"
+
+	"github.com/Knetic/govaluate"
+	"github.com/casbin/casbin/v2/util"
+)
 
 // GetAllSubjects gets the list of subjects that show up in the current policy.
 func (e *Enforcer) GetAllSubjects() []string {
@@ -96,6 +103,77 @@ func (e *Enforcer) GetFilteredNamedGroupingPolicy(ptype string, fieldIndex int,
 	return e.model.GetFilteredPolicy("g", ptype, fieldIndex, fieldValues...)
 }
 
+// GetFilteredNamedPolicyWithMatcher gets rules based on matcher from the policy.
+func (e *Enforcer) GetFilteredNamedPolicyWithMatcher(ptype string, matcher string) ([][]string, error) {
+	var res [][]string
+	var err error
+
+	functions := e.fm.GetFunctions()
+	if _, ok := e.model["g"]; ok {
+		for key, ast := range e.model["g"] {
+			rm := ast.RM
+			functions[key] = util.GenerateGFunction(rm)
+		}
+	}
+
+	var expString string
+	if matcher == "" {
+		return res, fmt.Errorf("matcher is empty")
+	} else {
+		expString = util.RemoveComments(util.EscapeAssertion(matcher))
+	}
+
+	var expression *govaluate.EvaluableExpression
+
+	expression, err = govaluate.NewEvaluableExpressionWithFunctions(expString, functions)
+	if err != nil {
+		return res, err
+	}
+
+	pTokens := make(map[string]int, len(e.model["p"][ptype].Tokens))
+	for i, token := range e.model["p"][ptype].Tokens {
+		pTokens[token] = i
+	}
+
+	parameters := enforceParameters{
+		pTokens: pTokens,
+	}
+
+	if policyLen := len(e.model["p"][ptype].Policy); policyLen != 0 && strings.Contains(expString, ptype+"_") {
+		for _, pvals := range e.model["p"][ptype].Policy {
+			if len(e.model["p"][ptype].Tokens) != len(pvals) {
+				return res, fmt.Errorf(
+					"invalid policy size: expected %d, got %d, pvals: %v",
+					len(e.model["p"][ptype].Tokens),
+					len(pvals),
+					pvals)
+			}
+
+			parameters.pVals = pvals
+
+			result, err := expression.Eval(parameters)
+
+			if err != nil {
+				return res, err
+			}
+
+			switch result := result.(type) {
+			case bool:
+				if result {
+					res = append(res, pvals)
+				}
+			case float64:
+				if result != 0 {
+					res = append(res, pvals)
+				}
+			default:
+				return res, errors.New("matcher result should be bool, int or float")
+			}
+		}
+	}
+	return res, nil
+}
+
 // HasPolicy determines whether an authorization rule exists.
 func (e *Enforcer) HasPolicy(params ...interface{}) bool {
 	return e.HasNamedPolicy("p", params...)


Test patch:
diff --git a/management_api_test.go b/management_api_test.go
index ced884e..37215fd 100644
--- a/management_api_test.go
+++ b/management_api_test.go
@@ -59,6 +59,20 @@ func testGetFilteredPolicy(t *testing.T, e *Enforcer, fieldIndex int, res [][]st
 	}
 }
 
+func testGetFilteredNamedPolicyWithMatcher(t *testing.T, e *Enforcer, ptype string, matcher string, res [][]string) {
+	t.Helper()
+	myRes, err := e.GetFilteredNamedPolicyWithMatcher(ptype, matcher)
+	t.Log("Policy for", matcher, ": ", myRes)
+
+	if err != nil {
+		t.Error(err)
+	}
+
+	if !util.Array2DEquals(res, myRes) {
+		t.Error("Policy for ", matcher, ": ", myRes, ", supposed to be ", res)
+	}
+}
+
 func testGetGroupingPolicy(t *testing.T, e *Enforcer, res [][]string) {
 	t.Helper()
 	myRes := e.GetGroupingPolicy()
@@ -116,6 +130,13 @@ func TestGetPolicyAPI(t *testing.T) {
 	testGetFilteredPolicy(t, e, 2, [][]string{{"alice", "data1", "read"}, {"data2_admin", "data2", "read"}}, "read")
 	testGetFilteredPolicy(t, e, 2, [][]string{{"bob", "data2", "write"}, {"data2_admin", "data2", "write"}}, "write")
 
+	testGetFilteredNamedPolicyWithMatcher(t, e, "p", "'alice' == p.sub", [][]string{{"alice", "data1", "read"}})
+	testGetFilteredNamedPolicyWithMatcher(t, e, "p", "keyMatch2(p.sub, '*')", [][]string{
+		{"alice", "data1", "read"},
+		{"bob", "data2", "write"},
+		{"data2_admin", "data2", "read"},
+		{"data2_admin", "data2", "write"}})
+
 	testGetFilteredPolicy(t, e, 0, [][]string{{"data2_admin", "data2", "read"}, {"data2_admin", "data2", "write"}}, "data2_admin", "data2")
 	// Note: "" (empty string) in fieldValues means matching all values.
 	testGetFilteredPolicy(t, e, 0, [][]string{{"data2_admin", "data2", "read"}}, "data2_admin", "", "read")