Casbin
Model

Syntax for Models

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.

Request definition

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.

Policy Definition

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](https://github.com/casbin/casbin/issues/113)

Policy Effect

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:

Policy EffectMeaningExample
some(where (p.eft == allow))allow-overrideACL, RBAC, etc.
!some(where (p.eft == deny))deny-overrideDeny-override
some(where (p.eft == allow)) && !some(where (p.eft == deny))allow-and-denyAllow-and-deny
priority(p.eft) || denypriorityPriority
subjectPriority(p.eft)priority based on roleSubject-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.

Matchers

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 (&&, ||, !).

Order of expressions in matchers

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
```bash
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

When requiring multiple policy definitions or matchers, use identifiers like p2 or m2. All four primary sections support multiple types through numbered suffixes: r2, e2, etc. By default, these sections maintain one-to-one correspondence—your r2 section uses the m2 matcher for matching p2 policies.

Pass an EnforceContext as the first enforce parameter to specify section types. The EnforceContext 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 class EnforceContext(String suffix) {
      this.pType = "p" + suffix;
      this.eType = "e" + suffix;
      this.mType = "m" + suffix;
      this.rType = "r" + suffix;
    }
}

Example usage follows. Refer to the model and policy files. Request format:

// 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

The "in" operator is available—the sole text-named operator. It checks whether the right-side array contains a value equal to the left-side value. Equality uses the == operator without type checking between values. Values castable to interface and comparable with == behave as expected. Arrays must be []interface type when used as parameters.

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)
```go
e.Enforce(Sub{Name: "alice"}, Obj{Name: "a book", Admins: []interface{}{"alice", "bob"}})

Expression Evaluator

Casbin's matcher evaluation employs language-specific expression evaluators. Casbin leverages these evaluators to deliver the unified PERM language. Beyond the documented model syntax, these evaluators may provide additional functionality not universally supported across languages or implementations. Exercise caution when utilizing such features.

Expression evaluators by implementation:

Performance issues with Casbin likely stem from expression evaluator inefficiency. Report problems to either Casbin or the expression evaluator for optimization guidance. Consult the Benchmarks section for additional details.

On this page