Desarrollo de Aplicaciones

Gestión de roles en Laravel

La creación de roles en un proyecto web con Laravel será esencial para gestionar los permisos y qué acciones puede realizar dentro de la plataforma.

Gestión de roles en Laravel

¿Por qué es importante crear roles en un proyecto web?

Tan importante como distinguir entre usuarios autenticados de visitantes es distinguir entre los usuarios autenticados qué rol o permisos tienen.

Supongamos una página de empresa en la que tenemos un usuario que puede añadir/borrar/modificar miembros del equipo y luego tenemos otro usuario que puede escribir artículos para el blog.

La forma más fácil si solo tenemos estos dos tipos y uno de ellos tiene un rol y los demás tienen otro es añadir en la configuración un listado de emails que validan al usuario con un permiso determinado. Luego en la clase User podemos hacer una función que haga un in_array($this->email, config('administradores', [])). Así sabremos que de este tipo de usuarios se van a hacer uno o dos y no se van a crear más nunca.

Lo que pasa con este sistema es que tiene unas debilidades como, por ejemplo, que esos "administradores" son los que pongamos en código directamente y para cambiarlos hay que modificar un fichero, no podemos crear más desde la aplicación de ninguna forma.

Otra opción mucho "mejor" (la solución anterior en muchos casos es suficiente) sería tener una tabla de roles o permisos y relacionarlos con los usuarios. Imaginemos los diferentes niveles de permiso:

  • Crear un post
  • Editar un post
  • Publicar un post
  • Eliminar un post
  • Crear un miembro del equipo
  • Editar un miembro del equipo
  • Eliminar un miembro del equipo
  • Listar los registros del formulario de contacto

Estos serían los permisos, luego podemos relacionarlos con roles, por ejemplo el bloque de los posts (excepto "publicar") se lo podemos asignar al rol "Escritor", los de miembros del equipo los pueden modificar los miembros de "Secretaría" y luego para abreviar pondremos que hay un admin que todo lo puede.

Así pues tenemos Usuarios que tienen Roles, cada uno con sus Permisos:

class User
{
    public function rol()
    {
        return $this->belongsTo(Rol::class);
    }
}

class Rol
{
    public function users()
    {
        return $this->hasMany(User::class);
    }

    public function permisos()
    {
        return $this->belongsToMany(Permiso::class);
    }
}

class Permiso
{
    public function roles()
    {
        return $this->belongsToMany(Rol::class);
    }
}

Con eso ya sabemos qué permisos (a través de los roles) tiene cada usuario. Luego en el auth service provider definimos estas habilidades:

class AuthServiceProvider
{
    public funciton boot(GateContract $gate)
    {
        $permisos = Permiso::all();

        foreach($permisos as $permiso){

            $gate->define($permiso->name, function($user) use ($permiso){

                return $user->whereHas('rol', function($query) use ($permiso){

                    return $query->whereHas('permisos', function($query) use ($permiso){

                        return $query->whereName($permiso->name);
                    });
                });
            });
        }
    }
}

Con esto estamos definiendo para cada permiso que los usuarios que tengan un rol al que le asignemos ese permiso pasarán esta validación. La query que se realiza debería extraerse a métodos que la simplifiquen. Este $gate->define($clave, $check) nos permite después utilizar el middleware "can:$clave" y así podremos asignárselo a rutas, por ejemplo:

Route::post('posts', 'PostController@store')->middleware('can:crear-posts');

Así solo usuarios con rol Escritor y Admin podrán acceder, ya que es a esos roles a los que les hemos asignado el permiso de 'crear-posts'. Si al middleware le pedimos un $clave que no hayamos definido, la aplicación supondrá que el usuario no tiene ese permiso. Como los permisos pueden llegar a ser muchos (cientos) y "Admin" debe poder hacerlo todo en el AuthServiceProvider@boot podemos añadir lo siguiente:

$gate->before(function($user, $ability){

    if($user->hasRol('admin')){ //esta función habría que definirla

        return true;
    }
});

De esta manera, podemos no añadirle al rol "admin" ningún permiso y los seguirá teniendo todos (si los tiene todos menos uno o dos, la función del before incluye el parámetro $ability así que podremos filtrarlo en ese momento). Otra cosa a tener en cuenta es que el return true lo hacemos dentro de un if en vez de hacer return $user->hasRol('admin') porque si esa función tiene un return false se quedará con ese resultado en vez de continuar mirando los $gate->define().

Comparte este artículo

Artículos Relacionados