Syntax for Models
Una configurazione del modello (CONF) dovrebbe avere almeno quattro sezioni:
[request_definition]
,[policy_definition]
,[policy_effect]
, e[matchers]
.Se un modello utilizza il Controllo degli Accessi Basato sui Ruoli (RBAC), dovrebbe includere anche la sezione
[role_definition]
.Una configurazione del modello (CONF) può contenere commenti. I commenti iniziano con il simbolo
#
, e tutto ciò che segue il simbolo#
sarà commentato.
Definizione della richiesta
La sezione [request_definition]
definisce gli argomenti nella funzione e.Enforce(...)
.
[request_definition]
r = sub, obj, act
In questo esempio, sub
, obj
, e act
rappresentano il classico accesso triplo: il soggetto (entità che accede), l'oggetto (risorsa acceduta) e l'azione (metodo di accesso). Tuttavia, puoi personalizzare il formato della tua richiesta. Ad esempio, puoi utilizzare sub, act
se non hai bisogno di specificare una risorsa particolare, oppure sub, sub2, obj, act
se hai due entità di accesso.
Definizione della Politica
Il [policy_definition]
è la definizione di una politica. Definisce il significato della politica. Ad esempio, abbiamo il seguente modello:
[policy_definition]
p = sub, obj, act
p2 = sub, act
E abbiamo la seguente politica (se in un file di politica):
p, alice, data1, read
p2, bob, write-all-objects
Ogni riga in una politica è chiamata regola di politica. Ogni regola di politica inizia con un tipo di politica
, come p
o p2
. Viene utilizzato per abbinare la definizione della politica se ci sono più definizioni. La politica sopra indicata mostra il seguente vincolo. Il vincolo può essere utilizzato nel matcher.
(alice, data1, read) -> (p.sub, p.obj, p.act)
(bob, write-all-objects) -> (p2.sub, p2.act)
Gli elementi in una regola di politica sono sempre considerati come stringhe
. Se avete domande al riguardo, consultate la discussione al seguente link: https://github.com/casbin/casbin/issues/113
Effetto Politica
[policy_effect]
è la definizione per l'effetto della politica. Determina se la richiesta di accesso deve essere approvata se più regole di politica corrispondono alla richiesta. Ad esempio, una regola permette e l'altra nega.
[policy_effect]
e = some(where (p.eft == allow))
L'effetto della politica sopra indicato significa che se c'è una qualsiasi regola di politica corrispondente di allow
, l'effetto finale è allow
(noto anche come allow-override). p.eft
è l'effetto per una politica, e può essere allow
o deny
. È facoltativo e il valore predefinito è allow
. Poiché non l'abbiamo specificato sopra, utilizza il valore predefinito.
Un altro esempio per l'effetto della politica è:
[policy_effect]
e = !some(where (p.eft == deny))
Ciò significa che se non ci sono regole di politica abbinate di deny
, l'effetto finale è allow
(noto anche come deny-override). some
significa che esiste almeno una regola di politica abbinata. any
significa che tutte le regole di politica abbinate (non utilizzate qui). L'effetto della politica può persino essere collegato con espressioni logiche:
[policy_effect]
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
Ciò significa che deve esserci almeno una regola di politica abbinata di allow
e non ci deve essere alcuna regola di politica abbinata di deny
. Pertanto, in questo modo, sono supportate sia le autorizzazioni allow che deny, e il deny prevale.
Sebbene abbiamo progettato la sintassi dell'effetto della politica come sopra, le implementazioni attuali utilizzano solo effetti di politica hard-coded. Questo è perché abbiamo scoperto che non c'è molta necessità per quel livello di flessibilità. Quindi, per ora, devi utilizzare uno degli effetti di policy incorporati invece di personalizzare il tuo.
Gli effetti di policy incorporati supportati sono:
Effetto di Policy | Significato | Esempio |
---|---|---|
some(where (p.eft == allow)) | allow-override | ACL, RBAC, ecc. |
!some(where (p.eft == deny)) | negazione-sovraordinata | Negazione-sovraordinata |
alcuni(dove (p.eft == allow)) && !alcuni(dove (p.eft == deny)) | allow-e-deny | Allow-e-deny |
priorità(p.eft) || deny | priorità | Priorità |
prioritàSoggetto(p.eft) | priorità basata sul ruolo | Priorità-Soggetto |
Corrisponditori
[corrisponditori]
è la definizione per i corrisponditori di policy. I corrisponditori sono espressioni che definiscono come le regole di policy vengono valutate rispetto alla richiesta.
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
Il corrisponditore sopra è il più semplice e significa che il soggetto, l'oggetto e l'azione in una richiesta devono corrispondere a quelli in una regola di policy.
Operatori aritmetici come +, -, *, /
e operatori logici come &&, ||, !
possono essere utilizzati nei corrisponditori.
Ordine delle espressioni nei corrisponditori
L'ordine delle espressioni può influenzare notevolmente le prestazioni. Dai un'occhiata all'esempio seguente per maggiori dettagli:
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
}
Il tempo di applicazione potrebbe essere molto lungo, fino a 6 secondi.
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
Tuttavia, se modifichiamo l'ordine delle espressioni nei matcher e mettiamo le espressioni più dispendiose in termini di tempo, come le funzioni, dietro, il tempo di esecuzione sarà molto breve.
Cambiando l'ordine delle espressioni nei matcher nell'esempio sopra a:
[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
Tipi di Sezione Multipli
Se hai bisogno di definizioni di policy multiple o matcher multipli, puoi usare p2
o m2
come esempi. In effetti, tutte e quattro le sezioni menzionate sopra possono utilizzare tipi multipli, e la sintassi è r
seguito da un numero, come r2
o e2
. Di default, queste quattro sezioni dovrebbero corrispondere uno-a-uno. Ad esempio, la tua sezione r2
utilizzerà solo il matcher m2
per abbinare le policy p2
.
Puoi passare un EnforceContext
come primo parametro del metodo enforce
per specificare i tipi. L'EnforceContext
è definito come segue:
- 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;
}
}
Ecco un esempio di utilizzo. Si prega di fare riferimento al modello e alla policy. La richiesta è la seguente:
- 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
Grammatica Speciale
Potresti anche utilizzare l'operatore "in", che è l'unico operatore con un nome testuale. Questo operatore verifica l'array sul lato destro per vedere se contiene un valore uguale al valore sul lato sinistro. L'uguaglianza è determinata utilizzando l'operatore ==, e questa libreria non verifica i tipi tra i valori. Fintanto che due valori possono essere convertiti in interface{} e possono ancora essere verificati per l'uguaglianza con ==, si comporteranno come previsto. Nota che puoi utilizzare un parametro per l'array, ma deve essere un []interface{}.
Fare anche riferimento a rbac_model_matcher_using_in_op, keyget2_model, e keyget_model.
Esempio:
[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"}})
Valutatore di Espressioni
La valutazione del matcher in Casbin è implementata da valutatori di espressioni in ogni linguaggio. Casbin integra i loro poteri per fornire il linguaggio PERM unificato. Oltre alla sintassi del modello fornita qui, questi valutatori di espressioni potrebbero offrire funzionalità extra che potrebbero non essere supportate da un altro linguaggio o implementazione. Si prega di fare attenzione quando si utilizza questa funzionalità.
I valutatori di espressioni utilizzati da ogni implementazione di Casbin sono i seguenti:
Implementazione | Linguaggio | Valutatore di Espressioni |
---|---|---|
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 |
Se riscontri un problema di prestazioni con Casbin, è probabile che sia causato dall'inefficienza dell'evaluator di espressioni. Puoi risolvere il problema contattando direttamente Casbin o l'evaluator di espressioni per consigli su come migliorare le prestazioni. Per maggiori dettagli, consulta la sezione Benchmark.