跳转到主要内容

Model 语法

每个 model CONF 文件都必须定义以下四个部分:[request_definition]、[policy_definition]、[policy_effect] 和 [matchers]。

  • 对于 RBAC,需要添加 [role_definition] 部分。
  • 对于带约束的 RBAC(例如职责分离),可以添加 [constraint_definition]。
  • 以 # 开头的行是注释;该行其余内容会被忽略。

请求定义(Request Definition)​

[request_definition] 部分定义了传给 e.Enforce(...) 的参数。

[request_definition]
r = sub, obj, act

这里的 sub、obj 和 act 是标准三元组:subject(主体)、object(客体)和 action(操作)。你可以改变其格式——例如没有资源时用 sub, act,涉及两个主体时用 sub, sub2, obj, act。

Policy 定义(Policy Definition)​

[policy_definition] 部分描述 policy 规则的结构。例如:

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

对应的 policy 文件如下:

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

policy 文件中的每一行都是一条规则。第一个 token 是 policy 类型(p、p2 等),并且必须与某个 policy 定义相对应。上面的示例会为 matcher 产生如下绑定:

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

policy 规则的元素始终被视为字符串。相关讨论参见 casbin/casbin#113。

Policy 效果(Policy Effect)​

[policy_effect] 部分定义了当多条 policy 同时匹配时(例如一条允许、另一条拒绝)如何组合结果。

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

这产生allow-override(允许优先):只要任一匹配的 policy 的 effect 为 allow,结果就是允许。p.eft 字段表示 policy 的 effect(allow 或 deny)。它是可选的,省略时默认为 allow。

下面是另一种 policy effect:

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

这实现了 deny-override(拒绝优先):只有当没有匹配的 policy 的 effect 为 deny 时,结果才为允许。你可以用逻辑运算符组合表达式:

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

该表达式要求至少有一个 allow 且没有 deny。Casbin 同时支持 allow 和 deny;当两者同时匹配时,deny 优先。

备注

policy effect 必须是下面列出的内置 effect 之一。当前实现不支持自定义的 effect 表达式。

可用的内置 policy effect:

Policy Effect(策略效果)Meaning(含义)Example(示例)
some(where (p.eft == allow))allow-override(允许优先)ACL, RBAC, etc.
!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) || denypriority(优先级)Priority
subjectPriority(p.eft)priority based on role(基于角色的优先级)Subject-Priority

约束定义(Constraint Definition)​

可选的 [constraint_definition] 部分为 RBAC 定义不变量(例如职责分离)。当角色分配发生变化时会检查约束。要使用约束,必须先有 [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) —— 一个用户不能同时拥有两个角色。如果 Alice 拥有 finance_requester,就不能再被分配 finance_approver。

c = sod("finance_requester", "finance_approver")

职责分离上限(Separation of Duties Max,sodMax) —— 限制用户可以拥有集合中的多少个角色。如果对 payroll 角色使用 sodMax(..., 1),则用户最多只能拥有 view、edit 或 approve 中的一个。

c2 = sodMax(["payroll_view", "payroll_edit", "payroll_approve"], 1)

角色基数(Role Cardinality,roleMax) —— 限制可以拥有某个角色的用户数量。例如,最多两个用户可以拥有 superadmin。

c3 = roleMax("superadmin", 2)

前置角色(Prerequisite Role,rolePre) —— 必须先拥有一个角色,才能拥有另一个角色。例如,用户必须先拥有 security_trained,才能被分配 db_admin。

c4 = rolePre("db_admin", "security_trained")

约束的工作方式(How Constraints Work)​

当 grouping policy 发生变化时(例如 AddGroupingPolicy()、RemoveGroupingPolicy())会强制执行约束。如果某个变更会违反约束,该操作将以错误失败,policy 不会被更新。

加载 model 时,会针对当前 policy 检查所有约束。无效的约束(语法错误、缺少 RBAC 配置,或当前数据已经违反某条约束)会导致加载失败,并给出清晰的错误信息。

匹配器(Matchers)​

[matchers] 部分定义了如何针对 policy 规则评估请求。

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

该 matcher 要求请求的 subject、object 和 action 与 policy 字段完全匹配。

matcher 支持算术运算符(+、-、*、/)和逻辑运算符(&&、||、!)。

matcher 中的表达式顺序(Expression order in matchers)​

matcher 中表达式的顺序会显著影响性能。示例:

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
}

执行耗时最高可达 6 秒。

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

把开销小的条件放在前面,把开销大的条件(例如角色查询)放在后面。将上面示例中的 matcher 重新排序为:

[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

多个 section 类型(Multiple section types)​

你可以定义多个 request、policy、effect 或 matcher section,并使用 r2、p2、e2、m2 这样的后缀。它们按后缀配对:r2 与 p2 通过 m2 配对,并通过 e2 组合结果。

要使用非默认的一组 section,需要给 enforce 的第一个参数传入 EnforceContext。其结构如下:

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

用法示例(参见 model 和 policy 示例):

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

特殊语法:in 运算符(Special grammar: in operator)​

in 运算符检查右侧数组是否包含左侧的值(使用 == 判等,不做类型转换)。左侧可以是任意值;右侧必须是相同类型的数组。在 Go 中,数组必须是 []interface{}。

参考示例:rbac_model_matcher_using_in_op、keyget2_model 和 keyget_model。

示例:

[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)​

matcher 由各语言特定的表达式引擎求值。Casbin 借此提供统一的 PERM 语法。某些引擎支持文档化语法之外的额外特性;这些特性在其他语言中可能不可用。如果需要跨语言兼容性,请只使用文档化的语法。

各实现使用的表达式求值器:

实现(Implementation)语言(Language)表达式求值器(Expression Evaluator)
CasbinGolanghttps://github.com/apache/casbin-govaluate
jCasbinJavahttps://github.com/killme2008/aviatorscript
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
备注

如果执行较慢,表达式求值器往往是瓶颈。参见基准测试页面,并考虑向 Casbin 或求值器项目报告问题。