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 d74d98f fix: derive domain matches from explicit links (#540)
d74d98f is described below
commit d74d98f66928751d9204eb47b39b21217b3c581f
Author: tison <[email protected]>
AuthorDate: Mon Oct 26 21:17:17 2026 +0800
fix: derive domain matches from explicit links (#540)
* fix: derive conditional domain views from explicit links
* fix: preserve explicit memberships in ordinary domain managers
---
.../jcasbin/rbac/ConditionalDomainManager.java | 93 ++++++++++------------
.../org/casbin/jcasbin/rbac/DomainManager.java | 81 ++++++++-----------
.../org/casbin/jcasbin/main/ModelUnitTest.java | 84 +++++++++++++++++++
.../casbin/jcasbin/main/RoleManagerUnitTest.java | 41 ++++++++++
4 files changed, 199 insertions(+), 100 deletions(-)
diff --git
a/src/main/java/org/casbin/jcasbin/rbac/ConditionalDomainManager.java
b/src/main/java/org/casbin/jcasbin/rbac/ConditionalDomainManager.java
index 30e3b4f..db4e3e6 100644
--- a/src/main/java/org/casbin/jcasbin/rbac/ConditionalDomainManager.java
+++ b/src/main/java/org/casbin/jcasbin/rbac/ConditionalDomainManager.java
@@ -29,7 +29,9 @@ import java.util.function.Function;
public class ConditionalDomainManager extends ConditionalRoleManager {
private static final String DEFAULT_DOMAIN = "casbin::default";
+ // Keep explicit links separate from the domain views built by matching.
private Map<String, ConditionalRoleManager> rmMap;
+ private Map<String, ConditionalRoleManager> matchedRmMap;
private SyncedLRUCache<String, Boolean> domainMatchingFuncCache;
public ConditionalDomainManager(int maxHierarchyLevel) {
@@ -40,53 +42,47 @@ public class ConditionalDomainManager extends
ConditionalRoleManager {
public void addMatchingFunc(String name, BiPredicate<String, String>
matchingFunc) {
this.matchingFunc = matchingFunc;
this.rmMap.values().forEach(rm -> rm.addMatchingFunc(name,
matchingFunc));
+ this.matchedRmMap.clear();
}
@Override
public void addDomainMatchingFunc(String name, BiPredicate<String, String>
domainMatchingFunc) {
this.domainMatchingFunc = domainMatchingFunc;
- rebuild();
- }
-
- private void rebuild() {
- Map<String, ConditionalRoleManager> rmMap = new HashMap<>(this.rmMap);
- clear();
- rmMap.forEach((domain, rm) -> {
- ConditionalRoleManager newRm = new
ConditionalRoleManager(this.maxHierarchyLevel, this.matchingFunc,
this.domainMatchingFunc);
- newRm.copyFrom(rm);
- this.rmMap.put(domain, newRm);
- });
- // 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);
- }
- });
- });
- }
+ this.rmMap.values().forEach(rm -> rm.domainMatchingFunc =
domainMatchingFunc);
+ this.domainMatchingFuncCache = new SyncedLRUCache<>(100);
+ this.matchedRmMap.clear();
}
private String domainName(String... domain) {
return domain.length == 0 ? DEFAULT_DOMAIN : domain[0];
}
- private ConditionalRoleManager getRoleManager(String domain, boolean
store) {
+ private ConditionalRoleManager getRoleManager(String domain) {
ConditionalRoleManager rm = this.rmMap.get(domain);
if (rm == null) {
rm = new ConditionalRoleManager(this.maxHierarchyLevel,
this.matchingFunc, this.domainMatchingFunc);
- if (store) {
- this.rmMap.put(domain, rm);
- }
- if (this.domainMatchingFunc != null) {
- for (Map.Entry<String, ConditionalRoleManager> entry :
this.rmMap.entrySet()) {
- String domain2 = entry.getKey();
- if (!domain.equals(domain2) && matchDomain(domain,
domain2)) {
- rm.copyFrom(entry.getValue());
- }
+ this.rmMap.put(domain, rm);
+ }
+ return rm;
+ }
+
+ private ConditionalRoleManager getMatchedRoleManager(String domain) {
+ if (this.domainMatchingFunc == null) {
+ ConditionalRoleManager rm = this.rmMap.get(domain);
+ return rm != null ? rm : new
ConditionalRoleManager(this.maxHierarchyLevel, this.matchingFunc, null);
+ }
+ ConditionalRoleManager rm = this.matchedRmMap.get(domain);
+ if (rm == null) {
+ rm = new ConditionalRoleManager(this.maxHierarchyLevel,
this.matchingFunc, this.domainMatchingFunc);
+ for (Map.Entry<String, ConditionalRoleManager> entry :
this.rmMap.entrySet()) {
+ if (domain.equals(entry.getKey()) || matchDomain(domain,
entry.getKey())) {
+ rm.copyFrom(entry.getValue());
}
}
+ // Do not retain views for arbitrary domains supplied by requests.
+ if (this.rmMap.containsKey(domain)) {
+ this.matchedRmMap.put(domain, rm);
+ }
}
return rm;
}
@@ -109,71 +105,62 @@ public class ConditionalDomainManager extends
ConditionalRoleManager {
public void clear() {
super.clear();
this.rmMap = new HashMap<>();
+ this.matchedRmMap = new HashMap<>();
this.domainMatchingFuncCache = new SyncedLRUCache<>(100);
}
@Override
public void addLink(String name1, String name2, String... domain) {
String domainName = domainName(domain);
- getRoleManager(domainName, true).addLink(name1, name2, domain);
-
- if (this.domainMatchingFunc != null) {
- this.rmMap.forEach((domain2, rm) -> {
- if (!domainName.equals(domain2) && matchDomain(domain2,
domainName)) {
- rm.addLink(name1, name2, domain);
- }
- });
- }
+ getRoleManager(domainName).addLink(name1, name2, domain);
+ this.matchedRmMap.clear();
}
@Override
public void deleteLink(String name1, String name2, String... domain) {
String domainName = domainName(domain);
- getRoleManager(domainName, true).deleteLink(name1, name2, domain);
-
- if (this.domainMatchingFunc != null) {
- this.rmMap.forEach((domain2, rm) -> {
- if (!domainName.equals(domain2) && matchDomain(domain2,
domainName)) {
- rm.deleteLink(name1, name2, domain);
- }
- });
- }
+ getRoleManager(domainName).deleteLink(name1, name2, domain);
+ this.matchedRmMap.clear();
}
@Override
public boolean hasLink(String name1, String name2, String... domain) {
- return getRoleManager(domainName(domain), false).hasLink(name1, name2,
domain);
+ return getMatchedRoleManager(domainName(domain)).hasLink(name1, name2,
domain);
}
@Override
public List<String> getRoles(String name, String... domain) {
- return getRoleManager(domainName(domain), false).getRoles(name,
domain);
+ return getMatchedRoleManager(domainName(domain)).getRoles(name,
domain);
}
@Override
public List<String> getUsers(String name, String... domain) {
- return getRoleManager(domainName(domain), false).getUsers(name,
domain);
+ return getMatchedRoleManager(domainName(domain)).getUsers(name,
domain);
}
@Override
public void addLinkConditionFunc(String userName, String roleName,
Function<String[], Boolean> fn) {
this.rmMap.values().forEach(rm -> rm.addLinkConditionFunc(userName,
roleName, fn));
+ this.matchedRmMap.clear();
}
@Override
public void addDomainLinkConditionFunc(String userName, String roleName,
String domain, Function<String[], Boolean> fn) {
- getRoleManager(domain, true);
+ getRoleManager(domain);
this.rmMap.values().forEach(rm ->
rm.addDomainLinkConditionFunc(userName, roleName, domain, fn));
+ this.matchedRmMap.clear();
}
@Override
public void setLinkConditionFuncParams(String userName, String roleName,
String... params) {
this.rmMap.values().forEach(rm ->
rm.setLinkConditionFuncParams(userName, roleName, params));
+ this.matchedRmMap.clear();
}
@Override
public void setDomainLinkConditionFuncParams(String userName, String
roleName, String domain, String... params) {
this.rmMap.values().forEach(rm ->
rm.setDomainLinkConditionFuncParams(userName, roleName, domain, params));
+ this.matchedRmMap.clear();
}
@Override
diff --git a/src/main/java/org/casbin/jcasbin/rbac/DomainManager.java
b/src/main/java/org/casbin/jcasbin/rbac/DomainManager.java
index 44150e1..131c6ff 100644
--- a/src/main/java/org/casbin/jcasbin/rbac/DomainManager.java
+++ b/src/main/java/org/casbin/jcasbin/rbac/DomainManager.java
@@ -24,7 +24,6 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
/**
@@ -32,7 +31,9 @@ import java.util.function.BiPredicate;
**/
public class DomainManager implements RoleManager {
private static final String DEFAULT_DOMAIN = "casbin::default";
+ // Keep explicit links separate from the domain views built by matching.
private Map<String, DefaultRoleManager> rmMap;
+ private Map<String, DefaultRoleManager> matchedRmMap;
private int maxHierarchyLevel;
private BiPredicate<String, String> matchingFunc;
private BiPredicate<String, String> domainMatchingFunc;
@@ -53,46 +54,45 @@ public class DomainManager implements RoleManager {
public void addMatchingFunc(String name, BiPredicate<String, String>
matchingFunc) {
this.matchingFunc = matchingFunc;
this.rmMap.values().forEach(rm -> rm.addMatchingFunc(name,
matchingFunc));
+ this.matchedRmMap.clear();
}
public void addDomainMatchingFunc(String name, BiPredicate<String, String>
domainMatchingFunc) {
this.domainMatchingFunc = domainMatchingFunc;
- rebuild();
- }
-
- /**
- * rebuild clears the map of RoleManagers
- */
- private void rebuild() {
- Map<String, DefaultRoleManager> 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));
- });
- });
+ this.domainMatchingFuncCache = new SyncedLRUCache<>(100);
+ this.matchedRmMap.clear();
}
private String domainName(String... domain) {
return domain.length == 0 ? DEFAULT_DOMAIN : domain[0];
}
- private DefaultRoleManager getRoleManager(String domain, boolean store) {
+ private DefaultRoleManager getRoleManager(String domain) {
DefaultRoleManager rm = this.rmMap.get(domain);
if (rm == null) {
rm = new DefaultRoleManager(this.maxHierarchyLevel,
this.matchingFunc, null);
- if (store) {
- this.rmMap.put(domain, rm);
- }
- if (this.domainMatchingFunc != null) {
- for (Map.Entry<String, DefaultRoleManager> entry :
this.rmMap.entrySet()) {
- String domain2 = entry.getKey();
- DefaultRoleManager rm2 = entry.getValue();
- if (!domain.equals(domain2) && match(domain, domain2)) {
- rm.copyFrom(rm2);
- }
+ this.rmMap.put(domain, rm);
+ }
+ return rm;
+ }
+
+ private DefaultRoleManager getMatchedRoleManager(String domain) {
+ if (this.domainMatchingFunc == null) {
+ DefaultRoleManager rm = this.rmMap.get(domain);
+ return rm != null ? rm : new
DefaultRoleManager(this.maxHierarchyLevel, this.matchingFunc, null);
+ }
+ DefaultRoleManager rm = this.matchedRmMap.get(domain);
+ if (rm == null) {
+ rm = new DefaultRoleManager(this.maxHierarchyLevel,
this.matchingFunc, null);
+ for (Map.Entry<String, DefaultRoleManager> entry :
this.rmMap.entrySet()) {
+ if (domain.equals(entry.getKey()) || match(domain,
entry.getKey())) {
+ rm.copyFrom(entry.getValue());
}
}
+ // Do not retain views for arbitrary domains supplied by requests.
+ if (this.rmMap.containsKey(domain)) {
+ this.matchedRmMap.put(domain, rm);
+ }
}
return rm;
}
@@ -114,52 +114,39 @@ public class DomainManager implements RoleManager {
@Override
public void clear() {
this.rmMap = new HashMap<>();
+ this.matchedRmMap = new HashMap<>();
this.domainMatchingFuncCache = new SyncedLRUCache<>(100);
}
@Override
public void addLink(String name1, String name2, String... domain) {
- DefaultRoleManager roleManager = getRoleManager(domainName(domain),
true);
+ DefaultRoleManager roleManager = getRoleManager(domainName(domain));
roleManager.addLink(name1, name2, domain);
-
- if (this.domainMatchingFunc != null) {
- this.rmMap.forEach((domain2, rm) -> {
- if (!domainName(domain).equals(domain2) && match(domain2,
domainName(domain))) {
- rm.addLink(name1, name2, domain);
- }
- });
- }
+ this.matchedRmMap.clear();
}
@Override
public void deleteLink(String name1, String name2, String... domain) {
- DefaultRoleManager roleManager = getRoleManager(domainName(domain),
true);
+ DefaultRoleManager roleManager = getRoleManager(domainName(domain));
roleManager.deleteLink(name1, name2, domain);
-
- if (this.domainMatchingFunc != null) {
- this.rmMap.forEach((domain2, rm) -> {
- if (!domainName(domain).equals(domain2) && match(domain2,
domainName(domain))) {
- rm.deleteLink(name1, name2, domain);
- }
- });
- }
+ this.matchedRmMap.clear();
}
@Override
public boolean hasLink(String name1, String name2, String... domain) {
- DefaultRoleManager roleManager = getRoleManager(domainName(domain),
false);
+ DefaultRoleManager roleManager =
getMatchedRoleManager(domainName(domain));
return roleManager.hasLink(name1, name2, domain);
}
@Override
public List<String> getRoles(String name, String... domain) {
- DefaultRoleManager roleManager = getRoleManager(domainName(domain),
false);
+ DefaultRoleManager roleManager =
getMatchedRoleManager(domainName(domain));
return roleManager.getRoles(name, domain);
}
@Override
public List<String> getUsers(String name, String... domain) {
- DefaultRoleManager roleManager = getRoleManager(domainName(domain),
false);
+ DefaultRoleManager roleManager =
getMatchedRoleManager(domainName(domain));
return roleManager.getUsers(name, domain);
}
diff --git a/src/test/java/org/casbin/jcasbin/main/ModelUnitTest.java
b/src/test/java/org/casbin/jcasbin/main/ModelUnitTest.java
index 5f75594..387a3b1 100644
--- a/src/test/java/org/casbin/jcasbin/main/ModelUnitTest.java
+++ b/src/test/java/org/casbin/jcasbin/main/ModelUnitTest.java
@@ -325,6 +325,90 @@ public class ModelUnitTest {
assertFalse(e.enforce("bob1", "tenantA", "data", "read"));
}
+ @DataProvider(name = "domainReconfiguration")
+ public Object[][] domainReconfiguration() {
+ return new Object[][] {{false, false}, {false, true}, {true, false},
{true, true}};
+ }
+
+ @Test(dataProvider = "domainReconfiguration")
+ public void testDomainMatchingCanBeNarrowed(boolean matchBeforeLinks,
boolean existingDomain) {
+ 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");
+ if (matchBeforeLinks) {
+ e.addNamedDomainMatchingFunc("g", "keyMatch",
BuiltInFunctions::keyMatch);
+ }
+ if (existingDomain) {
+ e.addGroupingPolicy("bob", "admin", "tenantA");
+ }
+ e.addGroupingPolicy("alice", "admin", "tenant*");
+ if (!matchBeforeLinks) {
+ e.addNamedDomainMatchingFunc("g", "keyMatch",
BuiltInFunctions::keyMatch);
+ }
+ assertTrue(e.enforce("alice", "tenantA", "data", "read"));
+
+ e.addNamedDomainMatchingFunc("g", "exact", String::equals);
+ assertFalse(e.enforce("alice", "tenantA", "data", "read"));
+ assertEquals(e.enforce("bob", "tenantA", "data", "read"),
existingDomain);
+ e.addNamedDomainMatchingFunc("g", "keyMatch",
BuiltInFunctions::keyMatch);
+ assertTrue(e.enforce("alice", "tenantA", "data", "read"));
+ assertTrue(e.removeGroupingPolicy("alice", "admin", "tenant*"));
+ assertFalse(e.enforce("alice", "tenantA", "data", "read"));
+ }
+
+ @Test(dataProvider = "domainReconfiguration")
+ public void testConditionalDomainMatchingCanBeNarrowed(boolean
matchBeforeLinks, boolean existingDomain) {
+ 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.addPolicy("admin", "tenant*", "data", "read");
+ if (matchBeforeLinks) {
+ e.addNamedDomainMatchingFunc("g", "keyMatch",
BuiltInFunctions::keyMatch);
+ }
+ if (existingDomain) {
+ e.addGroupingPolicy("bob", "admin", "tenantA", "_", "_");
+ }
+ e.addGroupingPolicy("alice", "admin", "tenant*", "deny", "_");
+ e.addNamedDomainLinkConditionFunc("g", "alice", "admin", "tenant*",
params -> "allow".equals(params[0]));
+ e.setNamedDomainLinkConditionFuncParams("g", "alice", "admin",
"tenant*", "deny", "_");
+ if (!matchBeforeLinks) {
+ e.addNamedDomainMatchingFunc("g", "keyMatch",
BuiltInFunctions::keyMatch);
+ }
+ assertFalse(e.enforce("alice", "tenantA", "data", "read"));
+ assertFalse(e.enforce("alice", "tenantB", "data", "read"));
+
+ e.addNamedDomainMatchingFunc("g", "exact", String::equals);
+ 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", "tenant*", "data", "read"));
+ assertFalse(e.enforce("alice", "tenantA", "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"));
+ e.setNamedDomainLinkConditionFuncParams("g", "alice", "admin",
"tenant*", "allow", "_");
+ assertTrue(e.enforce("alice", "tenantA", "data", "read"));
+ assertTrue(e.removeGroupingPolicy("alice", "admin", "tenant*", "deny",
"_"));
+ assertFalse(e.enforce("alice", "tenantA", "data", "read"));
+ assertFalse(e.enforce("alice", "tenantB", "data", "read"));
+ assertEquals(e.enforce("bob", "tenantA", "data", "read"),
existingDomain);
+ }
+
@Test
public void testRBACModelWithDomainsAtRuntime() {
Enforcer e = new Enforcer("examples/rbac_with_domains_model.conf");
diff --git a/src/test/java/org/casbin/jcasbin/main/RoleManagerUnitTest.java
b/src/test/java/org/casbin/jcasbin/main/RoleManagerUnitTest.java
index ab97293..05e39da 100644
--- a/src/test/java/org/casbin/jcasbin/main/RoleManagerUnitTest.java
+++ b/src/test/java/org/casbin/jcasbin/main/RoleManagerUnitTest.java
@@ -19,6 +19,7 @@ package org.casbin.jcasbin.main;
import org.casbin.jcasbin.rbac.DefaultRoleManager;
import org.casbin.jcasbin.rbac.ConditionalRoleManager;
+import org.casbin.jcasbin.rbac.ConditionalDomainManager;
import org.casbin.jcasbin.rbac.DomainManager;
import org.casbin.jcasbin.rbac.RoleManager;
import org.casbin.jcasbin.util.BuiltInFunctions;
@@ -27,6 +28,8 @@ import org.testng.annotations.Test;
import java.util.Arrays;
import java.util.Collections;
+import static org.testng.Assert.assertEquals;
+
/**
* @author Yixiang Zhao (@seriouszyx)
**/
@@ -47,6 +50,44 @@ public class RoleManagerUnitTest {
TestUtil.testHasRole(rm, "alice", "admin", false);
}
+ @Test
+ public void testDeletingPatternLinkPreservesExplicitDomainLink() {
+ DomainManager standard = new DomainManager(10);
+ standard.addDomainMatchingFunc("keyMatch", BuiltInFunctions::keyMatch);
+ ConditionalDomainManager conditional = new
ConditionalDomainManager(10);
+ conditional.addDomainMatchingFunc("keyMatch",
BuiltInFunctions::keyMatch);
+ for (RoleManager rm : Arrays.asList(standard, conditional)) {
+ rm.addLink("alice", "admin", "tenant*");
+ rm.addLink("alice", "admin", "tenantA");
+ TestUtil.testHasRole(rm, "alice", "admin", "tenantA", true);
+ TestUtil.testHasRole(rm, "alice", "admin", "tenantB", true);
+
+ rm.deleteLink("alice", "admin", "tenant*");
+ TestUtil.testHasRole(rm, "alice", "admin", "tenantA", true);
+ TestUtil.testHasRole(rm, "alice", "admin", "tenantB", false);
+ TestUtil.testGetRoles(rm, "alice", "tenantA",
Collections.singletonList("admin"));
+ assertEquals(rm.getUsers("admin", "tenantA"),
Collections.singletonList("alice"));
+
+ rm.clear();
+ TestUtil.testHasRole(rm, "alice", "admin", "tenantA", false);
+ }
+ }
+
+ @Test
+ public void testDomainConditionsRegisteredBeforePatternLinks() {
+ ConditionalDomainManager rm = new ConditionalDomainManager(10);
+ rm.addDomainMatchingFunc("keyMatch", BuiltInFunctions::keyMatch);
+ rm.addDomainLinkConditionFunc("alice", "admin", "tenantA", params ->
"allow".equals(params[0]));
+ rm.setDomainLinkConditionFuncParams("alice", "admin", "tenantA",
"deny");
+ rm.addLink("alice", "admin", "tenant*");
+ TestUtil.testHasRole(rm, "alice", "admin", "tenantA", false);
+
+ rm.setDomainLinkConditionFuncParams("alice", "admin", "tenantA",
"allow");
+ TestUtil.testHasRole(rm, "alice", "admin", "tenantA", true);
+ rm.addDomainLinkConditionFunc("alice", "admin", "tenantA", params ->
false);
+ TestUtil.testHasRole(rm, "alice", "admin", "tenantA", false);
+ }
+
@Test
public void testRole() {
RoleManager rm = new DefaultRoleManager(3);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]