Перейти к основному содержимому

Синтаксис модели

Каждый CONF-файл модели должен определять четыре секции: [request_definition], [policy_definition], [policy_effect] и [matchers].

  • Для RBAC добавьте секцию [role_definition].
  • Для RBAC с ограничениями (например, разделение обязанностей) можно добавить [constraint_definition].
  • Строки, начинающиеся с #, являются комментариями; остальная часть строки игнорируется.

Определение запроса​

Секция [request_definition] определяет параметры, передаваемые в e.Enforce(...).

[request_definition]
r = sub, obj, act

Здесь sub, obj и act — это стандартная тройка: субъект, объект и действие. Вы можете изменить формат, например, использовать sub, act, если ресурс отсутствует, или sub, sub2, obj, act, если участвуют два субъекта.

Определение политики​

Секция [policy_definition] описывает структуру правил политики. Например:

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

При наличии файла политики вида:

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

Каждая строка в файле политики представляет собой одно правило. Первый токен указывает тип политики (p, p2 и т.д.) и должен совпадать с определением политики. Пример выше даёт следующие привязки для матчера:

(alice, data1, read) -> (p.sub, p.obj, p.act)
(bob, write-all-objects) -> (p2.sub, p2.act)
подсказка

Элементы правил политики всегда обрабатываются как строки. См. обсуждение в casbin/casbin#113.

Эффект политики​

Секция [policy_effect] определяет способ комбинирования результатов при совпадении нескольких политик (например, одна разрешает, другая запрещает).

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

Это обеспечивает поведение allow-override: если хотя бы одна совпавшая политика имеет эффект allow, результат будет «разрешено». Поле p.eft задаёт эффект политики (allow или deny). Оно необязательно и по умолчанию принимает значение allow, если не указано.

Вот альтернативный эффект политики:

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

Это реализует поведение deny-override: результат будет «разрешено» только если ни одна из совпавших политик не имеет эффекта deny. Выражения можно комбинировать с помощью логических операторов:

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

Данное выражение требует наличия хотя бы одного allow и отсутствия deny. Casbin поддерживает оба варианта; при их одновременном совпадении приоритет отдаётся запрету.

примечание

Эффект политики должен быть одним из встроенных эффектов, перечисленных ниже. Пользовательские выражения для эффектов в текущей реализации не поддерживаются.

Доступные встроенные эффекты политики:

Эффект политикиЗначениеПример
some(where (p.eft == allow))allow-overrideACL, RBAC и др.
!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)Приоритет на основе ролиSubject-Priority

Определение ограничений​

Необязательная секция [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")

Типы ограничений​

Разделение обязанностей (sod) — Пользователь не может занимать обе роли. Если у Алисы есть роль finance_requester, ей нельзя назначить роль finance_approver.

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

Максимальное разделение обязанностей (sodMax) — Ограничивает количество ролей из набора, которые может иметь пользователь. С ограничением sodMax(..., 1) для ролей payroll пользователь может иметь максимум одну из ролей: view, edit или approve.

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

Мощность роли (roleMax) — Устанавливает лимит на количество пользователей, которые могут иметь определённую роль. Например, не более двух пользователей могут иметь роль superadmin.

c3 = roleMax("superadmin", 2)

Предварительная роль (rolePre) — Одна роль требуется до назначения другой. Например, пользователь должен иметь роль security_trained, прежде чем ему будет назначена роль db_admin.

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

Как работают ограничения​

Ограничения применяются при изменении групповых политик (например, через AddGroupingPolicy() или RemoveGroupingPolicy()). Если изменение нарушает ограничение, операция завершается ошибкой, и политика не обновляется.

При загрузке модели все ограничения проверяются относительно текущих политик. Недействительные ограничения (неверный синтаксис, отсутствие настройки RBAC или данные, уже нарушающие ограничение) приводят к ошибке загрузки с понятным сообщением.

Матчеры​

Раздел [matchers] определяет, как запросы оцениваются относительно правил политики.

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

Этот матчер требует точного совпадения субъекта, объекта и действия запроса с полями политики.

Матчеры поддерживают арифметические (+, -, *, /) и логические (&&, ||, !) операторы.

Порядок выражений в матчерах​

Порядок выражений в матчере может существенно влиять на производительность. Пример:

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) может достигать 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

Ставьте более дешевые условия первыми, а дорогие (например, поиск ролей) — позже. Если изменить порядок матчера в примере выше на:

[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

Несколько типов секций​

Вы можете определить несколько секций запросов, политик, эффектов или матчеров с суффиксами, такими как r2, p2, e2, m2. Они сопоставляются по суффиксу: r2 связывается с p2 через m2 и комбинируется с e2.

Чтобы использовать набор по умолчанию, передайте EnforceContext в качестве первого аргумента в enforce. Структура:

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​

Оператор 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"}})

Вычислители выражений​

Матчеры вычисляются специфичным для языка движком выражений. Casbin использует их для предоставления единого синтаксиса PERM. Некоторые движки поддерживают дополнительные функции за пределами документированного синтаксиса; эти функции могут быть недоступны в других языках. Используйте только документированный синтаксис, если вам нужна совместимость между языками.

Вычислители выражений по реализациям:

РеализацияЯзыкВычислитель выражений
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
примечание

Если проверка доступа выполняется медленно, узким местом часто является вычислитель выражений. См. страницу Benchmarks и рассмотрите возможность отправки отчетов об ошибках в проекты Casbin или вычислителя.

Comments are powered by giscus and GitHub Discussions. They load from giscus.app and GitHub only after you click the button below.