Repository: casbin/casbin
Pull request #675 (2021-01-10 08:00:48)

feat: add Extracting variables from paths(#673)
Signed-off-by: closetool <c299999999@qq.com>
add new feature

Fix: https://github.com/casbin/casbin/issues/673

hsluoyz: @closetool the current code looks ok to me. Can you add some tests for the `keyGet()` and `keyGet2()` functions?

Patch:
diff --git a/model/function.go b/model/function.go
index 1f7ddd9..2ac426f 100644
--- a/model/function.go
+++ b/model/function.go
@@ -39,7 +39,9 @@ func LoadFunctionMap() FunctionMap {
 	fm.fns = &sync.Map{}
 
 	fm.AddFunction("keyMatch", util.KeyMatchFunc)
+	fm.AddFunction("keyGet", util.KeyGetFunc)
 	fm.AddFunction("keyMatch2", util.KeyMatch2Func)
+	fm.AddFunction("keyGet2", util.KeyGet2Func)
 	fm.AddFunction("keyMatch3", util.KeyMatch3Func)
 	fm.AddFunction("keyMatch4", util.KeyMatch4Func)
 	fm.AddFunction("regexMatch", util.RegexMatchFunc)
diff --git a/persist/dispatcher.go b/persist/dispatcher.go
index 6c2f781..671154b 100644
--- a/persist/dispatcher.go
+++ b/persist/dispatcher.go
@@ -25,5 +25,5 @@ type Dispatcher interface {
 	// ClearPolicy clears all current policy in all instances
 	ClearPolicy() error
 	// UpdatePolicy updates policy rule from all instance.
-	UpdatePolicy(sec string, ptype string, oldRule, newRule[]string) error
+	UpdatePolicy(sec string, ptype string, oldRule, newRule []string) error
 }
diff --git a/rbac_api_synced.go b/rbac_api_synced.go
index a62fc52..978cd31 100644
--- a/rbac_api_synced.go
+++ b/rbac_api_synced.go
@@ -40,7 +40,7 @@ func (e *SyncedEnforcer) HasRoleForUser(name string, role string, domain ...stri
 func (e *SyncedEnforcer) AddRoleForUser(user string, role string, domain ...string) (bool, error) {
 	e.m.Lock()
 	defer e.m.Unlock()
-	return e.Enforcer.AddRoleForUser(user, role, domain ...)
+	return e.Enforcer.AddRoleForUser(user, role, domain...)
 }
 
 // DeleteRoleForUser deletes a role for a user.
@@ -48,7 +48,7 @@ func (e *SyncedEnforcer) AddRoleForUser(user string, role string, domain ...stri
 func (e *SyncedEnforcer) DeleteRoleForUser(user string, role string, domain ...string) (bool, error) {
 	e.m.Lock()
 	defer e.m.Unlock()
-	return e.Enforcer.DeleteRoleForUser(user, role, domain ...)
+	return e.Enforcer.DeleteRoleForUser(user, role, domain...)
 }
 
 // DeleteRolesForUser deletes all roles for a user.
@@ -56,7 +56,7 @@ func (e *SyncedEnforcer) DeleteRoleForUser(user string, role string, domain ...s
 func (e *SyncedEnforcer) DeleteRolesForUser(user string, domain ...string) (bool, error) {
 	e.m.Lock()
 	defer e.m.Unlock()
-	return e.Enforcer.DeleteRolesForUser(user, domain ...)
+	return e.Enforcer.DeleteRolesForUser(user, domain...)
 }
 
 // DeleteUser deletes a user.
@@ -111,7 +111,7 @@ func (e *SyncedEnforcer) DeletePermissionsForUser(user string) (bool, error) {
 func (e *SyncedEnforcer) GetPermissionsForUser(user string, domain ...string) [][]string {
 	e.m.RLock()
 	defer e.m.RUnlock()
-	return e.Enforcer.GetPermissionsForUser(user, domain ...)
+	return e.Enforcer.GetPermissionsForUser(user, domain...)
 }
 
 // HasPermissionForUser determines whether a user has a permission.
diff --git a/util/builtin_operators.go b/util/builtin_operators.go
index 717c7e9..eba71cf 100755
--- a/util/builtin_operators.go
+++ b/util/builtin_operators.go
@@ -68,6 +68,34 @@ func KeyMatchFunc(args ...interface{}) (interface{}, error) {
 	return bool(KeyMatch(name1, name2)), nil
 }
 
+// KeyGet returns the matched part
+// For example, "/foo/bar/foo" matches "/foo/*"
+// "bar/foo" will been returned
+func KeyGet(key1, key2 string) string {
+	i := strings.Index(key2, "*")
+	if i == -1 {
+		return ""
+	}
+	if len(key1) > i {
+		if key1[:i] == key2[:i] {
+			return key1[i:]
+		}
+	}
+	return ""
+}
+
+// KeyGetFunc is the wrapper for KeyGet
+func KeyGetFunc(args ...interface{}) (interface{}, error) {
+	if err := validateVariadicArgs(2, args...); err != nil {
+		return false, fmt.Errorf("%s: %s", "keyGet", err)
+	}
+
+	name1 := args[0].(string)
+	name2 := args[1].(string)
+
+	return KeyGet(name1, name2), nil
+}
+
 // KeyMatch2 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *.
 // For example, "/foo/bar" matches "/foo/*", "/resource1" matches "/:resource"
 func KeyMatch2(key1 string, key2 string) bool {
@@ -91,6 +119,42 @@ func KeyMatch2Func(args ...interface{}) (interface{}, error) {
 	return bool(KeyMatch2(name1, name2)), nil
 }
 
+// KeyGet2 returns value matched pattern
+// For example, "/resource1" matches "/:resource"
+// if the pathVar == "resource", then "resource1" will be returned
+func KeyGet2(key1, key2 string, pathVar string) string {
+	key2 = strings.Replace(key2, "/*", "/.*", -1)
+
+	re := regexp.MustCompile(`:[^/]+`)
+	keys := re.FindAllString(key2, -1)
+	key2 = re.ReplaceAllString(key2, "$1([^/]+)$2")
+	key2 = "^" + key2 + "$"
+	re2 := regexp.MustCompile(key2)
+	values := re2.FindAllStringSubmatch(key1, -1)
+	if len(values) == 0 {
+		return ""
+	}
+	for i, key := range keys {
+		if pathVar == key[1:] {
+			return values[0][i+1]
+		}
+	}
+	return ""
+}
+
+// KeyGet2Func is the wrapper for KeyGet2
+func KeyGet2Func(args ...interface{}) (interface{}, error) {
+	if err := validateVariadicArgs(3, args...); err != nil {
+		return false, fmt.Errorf("%s: %s", "keyGet2", err)
+	}
+
+	name1 := args[0].(string)
+	name2 := args[1].(string)
+	key := args[2].(string)
+
+	return KeyGet2(name1, name2, key), nil
+}
+
 // KeyMatch3 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *.
 // For example, "/foo/bar" matches "/foo/*", "/resource1" matches "/{resource}"
 func KeyMatch3(key1 string, key2 string) bool {


Test patch:
diff --git a/model/model_test.go b/model/model_test.go
index bfce615..06e59d3 100644
--- a/model/model_test.go
+++ b/model/model_test.go
@@ -26,10 +26,10 @@ var (
 	basicExample = filepath.Join("..", "examples", "basic_model.conf")
 	basicConfig  = &MockConfig{
 		data: map[string]string{
-			"request_definition::r":"sub, obj, act",
-			"policy_definition::p":"sub, obj, act",
-			"policy_effect::e":"some(where (p.eft == allow))",
-			"matchers::m":"r.sub == p.sub && r.obj == p.obj && r.act == p.act",
+			"request_definition::r": "sub, obj, act",
+			"policy_definition::p":  "sub, obj, act",
+			"policy_effect::e":      "some(where (p.eft == allow))",
+			"matchers::m":           "r.sub == p.sub && r.obj == p.obj && r.act == p.act",
 		},
 	}
 )
diff --git a/model_test.go b/model_test.go
index 6dba41a..41ced2c 100644
--- a/model_test.go
+++ b/model_test.go
@@ -348,7 +348,7 @@ func (rm *testCustomRoleManager) GetRoles(name string, domain ...string) ([]stri
 func (rm *testCustomRoleManager) GetUsers(name string, domain ...string) ([]string, error) {
 	return []string{}, nil
 }
-func (rm *testCustomRoleManager) PrintRoles() error { return nil }
+func (rm *testCustomRoleManager) PrintRoles() error           { return nil }
 func (rm *testCustomRoleManager) SetLogger(logger log.Logger) {}
 
 func TestRBACModelWithCustomRoleManager(t *testing.T) {
diff --git a/util/builtin_operators_test.go b/util/builtin_operators_test.go
index c55216a..10f5017 100644
--- a/util/builtin_operators_test.go
+++ b/util/builtin_operators_test.go
@@ -40,6 +40,28 @@ func TestKeyMatch(t *testing.T) {
 	testKeyMatch(t, "/foobar", "/foo/*", false)
 }
 
+func testKeyGet(t *testing.T, key1 string, key2 string, res string) {
+	t.Helper()
+	myRes := KeyGet(key1,key2)
+	t.Logf(`%s < %s: "%s"`,key1,key2,myRes)
+
+	if myRes != res {
+		t.Errorf(`%s < %s: "%s", supposed to be "%s"`,key1,key2,myRes,res)
+	}
+}
+
+func TestKeyGet(t *testing.T) {
+	testKeyGet(t,"/foo","/foo","")
+	testKeyGet(t,"/foo","/foo*","")
+	testKeyGet(t, "/foo", "/foo/*", "")
+	testKeyGet(t, "/foo/bar", "/foo", "")
+	testKeyGet(t, "/foo/bar", "/foo*", "/bar")
+	testKeyGet(t, "/foo/bar", "/foo/*", "bar")
+	testKeyGet(t, "/foobar", "/foo", "")
+	testKeyGet(t, "/foobar", "/foo*", "bar")
+	testKeyGet(t, "/foobar", "/foo/*", "")
+}
+
 func testKeyMatch2(t *testing.T, key1 string, key2 string, res bool) {
 	t.Helper()
 	myRes := KeyMatch2(key1, key2)
@@ -94,6 +116,48 @@ func TestKeyMatch2(t *testing.T) {
 	testKeyMatch2(t, "/alice/all", "/:/all", false)
 }
 
+func testKeyGet2(t *testing.T,key1 string, key2 string,pathVar string,res string) {
+	t.Helper()
+	myRes := KeyGet2(key1,key2,pathVar)
+	t.Logf(`%s < %s: %s = "%s"`,key1,key2,pathVar,myRes)
+
+	if myRes != res {
+		t.Errorf(`%s < %s: %s = "%s" supposed to be "%s"`,key1,key2,pathVar,myRes,res)
+	}
+}
+
+func TestKeyGet2(t *testing.T) {
+	testKeyGet2(t, "/foo", "/foo", "id","")
+	testKeyGet2(t, "/foo", "/foo*", "id","")
+	testKeyGet2(t, "/foo", "/foo/*", "id","")
+	testKeyGet2(t, "/foo/bar", "/foo", "id","")
+	testKeyGet2(t, "/foo/bar", "/foo*", "id","") // different with KeyMatch.
+	testKeyGet2(t, "/foo/bar", "/foo/*", "id","")
+	testKeyGet2(t, "/foobar", "/foo", "id","")
+	testKeyGet2(t, "/foobar", "/foo*", "id","") // different with KeyMatch.
+	testKeyGet2(t, "/foobar", "/foo/*", "id","")
+
+	testKeyGet2(t, "/", "/:resource", "resource","")
+	testKeyGet2(t, "/resource1", "/:resource", "resource","resource1")
+	testKeyGet2(t, "/myid", "/:id/using/:resId", "id","")
+	testKeyGet2(t, "/myid/using/myresid", "/:id/using/:resId", "id","myid")
+	testKeyGet2(t, "/myid/using/myresid", "/:id/using/:resId", "resId","myresid")
+
+	testKeyGet2(t, "/proxy/myid", "/proxy/:id/*", "id","")
+	testKeyGet2(t, "/proxy/myid/", "/proxy/:id/*", "id","myid")
+	testKeyGet2(t, "/proxy/myid/res", "/proxy/:id/*", "id","myid")
+	testKeyGet2(t, "/proxy/myid/res/res2", "/proxy/:id/*", "id","myid")
+	testKeyGet2(t, "/proxy/myid/res/res2/res3", "/proxy/:id/*", "id","myid")
+	testKeyGet2(t, "/proxy/myid/res/res2/res3", "/proxy/:id/res/*", "id","myid")
+	testKeyGet2(t, "/proxy/", "/proxy/:id/*", "id","")
+
+	testKeyGet2(t, "/alice", "/:id", "id","alice")
+	testKeyGet2(t, "/alice/all", "/:id/all", "id","alice")
+	testKeyGet2(t, "/alice", "/:id/all", "id","")
+	testKeyGet2(t, "/alice/all", "/:id", "id","")
+
+	testKeyGet2(t, "/alice/all", "/:/all", "","")
+}
 func testKeyMatch3(t *testing.T, key1 string, key2 string, res bool) {
 	t.Helper()
 	myRes := KeyMatch3(key1, key2)