Syntax for Models
Una configuración de modelo (CONF) debe tener al menos cuatro secciones:
[request_definition]
,[policy_definition]
,[policy_effect]
, y[matchers]
.Si un modelo utiliza Control de Acceso Basado en Roles (RBAC), también debe incluir la sección
[role_definition]
.Una configuración de modelo (CONF) puede contener comentarios. Los comentarios comienzan con el símbolo
#
, y todo después del símbolo#
será comentado.
Definición de solicitud
La sección [request_definition]
define los argumentos en la función e.Enforce(...)
.
[request_definition]
r = sub, obj, act
En este ejemplo, sub
, obj
y act
representan el trío clásico de acceso: el sujeto (entidad que accede), el objeto (recurso accedido) y la acción (método de acceso). Sin embargo, puedes personalizar tu propio formato de solicitud. Por ejemplo, puedes usar sub, act
si no necesitas especificar un recurso particular, o sub, sub2, obj, act
si tienes dos entidades que acceden.
Definición de Política
La [policy_definition]
es la definición de una política. Define el significado de la política. Por ejemplo, tenemos el siguiente modelo:
[policy_definition]
p = sub, obj, act
p2 = sub, act
Y tenemos la siguiente política (si está en un archivo de política):
p, alice, data1, read
p2, bob, write-all-objects
Cada línea en una política se llama regla de política. Cada regla de política comienza con un tipo de política
, como p
o p2
. Se utiliza para coincidir con la definición de política si hay múltiples definiciones. La política anterior muestra la siguiente vinculación. La vinculación se puede utilizar en el comparador.
(alice, data1, read) -> (p.sub, p.obj, p.act)
(bob, write-all-objects) -> (p2.sub, p2.act)
Los elementos en una regla de política siempre se consideran cadenas de texto
. Si tienes alguna pregunta sobre esto, por favor consulta la discusión en: https://github.com/casbin/casbin/issues/113
Efecto de Política
[policy_effect]
es la definición para el efecto de la política. Determina si la solicitud de acceso debe ser aprobada si múltiples reglas de política coinciden con la solicitud. Por ejemplo, una regla permite y la otra niega.
[policy_effect]
e = some(where (p.eft == allow))
El efecto de política anterior significa que si hay alguna regla de política coincidente de allow
, el efecto final es allow
(también conocido como permitir-sobrescribir). p.eft
es el efecto para una política, y puede ser allow
o deny
. Es opcional, y el valor predeterminado es allow
. Dado que no lo especificamos anteriormente, utiliza el valor predeterminado.
Otro ejemplo para el efecto de la política es:
[policy_effect]
e = !some(where (p.eft == deny))
Esto significa que si no hay reglas de política coincidentes de deny
, el efecto final es allow
(también conocido como negar-sobrescribir). some
significa que existe una regla de política coincidente. any
significa que todas las reglas de política coincidentes (no se usa aquí). El efecto de la política incluso puede estar conectado con expresiones lógicas:
[policy_effect]
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
Esto significa que debe haber al menos una regla de política coincidente de allow
, y no puede haber ninguna regla de política coincidente de deny
. Por lo tanto, de esta manera, se admiten tanto las autorizaciones de permitir como de negar, y la negación prevalece.
Aunque diseñamos la sintaxis del efecto de la política como se muestra arriba, las implementaciones actuales solo usan efectos de política codificados. Esto se debe a que encontramos que no hay mucha necesidad de ese nivel de flexibilidad. Así que por ahora, debes usar uno de los efectos de política integrados en lugar de personalizar el tuyo propio.
Los efectos de política integrados admitidos son:
Efecto de Política | Significado | Ejemplo |
---|---|---|
some(where (p.eft == allow)) | permitir-sobrescribir | ACL, RBAC, etc. |
!some(where (p.eft == deny)) | negar-sobrescribir | Negar-sobrescribir |
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) | prioridad basada en el rol | Subject-Priority |
Matchers
[matchers]
es la definición para los comparadores de políticas. Los comparadores son expresiones que definen cómo se evalúan las reglas de políticas contra la solicitud.
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
El comparador anterior es el más simple y significa que el sujeto, objeto y acción en una solicitud deben coincidir con los de una regla de política.
Operadores aritméticos como +, -, *, /
y operadores lógicos como &&, ||, !
pueden usarse en los comparadores.
Orden de las expresiones en los comparadores
El orden de las expresiones puede afectar enormemente el rendimiento. Echa un vistazo al siguiente ejemplo para más detalles:
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
}
El tiempo de aplicación puede ser muy largo, hasta 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
Sin embargo, si ajustamos el orden de las expresiones en los comparadores y ponemos expresiones que consumen más tiempo como funciones detrás, el tiempo de ejecución será muy corto.
Cambiando el orden de las expresiones en los comparadores en el ejemplo anterior 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
Tipos de Sección Múltiples
Si necesitas múltiples definiciones de políticas o múltiples comparadores, puedes usar p2
o m2
como ejemplos. De hecho, las cuatro secciones mencionadas anteriormente pueden usar múltiples tipos, y la sintaxis es r
seguido de un número, como r2
o e2
. Por defecto, estas cuatro secciones deben corresponder uno a uno. Por ejemplo, tu sección r2
solo usará el comparador m2
para coincidir con las políticas p2
.
Puedes pasar un EnforceContext
como el primer parámetro del método enforce
para especificar los tipos. El EnforceContext
se define de la siguiente manera:
- 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;
}
}
Aquí hay un ejemplo de uso. Por favor, consulta el modelo y la política. La solicitud es la siguiente:
- 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
También podrías usar el operador "in", que es el único operador con un nombre de texto. Este operador verifica el arreglo en el lado derecho para ver si contiene un valor que es igual al valor del lado izquierdo. La igualdad se determina usando el operador ==, y esta biblioteca no verifica los tipos entre los valores. Mientras dos valores se puedan convertir a interface{} y todavía se puedan verificar para la igualdad con ==, actuarán como se espera. Ten en cuenta que puedes usar un parámetro para el arreglo, pero debe ser un []interface{}.
También consulta rbac_model_matcher_using_in_op, keyget2_model, y keyget_model.
Ejemplo:
[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"}})
Evaluador de Expresiones
La evaluación de comparadores en Casbin se implementa mediante evaluadores de expresiones en cada idioma. Casbin integra sus poderes para proporcionar el lenguaje PERM unificado. Además de la sintaxis del modelo proporcionada aquí, estos evaluadores de expresiones pueden ofrecer funcionalidades adicionales que podrían no ser compatibles con otro idioma o implementación. Por favor, ten precaución al usar esta funcionalidad.
Los evaluadores de expresiones utilizados por cada implementación de Casbin son los siguientes:
Implementación | Idioma | Evaluador de Expresiones |
---|---|---|
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 |
Si encuentras un problema de rendimiento con Casbin, es probable que sea causado por la baja eficiencia del evaluador de expresiones. Puedes dirigir el problema a Casbin o al evaluador de expresiones directamente para obtener consejos sobre cómo acelerar el rendimiento. Para más detalles, por favor consulta la sección Benchmarks.