Syntax for Models
Uma configuração de modelo (CONF) deve ter pelo menos quatro seções:
[request_definition]
,[policy_definition]
,[policy_effect]
e[matchers]
.Se um modelo usa Controle de Acesso Baseado em Funções (RBAC), ele também deve incluir a seção
[role_definition]
.Uma configuração de modelo (CONF) pode conter comentários. Comentários começam com o símbolo
#
, e tudo após o símbolo#
será comentado.
Definição de Requisição
A seção [request_definition]
define os argumentos na função e.Enforce(...)
.
[request_definition]
r = sub, obj, act
Neste exemplo, sub
, obj
e act
representam o clássico triplo de acesso: o sujeito (entidade que acessa), o objeto (recurso acessado) e a ação (método de acesso). No entanto, você pode personalizar seu próprio formato de requisição. Por exemplo, você pode usar sub, act
se não precisar especificar um recurso em particular, ou sub, sub2, obj, act
se você tiver duas entidades acessando.
Definição de Política
A [policy_definition]
é a definição para uma política. Ela define o significado da política. Por exemplo, temos o seguinte modelo:
[policy_definition]
p = sub, obj, act
p2 = sub, act
E temos a seguinte política (se em um arquivo de política):
p, alice, data1, read
p2, bob, write-all-objects
Cada linha em uma política é chamada de regra de política. Cada regra de política começa com um tipo de política
, como p
ou p2
. É usado para combinar a definição de política se houver múltiplas definições. A política acima mostra a seguinte vinculação. A vinculação pode ser usada no comparador.
(alice, data1, read) -> (p.sub, p.obj, p.act)
(bob, write-all-objects) -> (p2.sub, p2.act)
Os elementos em uma regra de política são sempre considerados como strings
. Se você tiver alguma dúvida sobre isso, por favor, consulte a discussão em: https://github.com/casbin/casbin/issues/113
Efeito de Política
[policy_effect]
é a definição para o efeito da política. Determina se a solicitação de acesso deve ser aprovada se várias regras de política corresponderem à solicitação. Por exemplo, uma regra permite e a outra nega.
[policy_effect]
e = some(where (p.eft == allow))
O efeito de política acima significa que se houver qualquer regra de política correspondente de allow
, o efeito final é allow
(também conhecido como sobrescrita de permissão). p.eft
é o efeito para uma política, e pode ser allow
ou deny
. É opcional, e o valor padrão é allow
. Como não especificamos acima, ele usa o valor padrão.
Outro exemplo para o efeito de política é:
[policy_effect]
e = !some(where (p.eft == deny))
Isto significa que se não houver regras de política correspondentes de deny
, o efeito final é allow
(também conhecido como sobrescrita de negação). some
significa que existe uma regra de política correspondente. any
significa que todas as regras de política correspondentes (não usado aqui). O efeito de política pode até ser conectado com expressões lógicas:
[policy_effect]
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
Isto significa que deve haver pelo menos uma regra de política correspondente de allow
, e não pode haver nenhuma regra de política correspondente de deny
. Portanto, desta forma, ambas as autorizações de permitir e negar são suportadas, e a negação sobrepõe.
Embora tenhamos projetado a sintaxe do efeito de política como acima, as implementações atuais usam apenas efeitos de política codificados. Isto é porque descobrimos que não há muita necessidade para esse nível de flexibilidade. Então, por enquanto, você deve usar um dos efeitos de política integrados em vez de personalizar o seu próprio.
Os efeitos de política integrados suportados são:
Efeito de Política | Significado | Exemplo |
---|---|---|
some(where (p.eft == allow)) | sobrescrita de permissão | ACL, RBAC, etc. |
!some(where (p.eft == deny)) | sobrescrita de negação | Sobrescrita de negação |
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) | prioridade baseada em papel | Subject-Priority |
Matchers
[matchers]
é a definição para os correspondentes de política. Os correspondentes são expressões que definem como as regras de política são avaliadas contra a solicitação.
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
O correspondente acima é o mais simples e significa que o sujeito, objeto e ação em uma solicitação devem corresponder aos da regra de política.
Operadores aritméticos como +, -, *, /
e operadores lógicos como &&, ||, !
podem ser usados nos correspondentes.
Ordem das expressões nos correspondentes
A ordem das expressões pode afetar muito o desempenho. Dê uma olhada no seguinte exemplo para mais detalhes:
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
}
O tempo de execução pode ser muito longo, até 6 segundos.
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
No entanto, se ajustarmos a ordem das expressões nos correspondentes e colocarmos expressões mais demoradas como funções para trás, o tempo de execução será muito curto.
Mudando a ordem das expressões nos correspondentes no exemplo acima para:
[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
Tipos de Seção Múltiplos
Se você precisar de múltiplas definições de política ou múltiplos correspondentes, você pode usar p2
ou m2
como exemplos. Na verdade, todas as quatro seções mencionadas acima podem usar múltiplos tipos, e a sintaxe é r
seguido por um número, como r2
ou e2
. Por padrão, essas quatro seções devem corresponder um-a-um. Por exemplo, sua seção r2
só usará o correspondente m2
para combinar com políticas p2
.
Você pode passar um EnforceContext
como o primeiro parâmetro do método enforce
para especificar os tipos. O EnforceContext
é definido da seguinte forma:
- 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;
}
}
Aqui está um exemplo de uso. Por favor, consulte o modelo e a política. A solicitação é a seguinte:
- 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
Gramática Especial
Você também pode usar o operador "in", que é o único operador com um nome de texto. Este operador verifica o array do lado direito para ver se contém um valor igual ao valor do lado esquerdo. A igualdade é determinada usando o operador ==, e esta biblioteca não verifica os tipos entre os valores. Contanto que dois valores possam ser convertidos para interface{} e ainda possam ser verificados para igualdade com ==, eles funcionarão conforme o esperado. Observe que você pode usar um parâmetro para o array, mas ele deve ser um []interface{}.
Consulte também rbac_model_matcher_using_in_op, keyget2_model, e keyget_model.
Exemplo:
[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"}})
Avaliador de Expressão
A avaliação de correspondentes no Casbin é implementada por avaliadores de expressão em cada linguagem. Casbin integra seus poderes para fornecer a linguagem PERM unificada. Além da sintaxe do modelo fornecida aqui, esses avaliadores de expressão podem oferecer funcionalidades extras que podem não ser suportadas por outra linguagem ou implementação. Por favor, seja cauteloso ao usar essa funcionalidade.
Os avaliadores de expressão usados por cada implementação do Casbin são os seguintes:
Implementação | Linguagem | Avaliador de Expressão |
---|---|---|
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 você encontrar um problema de desempenho com o Casbin, é provável que seja causado pela baixa eficiência do avaliador de expressões. Você pode endereçar o problema ao Casbin ou ao avaliador de expressões diretamente para obter conselhos sobre como acelerar o desempenho. Para mais detalhes, por favor, consulte a seção Benchmarks.