Skip to content

Commit be38837

Browse files
authored
Merge pull request casbin#194 from shink/master
feat: add support for custom function
2 parents 1104ca2 + d16820b commit be38837

14 files changed

+210
-103
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[request_definition]
2+
r = sub, obj, act
3+
4+
[policy_definition]
5+
p = sub_rule, obj, act
6+
7+
[policy_effect]
8+
e = some(where (p.eft == allow))
9+
10+
[matchers]
11+
m = eval(p.sub_rule) && r.act == p.act
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
p, r.sub.name == 'alice' && custom(r.obj), r.obj, GET
2+
p, r.sub.age >= 18 && custom(r.obj), r.obj, GET
+4-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
p, r.domain.equals("domain1"), admin, domain1, data1, read
2-
p, r.domain.equals("domain1"), admin, domain1, data1, write
3-
p, r.domain.equals("domain2"), admin, domain2, data2, read
4-
p, r.domain.equals("domain2"), admin, domain2, data2, write
1+
p, r.domain == 'domain1', admin, domain1, data1, read
2+
p, r.domain == 'domain1', admin, domain1, data1, write
3+
p, r.domain == 'domain2', admin, domain2, data2, read
4+
p, r.domain == 'domain2', admin, domain2, data2, write
55
g, alice, admin, domain1
66
g, bob, admin, domain2

pom.xml

+1-6
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@
188188
<dependency>
189189
<groupId>com.googlecode.aviator</groupId>
190190
<artifactId>aviator</artifactId>
191-
<version>4.1.2</version>
191+
<version>5.2.5</version>
192192
</dependency>
193193
<dependency>
194194
<groupId>com.github.seancfoley</groupId>
@@ -212,11 +212,6 @@
212212
<version>1.19</version>
213213
<scope>test</scope>
214214
</dependency>
215-
<dependency>
216-
<groupId>org.codehaus.janino</groupId>
217-
<artifactId>janino</artifactId>
218-
<version>3.1.2</version>
219-
</dependency>
220215
<dependency>
221216
<groupId>org.apache.commons</groupId>
222217
<artifactId>commons-csv</artifactId>

src/main/java/org/casbin/jcasbin/main/CoreEnforcer.java

