Syntax for Models
Конфігурація моделі (CONF) повинна містити принаймні чотири розділи:
[request_definition]
,[policy_definition]
,[policy_effect]
та[matchers]
.Якщо модель використовує контроль доступу на основі ролей (RBAC), вона також повинна включати розділ
[role_definition]
.Конфігурація моделі (CONF) може містити коментарі. Коментарі починаються з символу
#
, і все після символу#
буде закоментовано.
Визначення запиту
Розділ [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)
Елементи у правилі політики завжди розглядаються як рядки
. Якщо у вас є питання з цього приводу, будь ласка, зверніться до обговорення за адресою: https://github.com/casbin/casbin/issues/113
Ефект політики
[policy_effect]
є визначенням для ефекту політики. Він визначає, чи слід схвалити запит на доступ, якщо кілька правил політики відповідають запиту. Наприклад, одне правило дозволяє, а інше забороняє.
[policy_effect]
e = some(where (p.eft == allow))
Вищезазначений ефект політики означає, що якщо є будь-яке відповідне правило політики allow
, кінцевий ефект буде allow
(також відомий як allow-override). p.eft
є ефектом для політики, і він може бути або allow
, або deny
. Це необов'язково, і значення за замовчуванням є allow
. Оскільки ми не вказали це вище, використовується значення за замовчуванням.
Інший приклад для ефекту політики:
[policy_effect]
e = !some(where (p.eft == deny))
Це означає, що якщо немає відповідних правил політики deny
, кінцевий ефект буде allow
(також відомий як deny-override). some
означає, що існує хоча б одне відповідне правило політики. any
означає, що всі відповідні правила політики (тут не використовується). Ефект політики навіть може бути поєднаний з логічними виразами:
[policy_effect]
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
Це означає, що має бути хоча б одне відповідне правило політики allow
, і не може бути жодного відповідного правила політики deny
. Отже, таким чином, підтримуються як дозволи, так і заборони, і заборона має перевагу.
Хоча ми розробили синтаксис ефекту політики як вище, поточні реалізації використовують лише жорстко закодовані ефекти політики. Це тому, що ми виявили, що немає великої потреби в такій гнучкості. Отже, наразі ви повинні використовувати один з вбудованих ефектів політики замість налаштування власного.
Підтримувані вбудовані ефекти політики:
Ефект політики | Значення | Приклад |
---|---|---|
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 |
subjectPriority(p.eft) | пріоритет на основі ролі | Subject-Priority |
Матчери
[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
}
Час виконання може бути дуже довгим, до 6 секунд.
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
Однак, якщо ми відрегулюємо порядок виразів у матчерах і розмістимо більш часозатратні вирази, як функції, позаду, час виконання буде дуже коротким.
Зміна порядку виразів у матчерах у вищезазначеному прикладі на:
[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
Декілька Типів Секцій
Якщо вам потрібно декілька визначень політики або декілька матчерів, ви можете використовувати p2
або m2
як приклади. Фактично, всі чотири згадані вище секції можуть використовувати декілька типів, і синтаксис це r
за яким слідує число, наприклад r2
або e2
. За замовчуванням, ці чотири секції повинні відповідати один одному. Наприклад, ваша секція r2
буде використовувати тільки матчер m2
для відповідності політикам p2
.
Ви можете передати EnforceContext
як перший параметр методу enforce
для вказівки типів. EnforceContext
визначений наступним чином:
- Go
- Node.js
- Java
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. Запит наступний:
- Go
- Node.js
- Java
// 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/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", який є єдиним оператором з текстовим ім'ям. Цей оператор перевіряє масив з правої сторони, щоб побачити, чи містить він значення, яке дорівнює значенню з лівої сторони. Рівність визначається за допомогою оператора ==, і ця бібліотека не перевіряє типи між значеннями. Доки два значення можуть бути приведені до interface{} і все ще можуть бути перевірені на рівність за допомогою ==, вони будуть діяти як очікується. Зауважте, що ви можете використовувати параметр для масиву, але він повинен бути []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 реалізована за допомогою оцінювачів виразів у кожній мові. Casbin інтегрує їхні можливості для надання єдиної мови PERM. Окрім синтаксису моделі, наданого тут, ці оцінювачі виразів можуть пропонувати додатковий функціонал, який може не підтримуватися іншою мовою або реалізацією. Будь ласка, будьте обережні при використанні цього функціоналу.
Оцінювачі виразів, які використовуються кожною реалізацією Casbin, наступні:
Реалізація | Мова | Оцінювач Виразів |
---|---|---|
Casbin | Golang | https://github.com/Knetic/govaluate |
jCasbin | Java | https://github.com/killme2008/aviator |
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 |
Якщо ви зіткнулися з проблемою продуктивності Casbin, це, ймовірно, спричинено низькою ефективністю обчислювача виразів. Ви можете звернутися з цією проблемою до Casbin або безпосередньо до обчислювача виразів за порадою щодо прискорення продуктивності. Для отримання додаткової інформації, будь ласка, зверніться до розділу Benchmarks.