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.git
The following commit(s) were added to refs/heads/master by this push:
new cfa80fc feat: add RBAC with ABAC condition example
cfa80fc is described below
commit cfa80fc4a04fcfdf528dd739d37d46323340d149
Author: Yang Luo <[email protected]>
AuthorDate: Tue Aug 4 16:17:28 2026 +0800
feat: add RBAC with ABAC condition example
---
examples/rbac_with_abac_rule_model.conf | 14 ++++
examples/rbac_with_abac_rule_policy.csv | 20 +++++
rbac_with_abac_rule_test.go | 129 ++++++++++++++++++++++++++++++++
3 files changed, 163 insertions(+)
diff --git a/examples/rbac_with_abac_rule_model.conf
b/examples/rbac_with_abac_rule_model.conf
new file mode 100644
index 0000000..cd64692
--- /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, cond, eft
+
+[role_definition]
+g = _, _
+
+[policy_effect]
+e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
+
+[matchers]
+m = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act) &&
eval(p.cond)
diff --git a/examples/rbac_with_abac_rule_policy.csv
b/examples/rbac_with_abac_rule_policy.csv
new file mode 100644
index 0000000..2b1acf6
--- /dev/null
+++ b/examples/rbac_with_abac_rule_policy.csv
@@ -0,0 +1,20 @@
+# RBAC roles combined with a per-policy ABAC condition.
+#
+# p, role, object, action, cond, effect
+#
+# "cond" is a matcher expression evaluated against the ctx argument of the
+# request. Write "true" for a policy that carries no condition. A cond may only
+# read attributes that every request reaching it actually supplies: reading an
+# attribute the ctx does not have makes Enforce return an error rather than a
+# plain "denied", so pass a struct (or a map with every attribute filled in).
+
+p, employee, data1, read, r.ctx.Hour >= 9 && r.ctx.Hour < 18, allow
+p, employee, data2, write, r.ctx.Secure == true, allow
+p, admin, data3, read, true, allow
+
+# Deny overrides every allow above: a high-risk session gets nothing.
+p, employee, *, *, r.ctx.Risk == 'high', deny
+
+g, admin, employee
+g, alice, admin
+g, bob, employee
diff --git a/rbac_with_abac_rule_test.go b/rbac_with_abac_rule_test.go
new file mode 100644
index 0000000..7a4e1c1
--- /dev/null
+++ b/rbac_with_abac_rule_test.go
@@ -0,0 +1,129 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package casbin
+
+import (
+ "testing"
+)
+
+// testCtx holds the request-time attributes that the cond column of
+// examples/rbac_with_abac_rule_policy.csv reads. A struct is the safe shape
for
+// a context: every field always exists, so a policy can never reference an
+// attribute the request forgot to supply. See TestRBACWithABACRuleMissingAttr
+// for what happens when it does.
+type testCtx struct {
+ Hour int
+ Secure bool
+ Risk string
+}
+
+// Helper function for RBAC-with-ABAC-condition enforcement testing.
+func testEnforceRBACWithABACRule(t *testing.T, e *Enforcer, sub string, obj
string, act string, ctx interface{}, res bool) {
+ t.Helper()
+ myRes, err := e.Enforce(sub, obj, act, ctx)
+ if err != nil {
+ t.Errorf("Enforce Error: %s", err)
+ return
+ }
+ if myRes != res {
+ t.Errorf("%s, %s, %s, %v: %t, supposed to be %t", sub, obj,
act, ctx, myRes, res)
+ }
+}
+
+// TestRBACWithABACRule covers a model where roles decide *what* a subject may
+// touch and a per-policy condition decides *under which circumstances*:
+//
+// m = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)
&& eval(p.cond)
+//
+// The deny-override effect lets a single policy line veto every allow above
it.
+func TestRBACWithABACRule(t *testing.T) {
+ e, err := NewEnforcer("examples/rbac_with_abac_rule_model.conf",
"examples/rbac_with_abac_rule_policy.csv")
+ if err != nil {
+ t.Fatalf("NewEnforcer failed: %v", err)
+ }
+
+ // alice is an admin, and admin inherits employee; bob is a plain
employee.
+ office := testCtx{Hour: 10, Secure: true, Risk: "low"}
+ afterHours := testCtx{Hour: 20, Secure: true, Risk: "low"}
+ insecure := testCtx{Hour: 10, Secure: false, Risk: "low"}
+ risky := testCtx{Hour: 10, Secure: true, Risk: "high"}
+
+ // data1/read is allowed to employees during business hours only.
+ testEnforceRBACWithABACRule(t, e, "alice", "data1", "read", office,
true)
+ testEnforceRBACWithABACRule(t, e, "bob", "data1", "read", office, true)
+ testEnforceRBACWithABACRule(t, e, "alice", "data1", "read", afterHours,
false)
+ testEnforceRBACWithABACRule(t, e, "bob", "data1", "read", afterHours,
false)
+
+ // No policy grants data1/write, so the condition never gets a say.
+ testEnforceRBACWithABACRule(t, e, "alice", "data1", "write", office,
false)
+
+ // data2/write requires a secure connection; data2/read is not granted.
+ testEnforceRBACWithABACRule(t, e, "bob", "data2", "write", office, true)
+ testEnforceRBACWithABACRule(t, e, "bob", "data2", "write", insecure,
false)
+ testEnforceRBACWithABACRule(t, e, "bob", "data2", "read", office, false)
+
+ // data3/read is unconditional ("true") but restricted to the admin
role.
+ testEnforceRBACWithABACRule(t, e, "alice", "data3", "read", office,
true)
+ testEnforceRBACWithABACRule(t, e, "alice", "data3", "read", afterHours,
true)
+ testEnforceRBACWithABACRule(t, e, "bob", "data3", "read", office, false)
+
+ // The wildcard deny line outranks every allow, including unconditional
ones.
+ testEnforceRBACWithABACRule(t, e, "alice", "data1", "read", risky,
false)
+ testEnforceRBACWithABACRule(t, e, "alice", "data3", "read", risky,
false)
+ testEnforceRBACWithABACRule(t, e, "bob", "data2", "write", risky, false)
+
+ // carol holds no role, and data4 is covered by no policy.
+ testEnforceRBACWithABACRule(t, e, "carol", "data1", "read", office,
false)
+ testEnforceRBACWithABACRule(t, e, "alice", "data4", "read", office,
false)
+}
+
+// TestRBACWithABACRuleMapCtx shows that a map works as the context too, as
long
+// as it carries every attribute the matching policies read.
+func TestRBACWithABACRuleMapCtx(t *testing.T) {
+ e, err := NewEnforcer("examples/rbac_with_abac_rule_model.conf",
"examples/rbac_with_abac_rule_policy.csv")
+ if err != nil {
+ t.Fatalf("NewEnforcer failed: %v", err)
+ }
+
+ office := map[string]interface{}{"Hour": 10, "Secure": true, "Risk":
"low"}
+ risky := map[string]interface{}{"Hour": 10, "Secure": true, "Risk":
"high"}
+
+ testEnforceRBACWithABACRule(t, e, "alice", "data1", "read", office,
true)
+ testEnforceRBACWithABACRule(t, e, "alice", "data1", "read", risky,
false)
+}
+
+// TestRBACWithABACRuleMissingAttr pins down the one sharp edge of putting
+// expressions in policies: a cond that reads an attribute the request context
+// does not carry is an evaluation error, not a denial. Callers must not treat
+// the returned false as "denied" without checking err.
+func TestRBACWithABACRuleMissingAttr(t *testing.T) {
+ e, err := NewEnforcer("examples/rbac_with_abac_rule_model.conf",
"examples/rbac_with_abac_rule_policy.csv")
+ if err != nil {
+ t.Fatalf("NewEnforcer failed: %v", err)
+ }
+
+ // The data1/read policy reads r.ctx.Hour, which this map omits.
+ if _, err := e.Enforce("bob", "data1", "read",
map[string]interface{}{"Secure": true, "Risk": "low"}); err == nil {
+ t.Error("Enforce: expected an error when the context omits an
attribute used by a policy condition, got nil")
+ }
+
+ // Same for a nil context: there is nothing to read attributes from.
+ if _, err := e.Enforce("bob", "data1", "read", nil); err == nil {
+ t.Error("Enforce: expected an error for a nil context, got nil")
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]