+2
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@ private boolean enforce(String matcher, Object... rvals) {
381381
for (AviatorFunction f : functions.values()) {
382382
aviatorEval.addFunction(f);
383383
}
384+
fm.setAviatorEval(aviatorEval);
384385

385386
modelModCount = model.getModCount();
386387
}
@@ -554,6 +555,7 @@ private boolean validateEnforceSection(String section, Object... rvals) {
554555
*/
555556
public void resetExpressionEvaluator() {
556557
aviatorEval = null;
558+
fm.setAviatorEval(null);
557559
}
558560

559561
public boolean isAutoNotifyWatcher() {

src/main/java/org/casbin/jcasbin/main/ManagementEnforcer.java

+8-5
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,11 @@
1414

1515
package org.casbin.jcasbin.main;
1616

17-
import com.googlecode.aviator.runtime.type.AviatorFunction;
1817
import org.casbin.jcasbin.effect.Effect;
1918
import org.casbin.jcasbin.model.Assertion;
2019
import org.casbin.jcasbin.util.Util;
20+
import org.casbin.jcasbin.util.function.CustomFunction;
2121

22-
import java.lang.reflect.Method;
2322
import java.util.*;
2423

2524
/**
@@ -475,6 +474,7 @@ public boolean addNamedGroupingPolicy(String ptype, List<String> params) {
475474
boolean ruleAdded = addPolicy("g", ptype, params);
476475

477476
aviatorEval = null;
477+
fm.setAviatorEval(null);
478478
return ruleAdded;
479479
}
480480

@@ -534,6 +534,7 @@ public boolean removeNamedGroupingPolicy(String ptype, List<String> params) {
534534
boolean ruleRemoved = removePolicy("g", ptype, params);
535535

536536
aviatorEval = null;
537+
fm.setAviatorEval(null);
537538
return ruleRemoved;
538539
}
539540

@@ -561,18 +562,20 @@ public boolean removeFilteredNamedGroupingPolicy(String ptype, int fieldIndex, S
561562
boolean ruleRemoved = removeFilteredPolicy("g", ptype, fieldIndex, fieldValues);
562563

563564
aviatorEval = null;
565+
fm.setAviatorEval(null);
564566
return ruleRemoved;
565567
}
566568

567569
/**
568570
* addFunction adds a customized function.
569571
*
570-
* @param name the name of the new function.
571-
* @param function the function.
572+
* @param name the name of the function.
573+
* @param function the custom function.
572574
*/
573-
public void addFunction(String name, AviatorFunction function) {
575+
public void addFunction(String name, CustomFunction function) {
574576
fm.addFunction(name, function);
575577
aviatorEval = null;
578+
fm.setAviatorEval(null);
576579
}
577580

578581
/**

src/main/java/org/casbin/jcasbin/model/FunctionMap.java

+26
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
package org.casbin.jcasbin.model;
1616

17+
import com.googlecode.aviator.AviatorEvaluatorInstance;
1718
import com.googlecode.aviator.runtime.type.AviatorFunction;
1819
import org.casbin.jcasbin.util.function.*;
1920

@@ -39,6 +40,31 @@ public void addFunction(String name, AviatorFunction function) {
3940
fm.put(name, function);
4041
}
4142

43+
/**
44+
* setAviatorEval adds AviatorEvaluatorInstance to the custom function.
45+
*
46+
* @param name the name of the custom function.
47+
* @param aviatorEval the AviatorEvaluatorInstance object.
48+
*/
49+
public void setAviatorEval(String name, AviatorEvaluatorInstance aviatorEval) {
50+
if (fm.containsKey(name) && fm.get(name) instanceof CustomFunction) {
51+
((CustomFunction) fm.get(name)).setAviatorEval(aviatorEval);
52+
}
53+
}
54+
55+
/**
56+
* setAviatorEval adds AviatorEvaluatorInstance to all the custom function.
57+
*
58+
* @param aviatorEval the AviatorEvaluatorInstance object.
59+
*/
60+
public void setAviatorEval(AviatorEvaluatorInstance aviatorEval) {
61+
for (AviatorFunction function : fm.values()) {
62+
if (function instanceof CustomFunction) {
63+
((CustomFunction) function).setAviatorEval(aviatorEval);
64+
}
65+
}
66+
}
67+
4268
/**
4369
* loadFunctionMap loads an initial function map.
4470
*

src/main/java/org/casbin/jcasbin/util/BuiltInFunctions.java

+14-78
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
package org.casbin.jcasbin.util;
1616

17+
import com.googlecode.aviator.AviatorEvaluator;
18+
import com.googlecode.aviator.AviatorEvaluatorInstance;
1719
import com.googlecode.aviator.runtime.function.AbstractFunction;
1820
import com.googlecode.aviator.runtime.function.FunctionUtils;
1921
import com.googlecode.aviator.runtime.type.AviatorBoolean;
@@ -23,20 +25,15 @@
2325
import inet.ipaddr.IPAddress;
2426
import inet.ipaddr.IPAddressString;
2527
import org.casbin.jcasbin.rbac.RoleManager;
26-
import org.codehaus.commons.compiler.CompileException;
27-
import org.codehaus.janino.ExpressionEvaluator;
2828

29-
import java.lang.reflect.InvocationTargetException;
3029
import java.util.*;
31-
import java.util.Map.Entry;
3230
import java.util.regex.Matcher;
3331
import java.util.regex.Pattern;
3432

3533
public class BuiltInFunctions {
3634

3735
private static Pattern keyMatch2Pattern = Pattern.compile("(.*):[^/]+(.*)");
3836
private static Pattern keyMatch3Pattern = Pattern.compile("(.*)\\{[^/]+}(.*)");
39-
private static Pattern evalPattern = Pattern.compile("(?<=\\.).*?(?=\\.| )");
4037

4138
/**
4239
* keyMatch determines whether key1 matches the pattern of key2 (similar to RESTful path), key2
@@ -363,81 +360,20 @@ public String getName() {
363360
}
364361

365362
/**
366-
* eval calculates the stringified boolean expression and return its result. The syntax of
367-
* expressions is exactly the same as Java. Flaw: dynamically generated classes or non-static
368-
* inner class cannot be used.
363+
* eval calculates the stringified boolean expression and return its result.
369364
*
370-
* @author tldyl
371-
* @since 2020-07-02
372-
*
373-
* @param eval Boolean expression.
374-
* @param env Parameters.
375-
* @return The result of the eval.
365+
* @param eval the stringified boolean expression.
366+
* @param env the key-value pair of the parameters in the expression.
367+
* @param aviatorEval the AviatorEvaluatorInstance object which contains built-in functions and custom functions.
368+
* @return the result of the eval.
376369
*/
377-
public static boolean eval(String eval, Map<String, Object> env) {
378-
ExpressionEvaluator evaluator = new ExpressionEvaluator();
379-
Map<String, Map<String, Object>> evalModels = getEvalModels(env);
380-
try {
381-
List<String> parameterNameList = new ArrayList<>();
382-
List<Object> parameterValueList = new ArrayList<>();
383-
List<Class<?>> parameterClassList = new ArrayList<>();
384-
for (Map.Entry<String, Object> entry: env.entrySet()) {
385-
parameterNameList.add(entry.getKey());
386-
parameterValueList.add(entry.getValue());
387-
parameterClassList.add(entry.getValue().getClass());
388-
}
389-
List<String> sortedSrc = new ArrayList<>(getReplaceTargets(evalModels));
390-
sortedSrc.sort((o1, o2) -> o1.length() > o2.length() ? -1 : 1);
391-
for (String s : sortedSrc) {
392-
eval = eval.replace("." + s, "_" + s);
393-
}
394-
Matcher matcher = evalPattern.matcher(eval);
395-
while (matcher.find()) {
396-
for (int i = 0; i <= matcher.groupCount(); i++) {
397-
eval = eval.replace(matcher.group(), obtainFieldGetMethodName(matcher.group()));
398-
}
399-
}
400-
evaluator.setParameters(parameterNameList.toArray(new String[0]), parameterClassList.toArray(new Class[0]));
401-
evaluator.cook(eval);
402-
return (boolean) evaluator.evaluate(parameterValueList.toArray(new Object[0]));
403-
} catch (InvocationTargetException | CompileException e) {
404-
e.printStackTrace();
370+
public static boolean eval(String eval, Map<String, Object> env, AviatorEvaluatorInstance aviatorEval) {
371+
boolean res;
372+
if (aviatorEval != null) {
373+
res = (boolean) aviatorEval.execute(eval, env);
374+
} else {
375+
res = (boolean) AviatorEvaluator.execute(eval, env);
405376
}
406-
return false;
407-
}
408-
409-
/**
410-
* getEvalModels extracts the value from env and assemble it into a EvalModel object.
411-
*
412-
* @param env the map.
413-
*/
414-
private static Map<String, Map<String, Object>> getEvalModels(Map<String, Object> env) {
415-
final Map<String, Map<String, Object>> evalModels = new HashMap<>();
416-
for (final Entry<String, Object> entry : env.entrySet()) {
417-
final String[] names = entry.getKey().split("_");
418-
evalModels.computeIfAbsent(names[0], k -> new HashMap<>()).put(names[1], entry.getValue());
419-
}
420-
return evalModels;
421-
}
422-
423-
private static Set<String> getReplaceTargets(Map<String, Map<String, Object>> evalModels) {
424-
Set<String> ret = new HashSet<>();
425-
for (final Entry<String, Map<String, Object>> entry : evalModels.entrySet()) {
426-
ret.addAll(entry.getValue().keySet());
427-
}
428-
return ret;
429-
}
430-
431-
/**
432-
* Get the function name of its get method according to the field name.
433-
* For example, the input parameter is "age", the output parameter is "getAge()"
434-
*
435-
* @param fieldName the file name.
436-
* @return the function name of its get method.
437-
*/
438-
private static String obtainFieldGetMethodName(String fieldName) {
439-
return new StringBuffer().append("get")
440-
.append(fieldName.substring(0, 1).toUpperCase())
441-
.append(fieldName.substring(1)).append("()").toString();
377+
return res;
442378
}
443379
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright 2020 The casbin Authors. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package org.casbin.jcasbin.util.function;
16+
17+
import com.googlecode.aviator.AviatorEvaluatorInstance;
18+
import com.googlecode.aviator.runtime.function.AbstractFunction;
19+
20+
import java.util.Map;
21+
22+
/**
23+
* @author: shink
24+
*/
25+
public abstract class CustomFunction extends AbstractFunction {
26+
27+
private AviatorEvaluatorInstance aviatorEval;
28+
29+
public String replaceTargets(String exp, Map<String, Object> env) {
30+
for (String key : env.keySet()) {
31+
int index;
32+
if ((index = key.indexOf('_')) != -1) {
33+
String s = key.substring(index + 1);
34+
exp = exp.replace("." + s, "_" + s);
35+
}
36+
}
37+
return exp;
38+
}
39+
40+
public AviatorEvaluatorInstance getAviatorEval() {
41+
return aviatorEval;
42+
}
43+
44+
public void setAviatorEval(AviatorEvaluatorInstance aviatorEval) {
45+
this.aviatorEval = aviatorEval;
46+
}
47+
}

src/main/java/org/casbin/jcasbin/util/function/EvalFunc.java

+8-6
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
package org.casbin.jcasbin.util.function;
1616

17-
import com.googlecode.aviator.runtime.function.AbstractFunction;
1817
import com.googlecode.aviator.runtime.function.FunctionUtils;
1918
import com.googlecode.aviator.runtime.type.AviatorBoolean;
2019
import com.googlecode.aviator.runtime.type.AviatorObject;
@@ -24,14 +23,17 @@
2423

2524
/**
2625
* EvalFunc is the wrapper for eval.
27-
* @author tldyl
28-
* @since 2020-07-02
26+
* It extends CustomFunction, so it can be used in matcher and policy rule.
27+
*
28+
* @author shink
2929
*/
30-
public class EvalFunc extends AbstractFunction {
30+
public class EvalFunc extends CustomFunction {
31+
3132
@Override
3233
public AviatorObject call(Map<String, Object> env, AviatorObject arg1) {
33-
String ev = FunctionUtils.getStringValue(arg1, env);
34-
return AviatorBoolean.valueOf(BuiltInFunctions.eval(ev, env));
34+
String eval = FunctionUtils.getStringValue(arg1, env);
35+
eval = replaceTargets(eval, env);
36+
return AviatorBoolean.valueOf(BuiltInFunctions.eval(eval, env, getAviatorEval()));
3537
}
3638

3739
@Override

src/test/java/org/casbin/jcasbin/main/AbacAPIUnitTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public void testEvalWithDomain() {
5252
testDomainEnforce(e, "bob", "domain2", "data2", "read", true);
5353
}
5454

55-
public static class TestEvalRule { //This class must be static.
55+
public static class TestEvalRule {
5656
private String name;
5757
private int age;
5858

0 commit comments

Comments
 (0)