Autorización flexible mediante Casbin y PERM. Un ejemplo practico

Después de escribir el artículo anterior sobre el lenguaje PERM y la biblioteca Casbin , surgieron preguntas . Y no solo una persona, y quería responder primero en un comentario, pero me di cuenta de que el volumen del material va más allá del comentario habitual, por lo que presentaré esta respuesta en forma de artículo aparte.







Durante mucho tiempo no pude entender la construcción específica que se sentó en la cabeza de los interrogadores, pero al final, después de aclarar preguntas, recibí respuestas, cuya recopilación daré en la cita.







¿Y cómo con este tipo de DSL se resuelve el problema “mostrar la lista de objetos que puedo ver? Es necesario traducir esto de alguna manera en una consulta SQL, no sacar todos los registros de la base de datos.



Hay una interfaz en el sitio que muestra una lista de algo. Digamos: artículos en el área de administración de CMS. Hay decenas de miles de artículos en la base de datos, pero normalmente el usuario solo tiene acceso a una docena. ¿Cómo obtener artículos de la base de datos que sean visibles para un usuario específico? Bueno, si tenemos todas las reglas que cualquiera puede ver, ¿sacadas del código en algún tipo de DSL?

En otras palabras, cómo escribir una consulta como



seleccionar * de los artículos,

unir roles r en r.userId = currentUserId

donde article.owner = currentUserId

OR (r.role en ['admin', 'supevisor']) - administrador del total

OR (r.domain = currentDomainId AND r.role in ['domain-admin', 'domain-supervisor']) - administrador del dominio



Tengo tales reglas en el código, en forma de expresiones LINQ, y puedo resolver este problema. Y tal tarea surge incluso con más frecuencia que "verificar si hay acceso a un objeto descargado de la memoria"

Espero haber entendido esta construcción correctamente y durante la ingeniería inversa pude extraer los datos iniciales para resolver este problema. Para empezar, prescindiremos del uso de multi-tenancy (dominios), ya que complican la tarea y, en consecuencia, la comprensión. Di un ejemplo de su uso en el último artículo.







, , , Casbin.









CMS, . user



.       , admin



supervisor



. supervisor



, admin



supervisor



, , .







, :



CMS:

Esquema de tabla de base de datos







Users:

Contenido de la tabla de usuarios







:

Roles:

Contenido de la tabla de roles







, Piter , Bob — . Alice , , .







Articles:

Contenido de la tabla de artículos







, (Piter, id=3) :







select * from articles a
left join roles r on r.userId = 3
where a.owner = 3
OR (r.role in ('admin', 'supevisor'))
      
      





Resultado de muestra para administrador







(Bob, id=2) :







select * from articles a
left join roles r on r.userId = 2
where a.owner = 2
OR (r.role in ('admin', 'supevisor'))
      
      





Resultado de muestra para supervisor







(Alice, id=1) :







select * from articles a
left join roles r on r.userId = 1
where a.owner = 1
OR (r.role in ('admin', 'supevisor'))
      
      





Resultado de muestra para el usuario







, Casbin.







Casbin



PERM — , .

.. , () . ( Id=1 ).







, , — RBAC.

RBAC , . RBAC user



, author



user



(.. ), , admin



.

, , . user



supervisor



admin



, — , . , user



, . admin



supervisor



, .

RBAC, , -, , .

: RBAC vs. ABAC







, (user



, supervisor



,admin



) — —



. , . , , — .







" "



RBAC (rbac_model.conf



), :







[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
      
      





, Roles. . *.csv



, . cvs rbac_policy.csv



:







p, user, article, read
p, user, article, modify
p, user, article, create
p, user, article, delete

g, supervisor, user
g, admin, supervisor

g, 1, user
g, 2, supervisor
g, 3, admin
      
      





, user



, , . supervisor



user.



admin



supervisor



.

alice(1) user



, bob(2) supervisor



, piter(3) — admin



.

, , .







, . cross-cutting concern CQRS+MediatR







public IList<Article> GetArticlesForAdminPanel(int currentUserId)
{
    var e = new Enforcer("CasbinConfig/rbac_model.conf", "CasbinConfig/rbac_policy.csv");

    var obj = "article";
    var act = "read";

    // ,       
    if (e.Enforce(currentUserId.ToString(), obj, act))
    {
        //   
        var currentUserRoles = e.GetRolesForUser(currentUserId.ToString());
        //,      
        var isAdmin = currentUserRoles.Any(x => x == "admin" || x == "supervisor");

        // ,   ,   ,   
        if (!isAdmin) return _context.Articles.Where(x => x.OwnerId == currentUserId).ToList();
        else return _context.Articles.ToList();
    }
    else
    {
        //  ,  
        throw new Exception("403.       ");   
    }
}
      
      





! , .







" "



. , , - . , , user



, supervisor



admin



.







, rbac_with_abac_model.conf



:







[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 = (r.sub == r.obj.OwnerId.ToString() || g(r.sub, "supervisor")) && g(r.sub, p.sub) && r.act == p.act
      
      





, [matchers]



, r.obj == p.obj



(r.sub == r.obj.OwnerId.ToString() || g(r.sub, "supervisor"))



. r.sub (id ) r.obj.OwnerId (id ) r.sub "supervisor". admin



supervisor



admin



.







, . :







public void UpdateArticle(int currentUserId, Article newArticle)
{
    var e = new Enforcer("CasbinConfig/rbac_with_abac_model.conf", "CasbinConfig/rbac_policy.csv");

    var act = "modify";

    //,       
    if (e.Enforce(currentUserId.ToString(), newArticle, act))
    {
        //,   
        _context.Articles.Update(newArticle);
        _context.SaveChanges();
    }
    else
    {
        //  ,  
        throw new Exception("403.  ");
    }
}
      
      





, e.Enforce



, Article



.







— .









- , user



, supervisor



— , admin



.

- PERM, delete_model.conf



:







[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 = (r.sub == r.obj.OwnerId.ToString() || g(r.sub, "admin")) && g(r.sub, p.sub) && r.act == p.act
      
      





, , admin



. supervisor



, .







, :







public void DeleteArticle(int currentUserId, Article deleteArticle)
{
    var e = new Enforcer("CasbinConfig/delete_model.conf", "CasbinConfig/rbac_policy.csv");

    var act = "delete";

    //,       
    if (e.Enforce(currentUserId.ToString(), deleteArticle, act))
    {
        // 
        _context.Articles.Remove(deleteArticle);
        _context.SaveChanges();
    }
    else
    {
        //  ,  
        throw new Exception("403.  ");
    }
}
      
      







, , Casbin PERM .







, , . , , .







Casbin DynamicExpresso.Core C# , Casbin .







, Casbin , , API. UI .







Publiqué un código de ejemplo completamente funcional y autosuficiente que usé para escribir este artículo en mi Github , puedes descargarlo y jugar si estás interesado y dispuesto.







Enlaces útiles






All Articles