Model Syntax
Every model CONF file must define these four sections: [request_definition], [policy_definition], [policy_effect], and [matchers].
- For RBAC, add a
[role_definition]section. - For RBAC with constraints (e.g. separation of duties), you can add
[constraint_definition]. - Lines starting with
#are comments; the rest of the line is ignored.
Request Definition
The [request_definition] section defines the parameters passed to e.Enforce(...).
[request_definition]
r = sub, obj, act
Here sub, obj, and act are the standard triple: subject, object, and action. You can change the format—e.g. sub, act when there is no resource, or sub, sub2, obj, act when two subjects are involved.
Definizione della Politica
The [policy_definition] section describes the structure of policy rules. For example:
[policy_definition]
p = sub, obj, act
p2 = sub, act
With a policy file like:
p, alice, data1, read
p2, bob, write-all-objects
Each line in the policy file is one rule. The first token is the policy type (p, p2, etc.) and must match a policy definition. The example above yields these bindings for the matcher:
(alice, data1, read) -> (p.sub, p.obj, p.act)
(bob, write-all-objects) -> (p2.sub, p2.act)
Policy rule elements are always treated as strings. See casbin/casbin#113 for discussion.
Policy Effect
The [policy_effect] section defines how to combine results when multiple policies match (e.g. one allows, another denies).
[policy_effect]
e = some(where (p.eft == allow))
This gives allow-override: if any matched policy has effect allow, the result is allow. The p.eft field is the policy effect (allow or deny). It is optional and defaults to allow when omitted.
Here's an alternative policy effect:
[policy_effect]
e = !some(where (p.eft == deny))
This implements deny-override: the result is allow only when no matched policy has effect deny. You can combine expressions with logical operators:
[policy_effect]
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
That expression requires at least one allow and no deny. Casbin supports both allow and deny; when both match, deny wins.
The policy effect must be one of the built-in effects below. Custom effect expressions are not supported in the current implementation.
Available built-in policy effects:
| Effetto di Policy | Significato | Esempio |
|---|---|---|
| some(where (p.eft == allow)) | allow-override | ACL, RBAC, ecc. |
| !some(where (p.eft == deny)) | negazione-sovraordinata | Negazione-sovraordinata |
| alcuni(dove (p.eft == allow)) && !alcuni(dove (p.eft == deny)) | allow-e-deny | Allow-e-deny |
| priorità(p.eft) || deny | priorità | Priorità |
| prioritàSoggetto(p.eft) | priorità basata sul ruolo | Priorità-Soggetto |
Constraint Definition
The optional [constraint_definition] section defines invariants for RBAC (e.g. separation of duties). Constraints are checked when role assignments change. You must have [role_definition] to use constraints.
[constraint_definition]
c = sod("finance_requester", "finance_approver")
c2 = sodMax(["payroll_view", "payroll_edit", "payroll_approve"], 1)
c3 = roleMax("superadmin", 2)
c4 = rolePre("db_admin", "security_trained")
Constraint Types
Separation of Duties (sod) — A user cannot hold both roles. If Alice has finance_requester, she cannot be assigned finance_approver.
c = sod("finance_requester", "finance_approver")
Separation of Duties Max (sodMax) — Limits how many roles from a set a user can have. With sodMax(..., 1) for payroll roles, a user can have at most one of view, edit, or approve.
c2 = sodMax(["payroll_view", "payroll_edit", "payroll_approve"], 1)
Role Cardinality (roleMax) — Caps how many users can have a role. For example, at most two users can have superadmin.
c3 = roleMax("superadmin", 2)
Prerequisite Role (rolePre) — One role is required before another. For example, a user must have security_trained before being assigned db_admin.
c4 = rolePre("db_admin", "security_trained")
How Constraints Work
Constraints are enforced when grouping policies change (e.g. AddGroupingPolicy(), RemoveGroupingPolicy()). If a change would violate a constraint, the operation fails with an error and the policy is not updated.
When the model is loaded, all constraints are checked against current policies. Invalid constraints (bad syntax, missing RBAC setup, or current data that already violates a constraint) cause load to fail with a clear error.
Corrisponditori
The [matchers] section defines how requests are evaluated against policy rules.
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
This matcher requires the request subject, object, and action to match the policy fields exactly.
Matchers support arithmetic (+, -, *, /) and logical (&&, ||, !) operators.
Expression order in matchers
The order of expressions in a matcher can significantly affect performance. Esempio:
const rbac_models = `
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
`
func TestManyRoles(t *testing.T) {
m, _ := model.NewModelFromString(rbac_models)
e, _ := NewEnforcer(m, false)
roles := []string{"admin", "manager", "developer", "tester"}
// 2500 projects
for nbPrj := 1; nbPrj < 2500; nbPrj++ {
// 4 objects and 1 role per object (so 4 roles)
for _, role := range roles {
roleDB := fmt.Sprintf("%s_project:%d", role, nbPrj)
objectDB := fmt.Sprintf("/projects/%d", nbPrj)
e.AddPolicy(roleDB, objectDB, "GET")
}
jasmineRole := fmt.Sprintf("%s_project:%d", roles[1], nbPrj)
e.AddGroupingPolicy("jasmine", jasmineRole)
}
e.AddGroupingPolicy("abu", "manager_project:1")
e.AddGroupingPolicy("abu", "manager_project:2499")
// With same number of policies
// User 'abu' has only two roles
// User 'jasmine' has many roles (1 role per policy, here 2500 roles)
request := func(subject, object, action string) {
t0 := time.Now()
resp, _ := e.Enforce(subject, object, action)
tElapse := time.Since(t0)
t.Logf("RESPONSE %-10s %s\t %s : %5v IN: %+v", subject, object, action, resp, tElapse)
if tElapse > time.Millisecond*100 {
t.Errorf("More than 100 milliseconds for %s %s %s : %+v", subject, object, action, tElapse)
}
}
request("abu", "/projects/1", "GET") // really fast because only 2 roles in all policies and at the beginning of the casbin_rule table
request("abu", "/projects/2499", "GET") // fast because only 2 roles in all policies
request("jasmine", "/projects/1", "GET") // really fast at the beginning of the casbin_rule table
request("jasmine", "/projects/2499", "GET") // slow and fails the only 1st time <<<< pb here
request("jasmine", "/projects/2499", "GET") // fast maybe due to internal cache mechanism
// same issue with non-existing roles
// request("jasmine", "/projects/999999", "GET") // slow fails the only 1st time <<<< pb here
// request("jasmine", "/projects/999999", "GET") // fast maybe due to internal cache mechanism
}
Enforcement duration can reach 6 seconds.
go test -run ^TestManyRoles$ github.com/casbin/casbin/v3 -v
=== RUN TestManyRoles
rbac_api_test.go:598: RESPONSE abu /projects/1 GET : true IN: 438.379µs
rbac_api_test.go:598: RESPONSE abu /projects/2499 GET : true IN: 39.005173ms
rbac_api_test.go:598: RESPONSE jasmine /projects/1 GET : true IN: 1.774319ms
rbac_api_test.go:598: RESPONSE jasmine /projects/2499 GET : true IN: 6.164071648s
rbac_api_test.go:600: More than 100 milliseconds for jasmine /projects/2499 GET : 6.164071648s
rbac_api_test.go:598: RESPONSE jasmine /projects/2499 GET : true IN: 12.164122ms
--- FAIL: TestManyRoles (6.24s)
FAIL
FAIL github.com/casbin/casbin/v3 6.244s
FAIL
Put cheaper conditions first and expensive ones (e.g. role lookups) later. Reordering the matcher in the example above to:
[matchers]
m = r.obj == p.obj && g(r.sub, p.sub) && r.act == p.act
go test -run ^TestManyRoles$ github.com/casbin/casbin/v3 -v
=== RUN TestManyRoles
rbac_api_test.go:599: RESPONSE abu /projects/1 GET : true IN: 786.635µs
rbac_api_test.go:599: RESPONSE abu /projects/2499 GET : true IN: 4.933064ms
rbac_api_test.go:599: RESPONSE jasmine /projects/1 GET : true IN: 2.908534ms
rbac_api_test.go:599: RESPONSE jasmine /projects/2499 GET : true IN: 7.292963ms
rbac_api_test.go:599: RESPONSE jasmine /projects/2499 GET : true IN: 6.168307ms
--- PASS: TestManyRoles (0.05s)
PASS
ok github.com/casbin/casbin/v3 0.053s
Multiple section types
You can define multiple request, policy, effect, or matcher sections with suffixes like r2, p2, e2, m2. They pair by suffix: r2 is matched with p2 via m2 and combined with e2.
To use a non-default set, pass an EnforceContext as the first argument to enforce. Structure:
EnforceContext{"r2","p2","e2","m2"}
type EnforceContext struct {
RType string
PType string
EType string
MType string
}
const enforceContext = new EnforceContext('r2', 'p2', 'e2', 'm2');
class EnforceContext {
constructor(rType, pType, eType, mType) {
this.pType = pType;
this.eType = eType;
this.mType = mType;
this.rType = rType;
}
}
EnforceContext enforceContext = new EnforceContext("2");
public class EnforceContext {
private String pType;
private String eType;
private String mType;
private String rType;
public EnforceContext(String suffix) {
this.pType = "p" + suffix;
this.eType = "e" + suffix;
this.mType = "m" + suffix;
this.rType = "r" + suffix;
}
}
Example usage (see the model and policy examples):
// Pass in a suffix as a parameter to NewEnforceContext, such as 2 or 3, and it will create r2, p2, etc.
enforceContext := NewEnforceContext("2")
// You can also specify a certain type individually
enforceContext.EType = "e"
// Don't pass in EnforceContext; the default is r, p, e, m
e.Enforce("alice", "data2", "read") // true
// Pass in EnforceContext
e.Enforce(enforceContext, struct{ Age int }{Age: 70}, "/data1", "read") //false
e.Enforce(enforceContext, struct{ Age int }{Age: 30}, "/data1", "read") //true
// Pass in a suffix as a parameter to NewEnforceContext, such as 2 or 3, and it will create r2, p2, etc.
const enforceContext = new NewEnforceContext('2');
// You can also specify a certain type individually
enforceContext.eType = "e"
// Don't pass in EnforceContext; the default is r, p, e, m
e.Enforce("alice", "data2", "read") // true
// Pass in EnforceContext
e.Enforce(enforceContext, {Age: 70}, "/data1", "read") //false
e.Enforce(enforceContext, {Age: 30}, "/data1", "read") //true
// Pass in a suffix as a parameter to NewEnforceContext, such as 2 or 3, and it will create r2, p2, etc.
EnforceContext enforceContext = new EnforceContext("2");
// You can also specify a certain type individually
enforceContext.seteType("e");
// Don't pass in EnforceContext; the default is r, p, e, m
e.enforce("alice", "data2", "read"); // true
// Pass in EnforceContext
// TestEvalRule is located in https://github.com/casbin/jcasbin/blob/master/src/test/java/org/casbin/jcasbin/main/AbacAPIUnitTest.java#L56
e.enforce(enforceContext, new AbacAPIUnitTest.TestEvalRule("alice", 70), "/data1", "read"); // false
e.enforce(enforceContext, new AbacAPIUnitTest.TestEvalRule("alice", 30), "/data1", "read"); // true
Special grammar: in operator
The in operator checks whether the right-hand array contains the left-hand value (using == equality, no type coercion). The left side can be any value; the right side must be an array of the same type. In Go, arrays must be []interface{}.
Reference examples: rbac_model_matcher_using_in_op, keyget2_model, and keyget_model.
Example:
[request_definition]
r = sub, obj
...
[matchers]
m = r.sub.Name in (r.obj.Admins)
e.Enforce(Sub{Name: "alice"}, Obj{Name: "a book", Admins: []interface{}{"alice", "bob"}})
Expression evaluators
Matchers are evaluated by a language-specific expression engine. Casbin uses these to provide a unified PERM syntax. Some engines support extra features beyond the documented syntax; those features may not be available in other languages. Use only documented syntax if you need cross-language compatibility.
Expression evaluators by implementation:
| Implementazione | Linguaggio | Valutatore di Espressioni |
|---|---|---|
| Casbin | Golang | https://github.com/casbin/govaluate |
| jCasbin | Java | https://github.com/killme2008/aviatorscript |
| Node-Casbin | Node.js | https://github.com/donmccurdy/expression-eval |
| PHP-Casbin | PHP | https://github.com/symfony/expression-language |
| PyCasbin | Python | https://github.com/danthedeckie/simpleeval |
| Casbin.NET | C# | https://github.com/davideicardi/DynamicExpresso |
| Casbin4D | Delphi | https://github.com/casbin4d/Casbin4D/tree/master/SourceCode/Common/Third%20Party/TExpressionParser |
| casbin-rs | Rust | https://github.com/jonathandturner/rhai |
| casbin-cpp | C++ | https://github.com/ArashPartow/exprtk |
If enforcement is slow, the expression evaluator is often the bottleneck. See the Benchmarks page and consider reporting issues to the Casbin or evaluator project.