跳转至主要内容

Syntax for Models

  • A model configuration (CONF) should have at least four sections: [request_definition], [policy_definition], [policy_effect], and [matchers].

  • If a model uses Role-Based Access Control (RBAC), it should also include the [role_definition] section.

  • A model configuration (CONF) can contain comments. Comments start with the # symbol, and everything after the # symbol will be commented out.

Request definition

The [request_definition] section defines the arguments in the e.Enforce(...) function.

[request_definition]
r = sub, obj, act

In this example, sub, obj, and act represent the classic access triple: the subject (accessing entity), the object (accessed resource), and the action (access method). However, you can customize your own request format. For example, you can use sub, act if you don't need to specify a particular resource, or sub, sub2, obj, act if you have two accessing entities.

Policy Definition

The [policy_definition] is the definition for a policy. It defines the meaning of the policy. For example, we have the following model:

[policy_definition]
p = sub, obj, act
p2 = sub, act

And we have the following policy (if in a policy file):

p, alice, data1, read
p2, bob, write-all-objects

Each line in a policy is called a policy rule. Each policy rule starts with a policy type, such as p or p2. It is used to match the policy definition if there are multiple definitions. The above policy shows the following binding. The binding can be used in the matcher.

(alice, data1, read) -> (p.sub, p.obj, p.act)
(bob, write-all-objects) -> (p2.sub, p2.act)
提示

The elements in a policy rule are always regarded as strings. If you have any questions about this, please refer to the discussion at: https://github.com/casbin/casbin/issues/113

Policy Effect

[policy_effect] is the definition for the policy effect. It determines whether the access request should be approved if multiple policy rules match the request. For example, one rule permits and the other denies.

[policy_effect]
e = some(where (p.eft == allow))

The above policy effect means that if there's any matched policy rule of allow, the final effect is allow (also known as allow-override). p.eft is the effect for a policy, and it can be either allow or deny. It is optional, and the default value is allow. Since we didn't specify it above, it uses the default value.

Another example for the policy effect is:

[policy_effect]
e = !some(where (p.eft == deny))

This means that if there are no matched policy rules of deny, the final effect is allow (also known as deny-override). some means that there exists one matched policy rule. any means that all matched policy rules (not used here). The policy effect can even be connected with logical expressions:

[policy_effect]
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))

This means that there must be at least one matched policy rule of allow, and there cannot be any matched policy rule of deny. Therefore, in this way, both allow and deny authorizations are supported, and the deny overrides.

备注

Although we designed the syntax of the policy effect as above, the current implementations only use hard-coded policy effects. This is because we found that there isn't much need for that level of flexibility. So for now, you must use one of the built-in policy effects instead of customizing your own.

The supported built-in policy effects are:

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

Matchers

[matchers] is the definition for policy matchers. The matchers are expressions that define how the policy rules are evaluated against the request.

[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act

The above matcher is the simplest and means that the subject, object, and action in a request should match the ones in a policy rule.

Arithmetic operators like +, -, *, / and logical operators like &&, ||, ! can be used in matchers.

Order of expressions in matchers

The order of expressions can greatly affect performance. Take a look at the following example for more 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
}

The enforcement time may be very long, up to 6 seconds.

go test -run ^TestManyRoles$ github.com/casbin/casbin/v2 -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/v2 6.244s
FAIL

However, if we adjust the order of the expressions in matchers and put more time-consuming expressions like functions behind, the execution time will be very short.

Changing the order of expressions in matchers in the above example to:

[matchers]
m = r.obj == p.obj && g(r.sub, p.sub) && r.act == p.act
go test -run ^TestManyRoles$ github.com/casbin/casbin/v2 -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/v2 0.053s

Multiple Section Types

If you need multiple policy definitions or multiple matchers, you can use p2 or m2 as examples. In fact, all four sections mentioned above can use multiple types, and the syntax is r followed by a number, such as r2 or e2. By default, these four sections should correspond one-to-one. For example, your r2 section will only use the m2 matcher to match p2 policies.

You can pass an EnforceContext as the first parameter of the enforce method to specify the types. The EnforceContext is defined as follows:

EnforceContext{"r2","p2","e2","m2"}
type EnforceContext struct {
RType string
PType string
EType string
MType string
}

Here is an example usage. Please refer to the model and policy. The request is as follows:

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

Special Grammar

You could also use the "in" operator, which is the only operator with a text name. This operator checks the array on the right-hand side to see if it contains a value that is equal to the value on the left side. Equality is determined by using the == operator, and this library does not check the types between the values. As long as two values can be cast to interface{} and can still be checked for equality with ==, they will act as expected. Note that you can use a parameter for the array, but it must be an []interface{}.

Also refer to 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 Evaluator

The matcher evaluation in Casbin is implemented by expression evaluators in each language. Casbin integrates their powers to provide the unified PERM language. In addition to the model syntax provided here, these expression evaluators may offer extra functionality that might not be supported by another language or implementation. Please be cautious when using this functionality.

The expression evaluators used by each Casbin implementation are as follows:

ImplementationLanguageExpression Evaluator
CasbinGolanghttps://github.com/Knetic/govaluate
jCasbinJavahttps://github.com/killme2008/aviator
Node-CasbinNode.jshttps://github.com/donmccurdy/expression-eval
PHP-CasbinPHPhttps://github.com/symfony/expression-language
PyCasbinPythonhttps://github.com/danthedeckie/simpleeval
Casbin.NETC#https://github.com/davideicardi/DynamicExpresso
Casbin4DDelphihttps://github.com/casbin4d/Casbin4D/tree/master/SourceCode/Common/Third%20Party/TExpressionParser
casbin-rsRusthttps://github.com/jonathandturner/rhai
casbin-cppC++https://github.com/ArashPartow/exprtk
备注

If you encounter a performance issue with Casbin, it is likely caused by the low efficiency of the expression evaluator. You can address the issue to Casbin or the expression evaluator directly for advice on speeding up the performance. For more details, please refer to the Benchmarks section.