Syntax for Models
Eine Modellkonfiguration (CONF) sollte mindestens vier Abschnitte haben:
[request_definition]
,[policy_definition]
,[policy_effect]
und[matchers]
.Wenn ein Modell Role-Based Access Control (RBAC) verwendet, sollte es auch den Abschnitt
[role_definition]
enthalten.Eine Modellkonfiguration (CONF) kann Kommentare enthalten. Kommentare beginnen mit dem
#
Symbol, und alles nach dem#
Symbol wird auskommentiert.
Anforderungsdefinition
Der Abschnitt [request_definition]
definiert die Argumente in der Funktion e.Enforce(...)
.
[request_definition]
r = sub, obj, act
In diesem Beispiel repräsentieren sub
, obj
und act
das klassische Zugriffstriplett: das Subjekt (zugreifende Entität), das Objekt (zugegriffene Ressource) und die Aktion (Zugriffsmethode). Sie können jedoch Ihr eigenes Anforderungsformat anpassen. Zum Beispiel können Sie sub, act
verwenden, wenn Sie keine bestimmte Ressource angeben müssen, oder sub, sub2, obj, act
, wenn Sie zwei zugreifende Entitäten haben.
Richtliniendefinition
Die [policy_definition]
ist die Definition für eine Richtlinie. Es definiert die Bedeutung der Richtlinie. Zum Beispiel haben wir das folgende Modell:
[policy_definition]
p = sub, obj, act
p2 = sub, act
Und wir haben die folgende Richtlinie (wenn in einer Richtliniendatei):
p, alice, data1, read
p2, bob, write-all-objects
Jede Zeile in einer Richtlinie wird als Richtlinienregel bezeichnet. Jede Richtlinienregel beginnt mit einem Richtlinientyp
, wie p
oder p2
. Es wird verwendet, um die Richtliniendefinition abzugleichen, wenn es mehrere Definitionen gibt. Die obige Richtlinie zeigt die folgende Bindung. Die Bindung kann im Matcher verwendet werden.
(alice, data1, read) -> (p.sub, p.obj, p.act)
(bob, write-all-objects) -> (p2.sub, p2.act)
Die Elemente in einer Richtlinienregel werden immer als Zeichenketten
betrachtet. Wenn Sie dazu Fragen haben, verweisen Sie bitte auf die Diskussion unter: https://github.com/casbin/casbin/issues/113
Richtlinienwirkung
[policy_effect]
ist die Definition für die Richtlinienwirkung. Es bestimmt, ob die Zugriffsanforderung genehmigt werden sollte, wenn mehrere Richtlinienregeln der Anforderung entsprechen. Zum Beispiel erlaubt eine Regel und die andere verbietet.
[policy_effect]
e = some(where (p.eft == allow))
Die obige Richtlinienwirkung bedeutet, dass, wenn es irgendeine übereinstimmende Richtlinienregel von allow
gibt, die endgültige Wirkung allow
ist (auch bekannt als allow-override). p.eft
ist die Wirkung für eine Richtlinie, und es kann entweder allow
oder deny
sein. Es ist optional, und der Standardwert ist allow
. Da wir es oben nicht angegeben haben, verwendet es den Standardwert.
Ein weiteres Beispiel für den Effekt der Richtlinie ist:
[policy_effect]
e = !some(where (p.eft == deny))
Das bedeutet, dass, wenn es keine passenden Richtlinienregeln für deny
gibt, der endgültige Effekt allow
ist (auch bekannt als deny-override). some
bedeutet, dass es eine passende Richtlinienregel gibt. any
bedeutet, dass alle passenden Richtlinienregeln (hier nicht verwendet). Der Effekt der Richtlinie kann sogar mit logischen Ausdrücken verbunden werden:
[policy_effect]
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
Das bedeutet, dass es mindestens eine passende Richtlinienregel für allow
geben muss und es darf keine passende Richtlinienregel für deny
geben. Daher werden auf diese Weise sowohl allow- als auch deny-Autorisierungen unterstützt und deny hat Vorrang.
Obwohl wir die Syntax des Richtlinieneffekts wie oben entworfen haben, verwenden die aktuellen Implementierungen nur fest codierte Richtlinieneffekte. Das liegt daran, dass wir festgestellt haben, dass es nicht viel Bedarf für diese Flexibilität gibt. Daher müssen Sie derzeit einen der eingebauten Richtlinieneffekte verwenden, anstatt Ihren eigenen anzupassen.
Die unterstützten eingebauten Richtlinieneffekte sind:
Richtlinien Effekt | Bedeutung | Beispiel |
---|---|---|
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) || deny | priority | Priority |
subjectPriority(p.eft) | Priorität basierend auf Rolle | Subject-Priority |
Matchers
[matchers]
ist die Definition für Policy-Matcher. Die Matcher sind Ausdrücke, die definieren, wie die Policy-Regeln gegen die Anfrage ausgewertet werden.
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
Der oben genannte Matcher ist der einfachste und bedeutet, dass das Subjekt, das Objekt und die Aktion in einer Anfrage mit denen in einer Policy-Regel übereinstimmen sollten.
In Matchern können arithmetische Operatoren wie +, -, *, /
und logische Operatoren wie &&, ||, !
verwendet werden.
Reihenfolge der Ausdrücke in Matchern
Die Reihenfolge der Ausdrücke kann die Leistung erheblich beeinflussen. Schauen Sie sich das folgende Beispiel für weitere Details an:
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
}
Die Durchsetzungszeit kann sehr lang sein, bis zu 6 Sekunden.
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
Wenn wir jedoch die Reihenfolge der Ausdrücke in Matchern anpassen und zeitaufwändigere Ausdrücke wie Funktionen nach hinten stellen, wird die Ausführungszeit sehr kurz sein.
Ändern der Reihenfolge der Ausdrücke in Matchern im obigen Beispiel zu:
[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
Mehrere Abschnittstypen
Wenn Sie mehrere Policy-Definitionen oder mehrere Matcher benötigen, können Sie p2
oder m2
als Beispiele verwenden. Tatsächlich können alle vier oben genannten Abschnitte mehrere Typen verwenden, und die Syntax ist r
gefolgt von einer Zahl, wie r2
oder e2
. Standardmäßig sollten diese vier Abschnitte eins zu eins entsprechen. Zum Beispiel wird Ihr Abschnitt r2
nur den Matcher m2
verwenden, um p2
-Richtlinien abzugleichen.
Sie können einen EnforceContext
als ersten Parameter der enforce
-Methode übergeben, um die Typen anzugeben. Der EnforceContext
ist wie folgt definiert:
- 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;
}
}
Hier ist ein Beispiel für die Verwendung. Bitte beziehen Sie sich auf das Modell und die Richtlinie. Die Anfrage ist wie folgt:
- 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
Spezielle Grammatik
Sie könnten auch den Operator "in" verwenden, der der einzige Operator mit einem Textnamen ist. Dieser Operator überprüft das Array auf der rechten Seite, um zu sehen, ob es einen Wert enthält, der dem Wert auf der linken Seite entspricht. Die Gleichheit wird durch Verwendung des == Operators bestimmt, und diese Bibliothek überprüft nicht die Typen zwischen den Werten. Solange zwei Werte in interface{} umgewandelt werden können und immer noch auf Gleichheit mit == überprüft werden können, werden sie wie erwartet funktionieren. Beachten Sie, dass Sie einen Parameter für das Array verwenden können, aber es muss ein []interface{} sein.
Siehe auch rbac_model_matcher_using_in_op, keyget2_model und keyget_model.
Beispiel:
[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"}})
Ausdrucksbewerter
Die Matcher-Bewertung in Casbin wird durch Ausdrucksbewerter in jeder Sprache implementiert. Casbin integriert ihre Kräfte, um die einheitliche PERM-Sprache bereitzustellen. Zusätzlich zur hier bereitgestellten Modellsyntax können diese Ausdrucksauswerter zusätzliche Funktionen bieten, die möglicherweise nicht von einer anderen Sprache oder Implementierung unterstützt werden. Bitte seien Sie vorsichtig bei der Verwendung dieser Funktion.
Die von jeder Casbin-Implementierung verwendeten Ausdrucksauswerter sind wie folgt:
Implementierung | Sprache | Ausdrucksauswerter |
---|---|---|
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 | Rost | https://github.com/jonathandturner/rhai |
casbin-cpp | C++ | https://github.com/ArashPartow/exprtk |
Wenn Sie auf ein Leistungsproblem mit Casbin stoßen, wird dies wahrscheinlich durch die geringe Effizienz des Ausdrucksauswerters verursacht. Sie können das Problem an Casbin oder den Ausdrucksauswerter direkt zur Beratung zur Beschleunigung der Leistung richten. Für weitere Details verweisen Sie bitte auf den Abschnitt Benchmarks.