diff --git a/examples/rbac_with_abac_rule_model.conf b/examples/rbac_with_abac_rule_model.conf new file mode 100644 index 00000000..8de900b4 --- /dev/null +++ b/examples/rbac_with_abac_rule_model.conf @@ -0,0 +1,14 @@ +[request_definition] +r = sub, obj, act, ctx + +[policy_definition] +p = sub, obj, act, ctx_rule, eft + +[role_definition] +g = _, _ + +[policy_effect] +e = some(where (p.eft == allow)) && !some(where (p.eft == deny)) + +[matchers] +m = g(r.sub, p.sub) && (r.obj == p.obj) && (r.act == p.act || p.act == "*") && (p.ctx_rule == "noRule" || eval(p.ctx_rule)) \ No newline at end of file diff --git a/examples/rbac_with_abac_rule_policy.csv b/examples/rbac_with_abac_rule_policy.csv new file mode 100644 index 00000000..bbf3693e --- /dev/null +++ b/examples/rbac_with_abac_rule_policy.csv @@ -0,0 +1,14 @@ +p, admin, data1, read, noRule, allow +p, admin, data1, read, r.ctx.age < 18 || r.ctx.type == 'minor', deny + +p, admin, data2, write, noRule, allow +p, admin, data2, write, r.ctx.network == 'http', deny + +p, admin, data3, *, noRule, allow + +p, user, data4, read, noRule, allow +p, user, data4, read, r.ctx.RiskStatus == 'high', deny + +g, alice, admin +g, alice, user +g, bob, admin diff --git a/src/test/java/org/casbin/jcasbin/main/AbacAPIUnitTest.java b/src/test/java/org/casbin/jcasbin/main/AbacAPIUnitTest.java index 4833936a..1eb05fb5 100644 --- a/src/test/java/org/casbin/jcasbin/main/AbacAPIUnitTest.java +++ b/src/test/java/org/casbin/jcasbin/main/AbacAPIUnitTest.java @@ -21,6 +21,7 @@ import static org.casbin.jcasbin.main.TestUtil.testDomainEnforce; import static org.casbin.jcasbin.main.TestUtil.testEnforce; +import static org.casbin.jcasbin.main.TestUtil.testRBACWithABACRuleEnforce; public class AbacAPIUnitTest { @Test @@ -79,6 +80,63 @@ public void testABACMapRequest() { testEnforce(e, "bob", data2, "write", true); } + @Test + public void testRBACWithABACRule() { + // rbac_with_abac_rule_model combines RBAC (g) with ABAC context rules (p.ctx_rule). + // The matcher evaluates a context rule as a per-request allow/deny filter. + // + // Ported from casbin/rbac_with_abac_rule_test.go (Go). In jcasbin (Aviator 5.9.0) + // `null < 18` evaluates to true, so an empty HashMap would incorrectly trip the + // data1/read deny rule. The Go test sidesteps the same problem in govaluate + // (which throws on missing keys) by always supplying a neutral context; we do + // the same here so the two test suites stay assertion-compatible. + Enforcer e = new Enforcer( + "examples/rbac_with_abac_rule_model.conf", + "examples/rbac_with_abac_rule_policy.csv" + ); + + Map neutralCtx = makeRBACABACCtx(100, "adult", "https", "low"); + Map minorCtx = makeRBACABACCtx(18, "minor", "https", "low"); + Map httpCtx = makeRBACABACCtx(100, "adult", "http", "low"); + Map highRiskCtx = makeRBACABACCtx(100, "adult", "https", "high"); + + // alice has roles {admin, user}; bob has role {admin}. + + // admin/data1/read: allow under noRule, deny when context matches r.ctx.age < 18 || r.ctx.type == "minor". + testRBACWithABACRuleEnforce(e, "alice", "data1", "read", neutralCtx, true); + testRBACWithABACRuleEnforce(e, "alice", "data1", "read", minorCtx, false); + + // admin/data2: no policy for "read" so it is denied; "write" is allowed under noRule + // and denied when r.ctx.network == "http". + testRBACWithABACRuleEnforce(e, "alice", "data2", "read", neutralCtx, false); + testRBACWithABACRuleEnforce(e, "alice", "data2", "write", neutralCtx, true); + testRBACWithABACRuleEnforce(e, "alice", "data2", "write", httpCtx, false); + + // admin/data3/* : wildcard action matches any act, allowed under noRule. + testRBACWithABACRuleEnforce(e, "alice", "data3", "read", neutralCtx, true); + testRBACWithABACRuleEnforce(e, "alice", "data3", "write", neutralCtx, true); + + // user/data4/read: allowed under noRule, denied when r.ctx.RiskStatus == "high". + testRBACWithABACRuleEnforce(e, "alice", "data4", "read", neutralCtx, true); + testRBACWithABACRuleEnforce(e, "alice", "data4", "read", highRiskCtx, false); + + // bob is admin only, so he can use admin policies but not user policies. + testRBACWithABACRuleEnforce(e, "bob", "data1", "read", neutralCtx, true); + testRBACWithABACRuleEnforce(e, "bob", "data4", "read", neutralCtx, false); + + // Unknown resource has no matching policy -> denied. + testRBACWithABACRuleEnforce(e, "alice", "data5", "read", neutralCtx, false); + } + + private static Map makeRBACABACCtx(int age, String typ, String network, String risk) { + Map ctx = new HashMap<>(); + ctx.put("age", age); + ctx.put("type", typ); + ctx.put("network", network); + ctx.put("RiskStatus", risk); + return ctx; + } + public static class TestEvalRule { private String name; private int age; diff --git a/src/test/java/org/casbin/jcasbin/main/TestUtil.java b/src/test/java/org/casbin/jcasbin/main/TestUtil.java index e86d1bea..ee54e8e4 100644 --- a/src/test/java/org/casbin/jcasbin/main/TestUtil.java +++ b/src/test/java/org/casbin/jcasbin/main/TestUtil.java @@ -59,6 +59,10 @@ static void testEnforceWithContext(Enforcer e, EnforceContext enforceContext, Ob assertEquals(res, e.enforce(enforceContext, sub, obj, act)); } + static void testRBACWithABACRuleEnforce(Enforcer e, Object sub, Object obj, String act, Map ctx, boolean res) { + assertEquals(res, e.enforce(sub, obj, act, ctx)); + } + static void testDomainEnforce(Enforcer e, Object sub, Object dom, Object obj, Object act, boolean res) { assertEquals(res, e.enforce(sub, dom, obj, act)); }