Skip to content

Commit dc122cd

Browse files
authored
Merge pull request #126 from shy1st/master
Add globMatch pattern.
2 parents 9220fb2 + 34b5bb8 commit dc122cd

File tree

10 files changed

+352
-16
lines changed

10 files changed

+352
-16
lines changed

examples/glob_model.conf

Lines changed: 11 additions & 0 deletions
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, obj, act
6+
7+
[policy_effect]
8+
e = some(where (p.eft == allow))
9+
10+
[matchers]
11+
m = r.sub == p.sub && globMatch(r.obj, p.obj) && r.act == p.act

examples/glob_policy.csv

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
p, u1, /foo/*, read
2+
p, u2, /foo*, read
3+
p, u3, /*/foo/*, read
4+
p, u4, *, read

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ public Enforcer(String modelPath, String policyFile) {
5252
*/
5353
public Enforcer(String modelPath, Adapter adapter) {
5454
this(newModel(modelPath, ""), adapter);
55-
5655
this.modelPath = modelPath;
5756
}
5857

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public static FunctionMap loadFunctionMap() {
5555
fm.addFunction("regexMatch", new RegexMatchFunc());
5656
fm.addFunction("ipMatch", new IPMatchFunc());
5757
fm.addFunction("eval", new EvalFunc());
58+
fm.addFunction("globMatch", new GlobMatchFunc());
5859

5960
return fm;
6061
}

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

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,22 @@
1414

1515
package org.casbin.jcasbin.util;
1616

17-
import java.util.ArrayList;
18-
import java.util.HashMap;
19-
import java.util.HashSet;
20-
import java.util.List;
21-
import java.util.Map;
22-
import java.util.Map.Entry;
23-
import java.util.Set;
24-
import java.util.regex.Matcher;
25-
import java.util.regex.Pattern;
26-
27-
import org.casbin.jcasbin.rbac.RoleManager;
28-
17+
import bsh.EvalError;
18+
import bsh.Interpreter;
2919
import com.googlecode.aviator.runtime.function.AbstractFunction;
3020
import com.googlecode.aviator.runtime.function.FunctionUtils;
3121
import com.googlecode.aviator.runtime.type.AviatorBoolean;
3222
import com.googlecode.aviator.runtime.type.AviatorFunction;
3323
import com.googlecode.aviator.runtime.type.AviatorObject;
34-
35-
import bsh.EvalError;
36-
import bsh.Interpreter;
3724
import inet.ipaddr.AddressStringException;
3825
import inet.ipaddr.IPAddress;
3926
import inet.ipaddr.IPAddressString;
27+
import org.casbin.jcasbin.rbac.RoleManager;
28+
29+
import java.util.*;
30+
import java.util.Map.Entry;
31+
import java.util.regex.Matcher;
32+
import java.util.regex.Pattern;
4033

