Синтаксис модели
Каждый 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-override | ACL, RBAC и др. |
| !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) || deny | priority | Priority |
| 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
}
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 EnforceContext(String suffix) {
this.pType = "p" + suffix;
this.eType = "e" + suffix;
this.mType = "m" + suffix;
this.rType = "r" + suffix;
}
}
Пример использования (см. примеры 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
// 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/apache/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
Специальный синтаксис: оператор 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. Некоторые движки поддерживают дополнительные функции за пределами документированного синтаксиса; эти функции могут быть недоступны в других языках. Используйте только документированный синтаксис, если вам нужна совместимость между языками.
Вычислители выражений по реализациям:
| Реализация | Язык | Вычислитель выражений |
|---|---|---|
| Casbin | Golang | https://github.com/apache/casbin-govaluate |
| jCasbin | Java | https://github.com/killme2008/aviatorscript |
| Node-Casbin | Node.js | https://github.com/donmccurdy/expression-eval |
| PHP-Casbin | PHP | https://github.com/symfony/expression-language |
| PyCasbin | Python | https://github.com/danthedeckie/simpleeval |
| Casbin.NET | C# | https://github.com/davideicardi/DynamicExpresso |
| Casbin4D | Delphi | https://github.com/casbin4d/Casbin4D/tree/master/SourceCode/Common/Third%20Party/TExpressionParser |
| casbin-rs | Rust | https://github.com/jonathandturner/rhai |
| casbin-cpp | C++ | 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.