From 2c363e14043d493ff082c1487f9fcbe859979256 Mon Sep 17 00:00:00 2001 From: LMay <67900553+LMay001@users.noreply.github.com> Date: Sun, 4 Feb 2024 01:33:52 +0800 Subject: [PATCH] feat: add ABAC & JSON related test cases (#376) --- examples/abac_model.conf | 2 +- examples/abac_rule_policy.csv | 5 +- pom.xml | 6 + .../org/casbin/jcasbin/main/CoreEnforcer.java | 35 ++++++ .../java/org/casbin/jcasbin/util/Util.java | 18 +++ .../casbin/jcasbin/main/AbacAPIUnitTest.java | 2 +- .../casbin/jcasbin/main/ModelUnitTest.java | 110 +++++++++++++++++- 7 files changed, 172 insertions(+), 6 deletions(-) diff --git a/examples/abac_model.conf b/examples/abac_model.conf index 50e3281e..44d06046 100644 --- a/examples/abac_model.conf +++ b/examples/abac_model.conf @@ -8,4 +8,4 @@ p = sub, obj, act e = some(where (p.eft == allow)) [matchers] -m = r.sub == r.obj.owner \ No newline at end of file +m = r.sub == r.obj.Owner diff --git a/examples/abac_rule_policy.csv b/examples/abac_rule_policy.csv index 2ae26dcb..be29dedf 100644 --- a/examples/abac_rule_policy.csv +++ b/examples/abac_rule_policy.csv @@ -1,3 +1,2 @@ -p, r.sub.not_exist_attribute_test == true, /data0, read -p, r.sub.age > 18 && r.sub.age < 25, /data1, read -p, r.sub.age < 60, /data2, write +p, r.sub.Age > 18, /data1, read +p, r.sub.Age < 60, /data2, write diff --git a/pom.xml b/pom.xml index df5a0b9d..86f6d00a 100644 --- a/pom.xml +++ b/pom.xml @@ -229,6 +229,12 @@ gson 2.9.0 + + com.fasterxml.jackson.core + jackson-databind + 2.13.1 + + diff --git a/src/main/java/org/casbin/jcasbin/main/CoreEnforcer.java b/src/main/java/org/casbin/jcasbin/main/CoreEnforcer.java index f77f13a0..ad4867d8 100644 --- a/src/main/java/org/casbin/jcasbin/main/CoreEnforcer.java +++ b/src/main/java/org/casbin/jcasbin/main/CoreEnforcer.java @@ -14,6 +14,9 @@ package org.casbin.jcasbin.main; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import com.googlecode.aviator.AviatorEvaluator; import com.googlecode.aviator.AviatorEvaluatorInstance; import com.googlecode.aviator.Expression; @@ -57,6 +60,7 @@ public class CoreEnforcer { boolean autoBuildRoleLinks; boolean autoNotifyWatcher = true; boolean autoNotifyDispatcher = true; + boolean acceptJsonRequest = false; private AviatorEvaluatorInstance aviatorEval; @@ -447,6 +451,15 @@ public void enableAutoBuildRoleLinks(boolean autoBuildRoleLinks) { this.autoBuildRoleLinks = autoBuildRoleLinks; } + /** + * EnableAcceptJsonRequest controls whether to accept json as a request parameter + * + * @param acceptJsonRequest + */ + public void enableAcceptJsonRequest(boolean acceptJsonRequest) { + this.acceptJsonRequest = acceptJsonRequest; + } + /** * buildRoleLinks manually rebuild the * role inheritance relations. @@ -516,6 +529,28 @@ private EnforceResult enforce(String matcher, Object... rvals) { expString = Util.removeComments(Util.escapeAssertion(matcher)); } + // json process + if (acceptJsonRequest) { + try { + List parsedRvals = new ArrayList<>(); + ObjectMapper objectMapper = new ObjectMapper(); + + for (Object rval : rvals) { + if (rval instanceof String && !((String) rval).isEmpty() && Util.isJsonString((String) rval)) { + String jsonString = (String) rval; + Map mapValue = objectMapper.readValue(jsonString, new TypeReference>() {}); + parsedRvals.add(mapValue); + } else { + parsedRvals.add(rval); + } + } + + rvals = parsedRvals.toArray(); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + } + expString = Util.convertInSyntax(expString); // Use md5 encryption as cacheKey to prevent expString from being too long Expression expression = aviatorEval.compile(Util.md5(expString), expString, compileCached); diff --git a/src/main/java/org/casbin/jcasbin/util/Util.java b/src/main/java/org/casbin/jcasbin/util/Util.java index 5bd98992..d27923b5 100644 --- a/src/main/java/org/casbin/jcasbin/util/Util.java +++ b/src/main/java/org/casbin/jcasbin/util/Util.java @@ -14,6 +14,8 @@ package org.casbin.jcasbin.util; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; @@ -330,4 +332,20 @@ private static MessageDigest getDigest(String algorithm) { throw new IllegalArgumentException(e); } } + + /** + * Helper method to check if a string is a valid JSON + * + * @param str + * @return boolean + */ + public static boolean isJsonString(String str) { + try { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.readTree(str); + return true; + } catch (JsonProcessingException e) { + return false; + } + } } diff --git a/src/test/java/org/casbin/jcasbin/main/AbacAPIUnitTest.java b/src/test/java/org/casbin/jcasbin/main/AbacAPIUnitTest.java index cf89ed19..d0f8a65b 100644 --- a/src/test/java/org/casbin/jcasbin/main/AbacAPIUnitTest.java +++ b/src/test/java/org/casbin/jcasbin/main/AbacAPIUnitTest.java @@ -35,7 +35,7 @@ public void testEval() { testEnforce(e, alice, "/data1", "read", true); testEnforce(e, alice, "/data1", "write", false); alice.setAge(25); - testEnforce(e, alice, "/data1", "read", false); + testEnforce(e, alice, "/data1", "read", true); testEnforce(e, alice, "/data1", "write", false); testEnforce(e, alice, "/data2", "read", false); testEnforce(e, alice, "/data2", "write", true); diff --git a/src/test/java/org/casbin/jcasbin/main/ModelUnitTest.java b/src/test/java/org/casbin/jcasbin/main/ModelUnitTest.java index e68df326..7ebfb40a 100644 --- a/src/test/java/org/casbin/jcasbin/main/ModelUnitTest.java +++ b/src/test/java/org/casbin/jcasbin/main/ModelUnitTest.java @@ -17,13 +17,18 @@ 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.junit.Test; -import java.util.List; +import java.util.*; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; 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.junit.Assert.assertEquals; public class ModelUnitTest { @Test @@ -416,6 +421,109 @@ public void testABACModel() { testEnforce(e, "bob", data2, "write", true); } + @Test + public void testABACMapRequest(){ + Enforcer e = new Enforcer("examples/abac_model.conf"); + + Map data1 = new HashMap<>(); + data1.put("Name", "data1"); + data1.put("Owner", "alice"); + + Map data2 = new HashMap<>(); + data2.put("Name", "data2"); + data2.put("Owner", "bob"); + + testEnforce(e, "alice", data1, "read", true); + testEnforce(e, "alice", data1, "write", true); + testEnforce(e, "alice", data2, "read", false); + testEnforce(e, "alice", data2, "write", false); + testEnforce(e, "bob", data1, "read", false); + testEnforce(e, "bob", data1, "write", false); + testEnforce(e, "bob", data2, "read", true); + testEnforce(e, "bob", data2, "write", true); + } + + static class StructRequest { + private List Roles; + private boolean Enabled; + private int Age; + private String Name; + + // Getters and setters + public List getRoles() { + return Roles; + } + + public void setRoles(List Roles) { + this.Roles = Roles; + } + + public boolean isEnabled() { + return Enabled; + } + + public void setEnabled(boolean Enabled) { + this.Enabled = Enabled; + } + + public int getAge() { + return Age; + } + + public void setAge(int Age) { + this.Age = Age; + } + + public String getName() { + return Name; + } + + public void setName(String Name) { + this.Name = Name; + } + } + + public static void testEnforce(Enforcer e, Object sub, Object obj, String act, boolean res) { + try { + boolean myRes = e.enforce(sub, obj, act); + assertEquals(String.format("%s, %s, %s: %b, supposed to be %b", sub, obj, act, myRes, res), res, myRes); + } catch (Exception ex) { + throw new RuntimeException(String.format("Enforce Error: %s", ex.getMessage()), ex); + } + } + + @Test + public void testABACTypes(){ + Enforcer e = new Enforcer("examples/abac_model.conf"); + String matcher = "\"moderator\" in r.sub.Roles && r.sub.Enabled == true && r.sub.Age >= 21 && r.sub.Name != \"foo\""; + e.getModel().model.get("m").get("m").value = (Util.removeComments(Util.escapeAssertion(matcher))); + + // Struct request + StructRequest structRequest = new StructRequest(); + structRequest.setRoles(Arrays.asList("user", "moderator")); + structRequest.setEnabled(true); + structRequest.setAge(30); + structRequest.setName("alice"); + testEnforce(e, structRequest, null, "", true); + + // Map request + Map mapRequest = new HashMap<>(); + mapRequest.put("Roles", Arrays.asList("user", "moderator")); + mapRequest.put("Enabled", true); + mapRequest.put("Age", 30); + mapRequest.put("Name", "alice"); + testEnforce(e, mapRequest, null, "", true); + + // JSON request + e.enableAcceptJsonRequest(true); + try { + String jsonRequest = new ObjectMapper().writeValueAsString(mapRequest); + testEnforce(e, jsonRequest, "", "", true); + } catch (JsonProcessingException jsonProcessingException) { + jsonProcessingException.printStackTrace(); + } + } + @Test public void testKeyMatchModel() { Enforcer e = new Enforcer("examples/keymatch_model.conf", "examples/keymatch_policy.csv");