4134
public class BuiltInFunctions {
4235

@@ -243,6 +236,17 @@ public static boolean ipMatch(String ip1, String ip2) {
243236
return ipa1.mask(mask).equals(ipas2.getHostAddress());
244237
}
245238

239+
/**
240+
* globMatch determines whether key1 matches the pattern of key2 in glob expression.
241+
*
242+
* @param key1 the first argument.
243+
* @param key2 the second argument.
244+
* @return whether key1 matches key2.
245+
*/
246+
public static boolean globMatch(String key1, String key2) {
247+
return Pattern.matches(Glob.toRegexPattern(key2), key1);
248+
}
249+
246250
/**
247251
* allMatch determines whether key1 matches the pattern of key2 , key2 can contain a *.
248252
*
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
// Copyright 2021 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+
16+
package org.casbin.jcasbin.util;
17+
import java.util.regex.PatternSyntaxException;
18+
19+
/**
20+
*
21+
*
22+
* @author shy
23+
* @since 2021/1/13
24+
*/
25+
public class Glob {
26+
27+
private static final String REGEX_META_CHARS = ".^$+{[]|()";
28+
private static final String GLOB_META_CHARS = "\\*?[{";
29+
30+
/**
31+
* Creates a regex pattern from the given glob expression.
32+
*
33+
* @param globPattern the given glob expression
34+
* @return the regex pattern
35+
*/
36+
public static String toRegexPattern(String globPattern) {
37+
boolean inGroup = false;
38+
StringBuilder regex = new StringBuilder("^");
39+
40+
int i = 0;
41+
while (i < globPattern.length()) {
42+
char c = globPattern.charAt(i++);
43+
switch (c) {
44+
case '\\':
45+
// escape special characters
46+
if (i == globPattern.length()) {
47+
throw new PatternSyntaxException("No character to escape",
48+
globPattern, i - 1);
49+
}
50+
char next = globPattern.charAt(i++);
51+
if (isGlobMeta(next) || isRegexMeta(next)) {
52+
regex.append('\\');
53+
}
54+
regex.append(next);
55+
break;
56+
case '/':
57+
regex.append(c);
58+
break;
59+
case '[':
60+
// don't match name separator in class
61+
regex.append("[[^/]&&[");
62+
if (next(globPattern, i) == '^') {
63+
// escape the regex negation char if it appears
64+
regex.append("\\^");
65+
i++;
66+
} else {
67+
// negation
68+
if (next(globPattern, i) == '!') {
69+
regex.append('^');
70+
i++;
71+
}
72+
// hyphen allowed at start
73+
if (next(globPattern, i) == '-') {
74+
regex.append('-');
75+
i++;
76+
}
77+
}
78+
boolean hasRangeStart = false;
79+
char last = 0;
80+
while (i < globPattern.length()) {
81+
c = globPattern.charAt(i++);
82+
if (c == ']') {
83+
break;
84+
}
85+
if (c == '/') {
86+
throw new PatternSyntaxException("Explicit 'name separator' in class",
87+
globPattern, i - 1);
88+
}
89+
// TBD: how to specify ']' in a class?
90+
if (c == '\\' || c == '[' || c == '&' && next(globPattern, i) == '&') {
91+
// escape '\', '[' or "&&" for regex class
92+
regex.append('\\');
93+
}
94+
regex.append(c);
95+
96+
if (c == '-') {
97+
if (!hasRangeStart) {
98+
throw new PatternSyntaxException("Invalid range",
99+
globPattern, i - 1);
100+
}
101+
if ((c = next(globPattern, i++)) == 0 || c == ']') {
102+
break;
103+
}
104+
if (c < last) {
105+
throw new PatternSyntaxException("Invalid range",
106+
globPattern, i - 3);
107+
}
108+
regex.append(c);
109+
hasRangeStart = false;
110+
} else {
111+
hasRangeStart = true;
112+
last = c;
113+
}
114+
}
115+
if (c != ']') {
116+
throw new PatternSyntaxException("Missing ']", globPattern, i - 1);
117+
}
118+
regex.append("]]");
119+
break;
120+
case '{':
121+
if (inGroup) {
122+
throw new PatternSyntaxException("Cannot nest groups",
123+
globPattern, i - 1);
124+
}
125+
regex.append("(?:(?:");
126+
inGroup = true;
127+
break;
128+
case '}':
129+
if (inGroup) {
130+
regex.append("))");
131+
inGroup = false;
132+
} else {
133+
regex.append('}');
134+
}
135+
break;
136+
case ',':
137+
if (inGroup) {
138+
regex.append(")|(?:");
139+
} else {
140+
regex.append(',');
141+
}
142+
break;
143+
case '*':
144+
if (next(globPattern, i) == '*') {
145+
// crosses directory boundaries
146+
regex.append(".*");
147+
i++;
148+
} else {
149+
// within directory boundary
150+
regex.append("[^/]*");
151+
}
152+
break;
153+
case '?':
154+
regex.append("[^/]");
155+
break;
156+
157+
default:
158+
if (isRegexMeta(c)) {
159+
regex.append('\\');
160+
}
161+
regex.append(c);
162+
}
163+
}
164+
165+
if (inGroup) {
166+
throw new PatternSyntaxException("Missing '}", globPattern, i - 1);
167+
}
168+
169+
return regex.append('$').toString();
170+
}
171+
172+
private static boolean isRegexMeta(char c) {
173+
return REGEX_META_CHARS.indexOf(c) != -1;
174+
}
175+
176+
private static boolean isGlobMeta(char c) {
177+
return GLOB_META_CHARS.indexOf(c) != -1;
178+
}
179+
180+
private static char next(String glob, int i) {
181+
return i < glob.length() ? glob.charAt(i) : 0;
182+
}
183+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright 2021 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.runtime.function.AbstractFunction;
18+
import com.googlecode.aviator.runtime.function.FunctionUtils;
19+
import com.googlecode.aviator.runtime.type.AviatorBoolean;
20+
import com.googlecode.aviator.runtime.type.AviatorObject;
21+
import org.casbin.jcasbin.util.BuiltInFunctions;
22+
23+
import java.util.Map;
24+
25+
/**
26+
* GlobMatchFunc is the wrapper for globMatch.
27+
*
28+
* @author shy
29+
* @since 2021/1/12
30+
*/
31+
public class GlobMatchFunc extends AbstractFunction {
32+
@Override
33+
public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
34+
String key1 = FunctionUtils.getStringValue(arg1, env);
35+
String key2 = FunctionUtils.getStringValue(arg2, env);
36+
37+
return AviatorBoolean.valueOf(BuiltInFunctions.globMatch(key1, key2));
38+
}
39+
40+
@Override
41+
public String getName() {
42+
return "globMatch";
43+
}
44+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright 2021 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.main;
16+
17+
import org.junit.Test;
18+
import static org.casbin.jcasbin.main.TestUtil.testGlobMatch;
19+
20+
public class BuiltInFunctionsUnitTest {
21+
22+
@Test
23+
public void testGlobMatchFunc() {
24+
testGlobMatch("/foo", "/foo", true);
25+
testGlobMatch("/foo", "/foo*", true);
26+
testGlobMatch("/foo", "/foo/*", false);
27+
testGlobMatch("/foo/bar", "/foo", false);
28+
testGlobMatch("/foo/bar", "/foo*", false);
29+
testGlobMatch("/foo/bar", "/foo/*", true);
30+
testGlobMatch("/foobar", "/foo", false);
31+
testGlobMatch("/foobar", "/foo*", true);
32+
testGlobMatch("/foobar", "/foo/*", false);
33+
34+
testGlobMatch("/foo", "*/foo", true);
35+
testGlobMatch("/foo", "*/foo*", true);
36+
testGlobMatch("/foo", "*/foo/*", false);
37+
testGlobMatch("/foo/bar", "*/foo", false);
38+
testGlobMatch("/foo/bar", "*/foo*", false);
39+
testGlobMatch("/foo/bar", "*/foo/*", true);
40+
testGlobMatch("/foobar", "*/foo", false);
41+
testGlobMatch("/foobar", "*/foo*", true);
42+
testGlobMatch("/foobar", "*/foo/*", false);
43+
44+
testGlobMatch("/prefix/foo", "*/foo", false);
45+
testGlobMatch("/prefix/foo", "*/foo*", false);
46+
testGlobMatch("/prefix/foo", "*/foo/*", false);
47+
testGlobMatch("/prefix/foo/bar", "*/foo", false);
48+
testGlobMatch("/prefix/foo/bar", "*/foo*", false);
49+
testGlobMatch("/prefix/foo/bar", "*/foo/*", false);
50+
testGlobMatch("/prefix/foobar", "*/foo", false);
51+
testGlobMatch("/prefix/foobar", "*/foo*", false);
52+
testGlobMatch("/prefix/foobar", "*/foo/*", false);
53+
54+
testGlobMatch("/prefix/subprefix/foo", "*/foo", false);
55+
testGlobMatch("/prefix/subprefix/foo", "*/foo*", false);
56+
testGlobMatch("/prefix/subprefix/foo", "*/foo/*", false);
57+
testGlobMatch("/prefix/subprefix/foo/bar", "*/foo", false);
58+
testGlobMatch("/prefix/subprefix/foo/bar", "*/foo*", false);
59+
testGlobMatch("/prefix/subprefix/foo/bar", "*/foo/*", false);
60+
testGlobMatch("/prefix/subprefix/foobar", "*/foo", false);
61+
testGlobMatch("/prefix/subprefix/foobar", "*/foo*", false);
62+
testGlobMatch("/prefix/subprefix/foobar", "*/foo/*", false);
63+
}
64+
}

0 commit comments

Comments
 (0)