diff --git a/src/main/java/org/casbin/jcasbin/model/FunctionMap.java b/src/main/java/org/casbin/jcasbin/model/FunctionMap.java index 7be2407a..82a4464d 100644 --- a/src/main/java/org/casbin/jcasbin/model/FunctionMap.java +++ b/src/main/java/org/casbin/jcasbin/model/FunctionMap.java @@ -84,6 +84,7 @@ public static FunctionMap loadFunctionMap() { fm.addFunction("keyMatch5", new KeyMatch5Func()); fm.addFunction("keyGet", new KeyGetFunc()); fm.addFunction("keyGet2", new KeyGet2Func()); + fm.addFunction("keyGet3", new KeyGet3Func()); fm.addFunction("regexMatch", new RegexMatchFunc()); fm.addFunction("ipMatch", new IPMatchFunc()); fm.addFunction("eval", new EvalFunc()); diff --git a/src/main/java/org/casbin/jcasbin/util/BuiltInFunctions.java b/src/main/java/org/casbin/jcasbin/util/BuiltInFunctions.java index e192effa..933f91aa 100644 --- a/src/main/java/org/casbin/jcasbin/util/BuiltInFunctions.java +++ b/src/main/java/org/casbin/jcasbin/util/BuiltInFunctions.java @@ -275,6 +275,47 @@ public static String keyGet2Func(String key1, String key2, String pathVar) { return ""; } + /** + * KeyGet3 returns value matched pattern. For example, "/resource1" matches "/{resource}", if the pathVar == "resource", then "resource1" will be returned. + * This is similar with KeyGet2(), except using "/proxy/{id}" instead of "/proxy/:id". + * + * @param key1 the first argument. + * @param key2 the second argument. + * @param pathVar the name of the variable to retrieve from the matched pattern. + * @return the matched part. + */ + public static String keyGet3Func(String key1, String key2, String pathVar) { + key2 = key2.replace("/*", "/.*"); + String regexp = "\\{[^/]+?\\}"; + Pattern re = Pattern.compile(regexp); + Matcher keys = re.matcher(key2); + List keysList = new ArrayList<>(); + while (keys.find()) { + keysList.add(keys.group()); + } + key2 = re.matcher(key2).replaceAll("([^/]+?)"); + // Escape { and } for Java regex compatibility since { is treated as repetition quantifier + key2 = key2.replace("{", "\\{").replace("}", "\\}"); + key2 = "^" + key2 + "$"; + Pattern re2 = Pattern.compile(key2); + Matcher values = re2.matcher(key1); + List valuesList = new ArrayList<>(); + while (values.find()) { + for (int i = 0; i <= values.groupCount(); i++) { + valuesList.add(values.group(i)); + } + } + if (valuesList.isEmpty()) { + return ""; + } + for (int i = 0; i < keysList.size(); i++) { + if (pathVar.equals(keysList.get(i).substring(1, keysList.get(i).length() - 1))) { + return valuesList.get(i + 1); + } + } + return ""; + } + /** * regexMatch determines whether key1 matches the pattern of key2 in regular expression. * diff --git a/src/main/java/org/casbin/jcasbin/util/function/KeyGet3Func.java b/src/main/java/org/casbin/jcasbin/util/function/KeyGet3Func.java new file mode 100644 index 00000000..05f59a73 --- /dev/null +++ b/src/main/java/org/casbin/jcasbin/util/function/KeyGet3Func.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021 The casbin Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.casbin.jcasbin.util.function; + +import com.googlecode.aviator.runtime.function.AbstractFunction; +import com.googlecode.aviator.runtime.function.FunctionUtils; +import com.googlecode.aviator.runtime.type.AviatorObject; +import com.googlecode.aviator.runtime.type.AviatorString; +import org.casbin.jcasbin.util.BuiltInFunctions; + +import java.util.Map; + +/** + * KeyGet3Func is the wrapper for keyGet3. + * + * @author yanglif + * @since 2021/02/09 + */ +public class KeyGet3Func extends AbstractFunction { + @Override + public AviatorObject call(Map env, AviatorObject arg1, AviatorObject arg2, AviatorObject arg3) { + String key1 = FunctionUtils.getStringValue(arg1, env); + String key2 = FunctionUtils.getStringValue(arg2, env); + String pathVar = FunctionUtils.getStringValue(arg3, env); + + return new AviatorString(BuiltInFunctions.keyGet3Func(key1, key2, pathVar)); + } + + @Override + public String getName() { + return "keyGet3"; + } +} \ No newline at end of file diff --git a/src/test/java/org/casbin/jcasbin/main/BuiltInFunctionsUnitTest.java b/src/test/java/org/casbin/jcasbin/main/BuiltInFunctionsUnitTest.java index 462c208c..92131ee0 100644 --- a/src/test/java/org/casbin/jcasbin/main/BuiltInFunctionsUnitTest.java +++ b/src/test/java/org/casbin/jcasbin/main/BuiltInFunctionsUnitTest.java @@ -214,6 +214,46 @@ public void TestKeyGet2() { testKeyGet2("/alice/all", "/:/all", "", ""); } + @Test + public void testKeyGet3Func() { + // KeyGet3() is similar with KeyGet2(), except using "/proxy/{id}" instead of "/proxy/:id". + testKeyGet3("/foo", "/foo", "id", ""); + testKeyGet3("/foo", "/foo*", "id", ""); + testKeyGet3("/foo", "/foo/*", "id", ""); + testKeyGet3("/foo/bar", "/foo", "id", ""); + testKeyGet3("/foo/bar", "/foo*", "id", ""); + testKeyGet3("/foo/bar", "/foo/*", "id", ""); + testKeyGet3("/foobar", "/foo", "id", ""); + testKeyGet3("/foobar", "/foo*", "id", ""); + testKeyGet3("/foobar", "/foo/*", "id", ""); + + testKeyGet3("/", "/{resource}", "resource", ""); + testKeyGet3("/resource1", "/{resource}", "resource", "resource1"); + testKeyGet3("/myid", "/{id}/using/{resId}", "id", ""); + testKeyGet3("/myid/using/myresid", "/{id}/using/{resId}", "id", "myid"); + testKeyGet3("/myid/using/myresid", "/{id}/using/{resId}", "resId", "myresid"); + + testKeyGet3("/proxy/myid", "/proxy/{id}/*", "id", ""); + testKeyGet3("/proxy/myid/", "/proxy/{id}/*", "id", "myid"); + testKeyGet3("/proxy/myid/res", "/proxy/{id}/*", "id", "myid"); + testKeyGet3("/proxy/myid/res/res2", "/proxy/{id}/*", "id", "myid"); + testKeyGet3("/proxy/myid/res/res2/res3", "/proxy/{id}/*", "id", "myid"); + testKeyGet3("/proxy/", "/proxy/{id}/*", "id", ""); + + testKeyGet3("/api/group1_group_name/project1_admin/info", "/api/{proj}_admin/info", "proj", ""); + testKeyGet3("/{id/using/myresid", "/{id/using/{resId}", "resId", "myresid"); + testKeyGet3("/{id/using/myresid/status}", "/{id/using/{resId}/status}", "resId", "myresid"); + + testKeyGet3("/proxy/myid/res/res2/res3", "/proxy/{id}/*/{res}", "res", "res3"); + testKeyGet3("/api/project1_admin/info", "/api/{proj}_admin/info", "proj", "project1"); + testKeyGet3("/api/group1_group_name/project1_admin/info", "/api/{g}_{gn}/{proj}_admin/info", + "g", "group1"); + testKeyGet3("/api/group1_group_name/project1_admin/info", "/api/{g}_{gn}/{proj}_admin/info", + "gn", "group_name"); + testKeyGet3("/api/group1_group_name/project1_admin/info", "/api/{g}_{gn}/{proj}_admin/info", + "proj", "project1"); + } + @Test public void testRegexMatchFunc() { testRegexMatch("/topic/create", "/topic/create", true); diff --git a/src/test/java/org/casbin/jcasbin/main/TestUtil.java b/src/test/java/org/casbin/jcasbin/main/TestUtil.java index c7684d2c..e86d1bea 100644 --- a/src/test/java/org/casbin/jcasbin/main/TestUtil.java +++ b/src/test/java/org/casbin/jcasbin/main/TestUtil.java @@ -273,6 +273,10 @@ static void testKeyGet2(String key1, String key2, String pathVar, String res) { assertEquals(res, BuiltInFunctions.keyGet2Func(key1, key2, pathVar)); } + static void testKeyGet3(String key1, String key2, String pathVar, String res) { + assertEquals(res, BuiltInFunctions.keyGet3Func(key1, key2, pathVar)); + } + static void testEval(String eval, Map env, AviatorEvaluatorInstance aviatorEval, boolean res) { assertEquals(res, BuiltInFunctions.eval(eval, env, aviatorEval)); }