This is an automated email from the ASF dual-hosted git repository.
hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin-jcasbin.git
The following commit(s) were added to refs/heads/master by this push:
new 38766ce fix: preserve domain isolation and conditional role
restrictions (#538)
38766ce is described below
commit 38766ce5c270d53a83544886a204693e575b08fe
Author: tison <[email protected]>
AuthorDate: Fri Oct 23 18:41:10 2026 +0800
fix: preserve domain isolation and conditional role restrictions (#538)
---
.../java/org/casbin/jcasbin/main/CoreEnforcer.java | 44 ++++++++-
.../jcasbin/rbac/ConditionalDomainManager.java | 20 +++--
.../org/casbin/jcasbin/main/ModelUnitTest.java | 100 +++++++++++++++++++++
3 files changed, 153 insertions(+), 11 deletions(-)
diff --git a/src/main/java/org/casbin/jcasbin/main/CoreEnforcer.java
b/src/main/java/org/casbin/jcasbin/main/CoreEnforcer.java
index e49b1e1..b1b16f5 100644
--- a/src/main/java/org/casbin/jcasbin/main/CoreEnforcer.java
+++ b/src/main/java/org/casbin/jcasbin/main/CoreEnforcer.java
@@ -46,11 +46,16 @@ import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiPredicate;
import java.util.function.Function;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* CoreEnforcer defines the core functionality of an enforcer.
*/
public class CoreEnforcer {
+ private static final Pattern STRING_LITERAL =
Pattern.compile("'(?:\\\\.|[^'\\\\])*'|\"(?:\\\\.|[^\"\\\\])*\"");
+ private static final Pattern MATCHER_FUNCTION_CALL =
Pattern.compile("(?<![\\w.])(\\w+)\\s*\\(");
+
String modelPath;
Model model;
FunctionMap fm;
@@ -409,15 +414,48 @@ public class CoreEnforcer {
private void registerDomainMatchingFunc(String ptype) {
Assertion r = model.model.getOrDefault("r",
Collections.emptyMap()).get("r");
Assertion p = model.model.getOrDefault("p",
Collections.emptyMap()).get("p");
- if (r == null || p == null || r.tokens.length < 2 || p.tokens.length <
2) {
+ if (r == null || p == null) {
return;
}
- String matchFun = "keyMatch(" + r.tokens[1] + ", " + p.tokens[1] + ")";
- if (model.model.get("m").get("m").value.contains(matchFun)) {
+ String matcher =
STRING_LITERAL.matcher(model.model.get("m").get("m").value).replaceAll("\"\"");
+ Matcher calls = MATCHER_FUNCTION_CALL.matcher(matcher);
+ Set<String> domainTokens = new HashSet<>();
+ Set<String> matchedTokens = new HashSet<>();
+ while (calls.find()) {
+ List<String> args = getMatcherFunctionArguments(matcher,
calls.end());
+ if (ptype.equals(calls.group(1)) && args.size() == 3 &&
Arrays.asList(r.tokens).contains(args.get(2))) {
+ domainTokens.add(args.get(2));
+ } else if ("keyMatch".equals(calls.group(1)) && args.size() == 2
+ && Arrays.asList(p.tokens).contains(args.get(1))) {
+ matchedTokens.add(args.get(0));
+ }
+ }
+ // Resource matching must not change how the role function's domain is
resolved.
+ if (!Collections.disjoint(domainTokens, matchedTokens)) {
addNamedDomainMatchingFunc(ptype, "g", BuiltInFunctions::keyMatch);
}
}
+ private static List<String> getMatcherFunctionArguments(String matcher,
int start) {
+ List<String> args = new ArrayList<>();
+ int depth = 1;
+ for (int i = start; i < matcher.length(); i++) {
+ char c = matcher.charAt(i);
+ if (c == '(' || c == '[' || c == '{') {
+ depth++;
+ } else if (c == ')' || c == ']' || c == '}') {
+ if (--depth == 0) {
+ args.add(matcher.substring(start, i).trim());
+ return args;
+ }
+ } else if (c == ',' && depth == 1) {
+ args.add(matcher.substring(start, i).trim());
+ start = i + 1;
+ }
+ }
+ return Collections.emptyList();
+ }
+
/**
* add or update the DomainManager object in rmMap and associate it with a
specific domain matching function
*/
diff --git
a/src/main/java/org/casbin/jcasbin/rbac/ConditionalDomainManager.java
b/src/main/java/org/casbin/jcasbin/rbac/ConditionalDomainManager.java
index 7d09a0b..30e3b4f 100644
--- a/src/main/java/org/casbin/jcasbin/rbac/ConditionalDomainManager.java
+++ b/src/main/java/org/casbin/jcasbin/rbac/ConditionalDomainManager.java
@@ -52,16 +52,20 @@ public class ConditionalDomainManager extends
ConditionalRoleManager {
Map<String, ConditionalRoleManager> rmMap = new HashMap<>(this.rmMap);
clear();
rmMap.forEach((domain, rm) -> {
- rm.allRoles.values().forEach(user -> {
- user.roles.keySet().forEach(roleName ->
addLink(user.getName(), roleName, domain));
- });
+ ConditionalRoleManager newRm = new
ConditionalRoleManager(this.maxHierarchyLevel, this.matchingFunc,
this.domainMatchingFunc);
+ newRm.copyFrom(rm);
+ this.rmMap.put(domain, newRm);
});
- rmMap.forEach((domain, rm) -> {
- ConditionalRoleManager newRm = getRoleManager(domain, true);
- rm.allRoles.values().forEach(user -> {
- user.roles.keySet().forEach(roleName ->
newRm.getRole(user.getName()).copyLinkConditions(user, roleName));
+ // Copy complete links from the snapshot so propagated links retain
their conditions.
+ if (this.domainMatchingFunc != null) {
+ this.rmMap.forEach((domain, rm) -> {
+ rmMap.forEach((pattern, source) -> {
+ if (!domain.equals(pattern) && matchDomain(domain,
pattern)) {
+ rm.copyFrom(source);
+ }
+ });
});
- });
+ }
}
private String domainName(String... domain) {
diff --git a/src/test/java/org/casbin/jcasbin/main/ModelUnitTest.java
b/src/test/java/org/casbin/jcasbin/main/ModelUnitTest.java
index d031fb1..919a96b 100644
--- a/src/test/java/org/casbin/jcasbin/main/ModelUnitTest.java
+++ b/src/test/java/org/casbin/jcasbin/main/ModelUnitTest.java
@@ -20,11 +20,13 @@ package org.casbin.jcasbin.main;
import com.googlecode.aviator.runtime.function.FunctionUtils;
import com.googlecode.aviator.runtime.type.AviatorBoolean;
import com.googlecode.aviator.runtime.type.AviatorObject;
+import org.casbin.jcasbin.model.Model;
import org.casbin.jcasbin.persist.file_adapter.AdapterMock;
import org.casbin.jcasbin.rbac.RoleManager;
import org.casbin.jcasbin.util.BuiltInFunctions;
import org.casbin.jcasbin.util.Util;
import org.casbin.jcasbin.util.function.CustomFunction;
+import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import java.util.*;
@@ -36,6 +38,8 @@ import static
org.casbin.jcasbin.main.TestUtil.testDomainEnforce;
import static org.casbin.jcasbin.main.TestUtil.testEnforce;
import static org.casbin.jcasbin.main.TestUtil.testEnforceWithoutUsers;
import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
public class ModelUnitTest {
@Test
@@ -178,6 +182,102 @@ public class ModelUnitTest {
testDomainEnforce(e, "bob", "domain2", "data2", "write", true);
}
+ @Test
+ public void testResourceMatchingDoesNotEnableDomainMatching() {
+ Model m = new Model();
+ m.addDef("r", "r", "sub, obj, act, dom");
+ m.addDef("p", "p", "sub, obj, act, dom");
+ m.addDef("g", "g", "_, _, _");
+ m.addDef("e", "e", "some(where (p.eft == allow))");
+ m.addDef("m", "m", "g(r.sub, p.sub, r.dom) && r.dom == p.dom &&
keyMatch(r.obj, p.obj) && r.act == p.act");
+ Enforcer e = new Enforcer(m);
+ e.addPolicy("admin", "/data/*", "read", "tenantA");
+ e.addGroupingPolicy("alice", "admin", "tenant*");
+ e.addGroupingPolicy("bob", "admin", "tenantA");
+
+ assertFalse(e.enforce("alice", "/data/1", "read", "tenantA"));
+ assertTrue(e.enforce("bob", "/data/1", "read", "tenantA"));
+
+ e.addNamedDomainMatchingFunc("g", "keyMatch",
BuiltInFunctions::keyMatch);
+ assertTrue(e.enforce("alice", "/data/1", "read", "tenantA"));
+ }
+
+ @DataProvider(name = "domainRoleCalls")
+ public Object[][] domainRoleCalls() {
+ return new Object[][] {
+ {"g2(r.sub,p.sub,r.tenant)"},
+ {"g2((r.sub),'admin',r.tenant)"}
+ };
+ }
+
+ @Test(dataProvider = "domainRoleCalls")
+ public void testDomainMatchingUsesRoleDomainArgument(String roleCall) {
+ Model m = new Model();
+ m.addDef("r", "r", "sub, obj, act, tenant");
+ m.addDef("p", "p", "sub, obj, act, tenant");
+ m.addDef("g", "g2", "_, _, _");
+ m.addDef("e", "e", "some(where (p.eft == allow))");
+ m.addDef("m", "m", roleCall + " && keyMatch(r.tenant,p.tenant) &&
r.obj == p.obj && r.act == p.act");
+ Enforcer e = new Enforcer(m);
+ e.addPolicy("admin", "data", "read", "tenant*");
+ e.addNamedGroupingPolicy("g2", "alice", "admin", "tenant*");
+
+ assertTrue(e.enforce("alice", "data", "read", "tenantA"));
+ assertFalse(e.enforce("alice", "data", "read", "other"));
+ assertFalse(e.enforce("bob", "data", "read", "tenantA"));
+ }
+
+ @Test
+ public void testQuotedDomainMatcherDoesNotEnableDomainMatching() {
+ Model m = new Model();
+ m.addDef("r", "r", "sub, dom, obj, act");
+ m.addDef("p", "p", "sub, dom, obj, act");
+ m.addDef("g", "g", "_, _, _");
+ m.addDef("e", "e", "some(where (p.eft == allow))");
+ m.addDef("m", "m", "g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj
== p.obj && r.act == p.act && r.obj != 'keyMatch(r_dom, p_dom)'");
+ Enforcer e = new Enforcer(m);
+ e.addPolicy("admin", "tenantA", "data", "read");
+ e.addGroupingPolicy("alice", "admin", "tenant*");
+ e.addGroupingPolicy("bob", "admin", "tenantA");
+
+ assertFalse(e.enforce("alice", "tenantA", "data", "read"));
+ assertTrue(e.enforce("bob", "tenantA", "data", "read"));
+ }
+
+ @Test
+ public void testConditionalDomainMatchingPreservesConditions() {
+ Model m = new Model();
+ m.addDef("r", "r", "sub, dom, obj, act");
+ m.addDef("p", "p", "sub, dom, obj, act");
+ m.addDef("g", "g", "_, _, _, (_, _)");
+ m.addDef("e", "e", "some(where (p.eft == allow))");
+ m.addDef("m", "m", "g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj
== p.obj && r.act == p.act");
+ Enforcer e = new Enforcer(m);
+ e.addPolicy("admin", "tenantA", "data", "read");
+ e.addPolicy("admin", "tenantB", "data", "read");
+ e.addGroupingPolicy("alice", "admin", "tenant*", "deny", "_");
+ e.addGroupingPolicy("bob", "reader", "tenantA", "_", "_");
+ e.addNamedDomainLinkConditionFunc("g", "alice", "admin", "tenant*",
params -> "allow".equals(params[0]));
+ e.setNamedDomainLinkConditionFuncParams("g", "alice", "admin",
"tenant*", "deny", "_");
+
+ assertFalse(e.enforce("alice", "tenantA", "data", "read"));
+ e.addNamedDomainMatchingFunc("g", "keyMatch",
BuiltInFunctions::keyMatch);
+ assertFalse(e.enforce("alice", "tenantA", "data", "read"));
+ assertFalse(e.enforce("alice", "tenantB", "data", "read"));
+
+ e.setNamedDomainLinkConditionFuncParams("g", "alice", "admin",
"tenant*", "allow", "_");
+ assertTrue(e.enforce("alice", "tenantA", "data", "read"));
+ assertTrue(e.enforce("alice", "tenantB", "data", "read"));
+
+ e.addNamedDomainMatchingFunc("g", "keyMatch",
BuiltInFunctions::keyMatch);
+ assertTrue(e.enforce("alice", "tenantA", "data", "read"));
+ assertTrue(e.enforce("alice", "tenantB", "data", "read"));
+
+ e.setNamedDomainLinkConditionFuncParams("g", "alice", "admin",
"tenant*", "deny", "_");
+ assertFalse(e.enforce("alice", "tenantA", "data", "read"));
+ assertFalse(e.enforce("alice", "tenantB", "data", "read"));
+ }
+
@Test
public void testRBACModelWithDomainsAtRuntime() {
Enforcer e = new Enforcer("examples/rbac_with_domains_model.conf");
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]