Skip to main content

모델(Model) 문법

  • 모델 설정에서 다음 4가지 항목을 반드시 포함합니다: [request_definition], [policy_definition], [policy_effect], [matchers].

  • RBAC를 사용할 때는 [role_definition] 항목이 추가됩니다.

  • A model CONF can contain comments. The comments start with #, and # will comment the rest of the line.

요청(Request) 모델 정의

[request_definition] is the definition for the access request. It defines the arguments in e.Enforce(...) function.

[request_definition]
r = sub, obj, act

sub, obj, act는 각각 접근 주체(Subject), 접근 대상(Object), 그리고 접근 동작(Action) 을 나타냅니다. 요청 형식을 맞춤 정의할 수 있습니다. sub, act와 같이 특정 리소스를 지정하지 않아도 되거나, sub, sub2, obj, act와 같이 복수의 접근 주체를 정의할 수 있습니다.

정책(Policy) 모델 정의

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

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

그리고 다음과 같은 정책 설정 파일이 있습니다.

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

정책 설정 파일의 각 줄을 정책 규칙이라고 합니다. 각 정책 규칙은 정책 유형으로 시작합니다. 예를 들어, p, p2입니다. 여러 종류의 정책 유형을 사용할 때, 어느 정책 유형에 해당하는 지를 가리킵니다. 위 정책은 아래와 같이 연결됩니다. 이 연결 관계는 [matchers] 항목에서 참조할 수 있습니다.

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

The elements in a policy rule are always regarded asstring. If you have any question about this, please see the discussion at: https://github.com/casbin/casbin/issues/113

정책 Effect

[policy_effect]에서 정책 Effect(요청을 허용할지 거부할지 결정) 를 정의합니다. 여러 개의 정책 규칙이 있을 때 최종적으로 요청을 허용할 것인지, 거부할 것인지를 판단하는 규칙을 지정합니다. 예를 들어, 한 규칙은 허용하고 다른 규칙은 거부되는 경우가 있습니다.

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

이 정책 Effect의 의미는, allow 정책 규칙 중 하나라도 만족되면 최종적으로 allow가 된다는 뜻입니다. (허용 우선) p.eft는 정책의 Effect입니다. allow 혹은 deny 둘 중 하나입니다. 생략 가능한 값이며, 기본 값은 allow입니다. 특정 값을 입력하지 않았으므로 기본 값이 사용되었습니다.

아래는 또 다른 예제입니다.

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

명시적인 거부 규칙이 없다면 최종적으로 허용이 된다는 뜻입니다. (거부 우선) some은 적어도 하나 이상 만족하는 정책 규칙이 있을 때 참(true) 이 됩니다. any는 어떤 정책 규칙에도 부합할 때 참(true) 이 됩니다. (여기서는 사용되지 않았습니다.) 정책 Effect는 논리 연산 표현식과 함께 사용할 수 있습니다.

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

이것은 적어도 하나 이상의 허용 규칙에 부합하고, 거부가 되는 규칙이 하나도 없는 조건을 나타냅니다. 따라서 이와 같이, 권한 관리에서 허용과 거부를 모두 사용할 수 있습니다. 거부가 허용에 우선합니다.

note

Although we designed the syntax of policy effect as above, the current implementations only use hard-coded policy effect, as we found there's no much need for that sort of flexibility. 따라서 지금은 맞춤 설정 대신 아래의 빌트인 정책 Effect를 사용하시기 바랍니다.

지원되는 빌트인 정책 Effect는 아래와 같습니다.

정책 Effect의미사례
some(where (p.eft == allow))허용 우선ACL, RBAC 등
!some(where (p.eft == deny))거부 우선거부(Deny) 우선
some(where (p.eft == allow)) && !some(where (p.eft == deny))명시적 허용 + 명시적 거부허용/거부
priority(p.eft) || deny우선순위우선순위
subjectPriority(p.eft)priority base on roleSubject-Priority

조건식

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

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

위 조건식은 간단합니다. 요청의 접근 주체(Subject), 접근 대상(Object) 및 동작(Action) 이 정책 규칙에 부합해야 한다는 것을 나타냅니다.

+, -, *, /와 같은 수리 연산자와 &&, ||, ! 같은 논리 연산자를 matchers에서 사용할 수 있습니다.

Orders of expressions in matchers

The order of expressions can greatly affect performance. Look at 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 fail 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 fail the only 1st time <<<< pb here
// request("jasmine", "/projects/999999", "GET") // fast maybe due to internal cache mechanism
}

The enforce time may be very 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 can 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 sections type

If you need multiple policy definitions or multiple matcher, you can use like p2, m2. In fact, all of the above four sections can use multiple types and the syntax is r+number, such as r2, e2. By default these four sections should correspond one to one. Such as your r2 will only use matcher m2 to match policies p2.

You can pass in EnforceContext as the first parameter of enforce method to specify the types, the EnforceContext is like this

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

Example usage, see model and policy, the request is as follows

// Pass in a suffix as 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 Grammer

You could also use in, the only operator with a text name. This operator checks the right-hand side array to see if it contains a value that is equal to the left-side value. Equality is determined by the use of the == operator, and this library doesn't check types between the values. Any two values, when cast to interface{}, and can still be checked for equality with == 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"}})

표현식 평가

각 언어에 있는 조건식 평가기를 사용하여 조건식 평가를 구현되었습니다. 일관된 PERM 언어를 지원하기 위해 각 언어와 연동하도록 하였습니다. 기본적인 공통 모델 문법 외에도, 각 언어 마다 각각의 추가 기능이 있습니다. 알아서 적절히 사용하십시오.

현재 구현된 Casbin 표현식 평가기 구현체 목록은 아래와 같습니다.

구현체언어Expression 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
note

If you encounter performance issue about Casbin, it's probably caused by the low efficiency of the expression evaluator. Casbin이나 표현식 평가기의 개선을 위해 이슈를 등록해주십시오. See Benchmarks section for details.