diff --git a/src/main/java/org/casbin/jcasbin/main/ManagementEnforcer.java b/src/main/java/org/casbin/jcasbin/main/ManagementEnforcer.java index 1a21dded..c6efc465 100644 --- a/src/main/java/org/casbin/jcasbin/main/ManagementEnforcer.java +++ b/src/main/java/org/casbin/jcasbin/main/ManagementEnforcer.java @@ -125,6 +125,19 @@ public List getAllNamedRoles(String ptype) { return model.getValuesForFieldInPolicy("g", ptype, 1); } + /** + * getAllUsers gets the list of users that show up in the current policy. + * Users are subjects that are not roles (i.e., subjects that do not appear + * as the second element in any grouping policy). + * + * @return all users in policy, excluding roles. + */ + public List getAllUsers() { + List subjects = getAllSubjects(); + List roles = getAllRoles(); + return Util.setSubtract(subjects, roles); + } + /** * getPolicy gets all the authorization rules in the policy. * diff --git a/src/main/java/org/casbin/jcasbin/main/SyncedEnforcer.java b/src/main/java/org/casbin/jcasbin/main/SyncedEnforcer.java index c59871de..9e0f9c25 100644 --- a/src/main/java/org/casbin/jcasbin/main/SyncedEnforcer.java +++ b/src/main/java/org/casbin/jcasbin/main/SyncedEnforcer.java @@ -405,6 +405,17 @@ public List getAllNamedRoles(String ptype) { return runSynchronized(() -> super.getAllNamedRoles(ptype), getReadWriteLock().readLock()); } + /** + * getAllUsers gets the list of users that show up in the current policy. + * Users are subjects that are not roles. + * + * @return all users in policy rules, excluding roles. + */ + @Override + public List getAllUsers() { + return runSynchronized(super::getAllUsers, getReadWriteLock().readLock()); + } + /** * getPolicy gets all the authorization rules in the policy. * diff --git a/src/main/java/org/casbin/jcasbin/model/Model.java b/src/main/java/org/casbin/jcasbin/model/Model.java index 69187db7..6825d873 100644 --- a/src/main/java/org/casbin/jcasbin/model/Model.java +++ b/src/main/java/org/casbin/jcasbin/model/Model.java @@ -470,4 +470,58 @@ private void writeString(StringBuilder s, String sec, Map tokenP s.append(String.format("%s = %s\n", sec, value)); } } + + /** + * getValuesForFieldInPolicyAllTypes gets all values for a field for all rules + * across all policy types in a section. Duplicated values are removed. + * + * @param sec the section, "p" or "g". + * @param fieldIndex the policy rule's index. + * @return all field values across all ptypes. + */ + public List getValuesForFieldInPolicyAllTypes(String sec, int fieldIndex) { + List values = new ArrayList<>(); + + Map section = model.get(sec); + if (section == null) { + return values; + } + + for (Assertion assertion : section.values()) { + for (List rule : assertion.policy) { + if (fieldIndex < rule.size()) { + values.add(rule.get(fieldIndex)); + } + } + } + + return Util.arrayRemoveDuplicates(values); + } + + /** + * getFieldIndex returns the index of a field in a policy type. + * For example, given ptype="p" and field="sub", returns the index + * where "p_sub" appears in the tokens array. + * + * @param ptype the policy type, e.g., "p", "p2" + * @param field the field name, e.g., "sub", "obj", "act" + * @return the index of the field in the policy rule, or -1 if not found + */ + public int getFieldIndex(String ptype, String field) { + String pattern = ptype + "_" + field; + Map pSection = model.get("p"); + if (pSection == null) { + return -1; + } + Assertion ast = pSection.get(ptype); + if (ast == null || ast.tokens == null) { + return -1; + } + for (int i = 0; i < ast.tokens.length; i++) { + if (pattern.equals(ast.tokens[i])) { + return i; + } + } + return -1; + } } diff --git a/src/main/java/org/casbin/jcasbin/util/Util.java b/src/main/java/org/casbin/jcasbin/util/Util.java index 4912993e..6df49c9c 100644 --- a/src/main/java/org/casbin/jcasbin/util/Util.java +++ b/src/main/java/org/casbin/jcasbin/util/Util.java @@ -388,4 +388,29 @@ public static boolean isJsonString(String str) { return false; } } + + /** + * setSubtract returns elements in list A that are not in list B. + * Preserves order from list A. + * + * @param a the first list (base) + * @param b the second list (subtrahend) + * @return a - b (elements in A but not in B) + */ + public static List setSubtract(List a, List b) { + if (a == null) { + a = new ArrayList<>(); + } + if (b == null) { + b = new ArrayList<>(); + } + Set bSet = new HashSet<>(b); + List result = new ArrayList<>(); + for (String item : a) { + if (!bSet.contains(item)) { + result.add(item); + } + } + return result; + } } diff --git a/src/test/java/org/casbin/jcasbin/main/ManagementAPIUnitTest.java b/src/test/java/org/casbin/jcasbin/main/ManagementAPIUnitTest.java index 54c0aadb..f3df5726 100644 --- a/src/test/java/org/casbin/jcasbin/main/ManagementAPIUnitTest.java +++ b/src/test/java/org/casbin/jcasbin/main/ManagementAPIUnitTest.java @@ -16,6 +16,7 @@ import com.googlecode.aviator.AviatorEvaluator; import com.googlecode.aviator.AviatorEvaluatorInstance; +import org.casbin.jcasbin.util.Util; import org.testng.Assert; import org.testng.annotations.Test; @@ -298,4 +299,74 @@ public void should_true_when_setAviatorEvaluator_given_customInstance() { assertEquals(enforcer.getAviatorEval(), instance); } + @Test + public void testGetUsersAPI() { + // 1. Basic RBAC: alice and bob are users, data2_admin is a role + Enforcer e = new Enforcer("examples/rbac_model.conf", "examples/rbac_policy.csv"); + testGetAllSubjectsUtil(e, asList("alice", "bob", "data2_admin")); + testGetAllRolesUtil(e, asList("data2_admin")); + testGetAllUsersUtil(e, asList("alice", "bob")); + + // 2. Add user "admin" that appears in both policy (as subject) and grouping (as role target) + // getAllUsers computes: subjects (from p) - roles (from g, column 1) + // admin is added as a policy subject and also assigned to role "root" + e.addPolicy("admin", "data1", "read"); + e.addGroupingPolicy("admin", "root"); + testGetAllSubjectsUtil(e, asList("alice", "bob", "data2_admin", "admin")); + testGetAllRolesUtil(e, asList("data2_admin", "root")); + testGetAllUsersUtil(e, asList("alice", "bob", "admin")); // admin is user since it's in p, not in g column 1 + e.removePolicy("admin", "data1", "read"); + e.removeGroupingPolicy("admin", "root"); + + // 3. Add regular user "eve" who is only in policy (not a role in any grouping) + e.addPolicy("eve", "data3", "read"); + testGetAllSubjectsUtil(e, asList("alice", "bob", "data2_admin", "eve")); + testGetAllRolesUtil(e, asList("data2_admin")); + testGetAllUsersUtil(e, asList("alice", "bob", "eve")); // eve is user since she's not in g column 1 + e.removePolicy("eve", "data3", "read"); + + // 4. Clear all policies - verify empty results + e.clearPolicy(); + testGetAllSubjectsUtil(e, asList()); + testGetAllRolesUtil(e, asList()); + testGetAllUsersUtil(e, asList()); + + // 5. Add new user and role relationship: user1 is a member of role "member" + // getAllSubjects: ["user1"] - from p column 0 + // getAllRoles: ["member"] - from g column 1 (the second element in grouping) + // getAllUsers: ["user1"] = subjects - roles + e.addPolicy("user1", "data1", "read"); + e.addGroupingPolicy("user1", "member"); + testGetAllSubjectsUtil(e, asList("user1")); + testGetAllRolesUtil(e, asList("member")); + testGetAllUsersUtil(e, asList("user1")); + } + + private void testGetAllSubjectsUtil(Enforcer enforcer, List res) { + List myRes = enforcer.getAllSubjects(); + Util.logPrint("All subjects: " + myRes); + + if (!Util.setEquals(res, myRes)) { + Assert.fail("All subjects: " + myRes + ", supposed to be " + res); + } + } + + private void testGetAllRolesUtil(Enforcer enforcer, List res) { + List myRes = enforcer.getAllRoles(); + Util.logPrint("All roles: " + myRes); + + if (!Util.setEquals(res, myRes)) { + Assert.fail("All roles: " + myRes + ", supposed to be " + res); + } + } + + private void testGetAllUsersUtil(Enforcer enforcer, List res) { + List myRes = enforcer.getAllUsers(); + Util.logPrint("All users: " + myRes); + + if (!Util.setEquals(res, myRes)) { + Assert.fail("All users: " + myRes + ", supposed to be " + res); + } + } + }