Syntax for Models
-
A model configuration (CONF) requires at minimum four sections:
[request_definition],[policy_definition],[policy_effect], and[matchers]. -
Models implementing Role-Based Access Control (RBAC) must additionally include a
[role_definition]section. -
Models requiring policy invariant enforcement for RBAC may optionally include a
[constraint_definition]section. -
Model configuration (CONF) files support comments. The
#symbol initiates a comment, treating all subsequent text on that line as commentary.
Определение запроса
The [request_definition] section specifies the parameters passed to the e.Enforce(...) function.
[request_definition]
r = sub, obj, act
Here, sub, obj, and act represent the traditional access control triple: subject (requesting entity), object (target resource), and action (operation type). This format is customizable—use sub, act when resources needn't be specified, or sub, sub2, obj, act when two requesting entities are involved.
Определение политики
The [policy_definition] describes policy structure and semantics. Consider this model:
[policy_definition]
p = sub, obj, act
p2 = sub, act
With this corresponding policy (from a policy file):
p, alice, data1, read
p2, bob, write-all-objects
Policy rules are organized as lines in the policy file. Each rule begins with a policy type identifier (like p or p2) that matches one of your policy definitions when multiple exist. The policy above creates these bindings for matcher use:
(alice, data1, read) -> (p.sub, p.obj, p.act)
(bob, write-all-objects) -> (p2.sub, p2.act)
Policy rule elements are always interpreted as strings. For questions about this behavior, consult the discussion at: https://github.com/casbin/casbin/issues/113
Эффект политики
The [policy_effect] section determines request approval when multiple matching policies exist—for instance, when one permits and another denies.
[policy_effect]
e = some(where (p.eft == allow))
This policy effect implements "allow-override": when any matched policy grants allow, the final decision is allow. The p.eft field contains a policy's effect, taking values of either allow or deny. This field is optional and defaults to allow—since we omitted it above, the default applies.
Here's an alternative policy effect:
[policy_effect]
e = !some(where (p.eft == deny))
This implements "deny-override": the final effect is allow when no deny policies match. Here, some indicates at least one matching policy exists, while any means all policies match (though not used in this example). Policy effects can combine using logical expressions:
[policy_effect]
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
This requires at least one allow policy and zero deny policies. Both allow and deny authorizations are supported, with deny taking priority over allow.
While we designed the policy effect syntax shown above, current implementations use hard-coded policy effects. We discovered limited need for this level of customization. Consequently, you must select from the built-in policy effects rather than defining custom ones.
Available built-in policy effects:
| Эффект политики | Значение | Пример |
|---|---|---|
| some(where (p.eft == allow)) | allow-override | ACL, RBAC и т.д. |
| !some(where (p.eft == deny)) | deny-override | Deny-override |
| some(where (p.eft == allow)) && !some(where (p.eft == deny)) | allow-and-deny | Allow-and-deny |
| priority(p.eft) || deny | priority | Priority |
| subjectPriority(p.eft) | приоритет на основе роли | Subject-Priority |
Constraint Definition
The [constraint_definition] section establishes policy invariants for RBAC systems. Constraints maintain role assignment validity by verifying rules whenever policies change. This optional section requires RBAC enablement (necessitating [role_definition]).
[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) - Blocks users from simultaneously holding conflicting roles. When Alice receives the finance_requester role, the system prevents assigning her finance_approver.
c = sod("finance_requester", "finance_approver")
Separation of Duties Max (sodMax) - Restricts the number of roles from a set that each user may hold. Setting sodMax to 1 for payroll operations means a user can view, edit, or approve—never possessing more than one of these roles concurrently.
c2 = sodMax(["payroll_view", "payroll_edit", "payroll_approve"], 1)
Role Cardinality (roleMax) - Limits how many users can possess a specific role. Setting a limit of 2 for superadmin restricts that role to two people organization-wide.
c3 = roleMax("superadmin", 2)
Prerequisite Role (rolePre) - Requires having a prerequisite role before granting another role. Users cannot receive db_admin access without first possessing the security_trained role.
c4 = rolePre("db_admin", "security_trained")
How Constraints Work
Constraints perform automatic validation during grouping policy modifications via methods like AddGroupingPolicy() or RemoveGroupingPolicy(). When changes violate constraints, the operations fail and return constraint violation errors, leaving policies unchanged.
At model initialization, the system verifies all existing constraints against current policies. Invalid constraints (incorrect syntax, missing RBAC configuration, or violations of existing data) prevent model loading and generate descriptive errors.
Сопоставители
The [matchers] section specifies how policy rules evaluate against requests.
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
This represents the most basic matcher, requiring that request subject, object, and action match corresponding policy rule fields.
Matchers support arithmetic operators (+, -, *, /) and logical operators (&&, ||, !).
Порядок выражений в сопоставителях
Expression ordering significantly impacts performance. Review the following example for details:
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
Reordering matcher expressions by placing expensive operations (like functions) later dramatically reduces execution time.
Modifying the matcher expression order in the previous example:
[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