domingo, 10 de enero de 2016

Roles y permisos para Laravel 5



Hace unos días me encontré con la necesidad de agregar roles y permisos a una pequeña aplicación que estaba desarrollando con Laravel 5. En linea general y hasta el momento nunca había tenido la necesidad de incorporar este tipo de características a una aplicación mía basada en Laravel, hasta el momento me era suficiente con emplear el sistema de autenticación básico y nativo del framework, pero esta vez no, si o si precisaba una solución un poco más avanzada.
Comencé a investigar, casi todas las opciones disponibles no terminaban de convencerme, o me brindaban excesivas opciones que nunca iba a utilizar o directamente no funcionaban en Laravel en su quinta versión. Hasta que finalmente encontré Roles, un proyecto desarrollado por un programador llamado Roman Bičan que funciona excelente con L5, además de ser extremadamente fácil de utilizar.
guía permisos y roles para laravel 5
Roles cuenta es un sistema de permisos y roles que hace todo lo que promete y más, es muy fácil de incorporar a cualquier tipo de proyecto y se puede tener funcionando en cuestión de minutos.
Un aspecto interesante de esta iniciativa es que el autor diseño el proyecto para funcionar desde Laravel versión 5.0, sin pasar por las versiones anteriores, una ventaja si lo que buscamos es emplear una recurso que cubra un abanico de compatibilidades hacia delante y no hacia atrás, como era mi caso.
Desde que uso Laravel puedo decir que el salto más importante que tuve fue en la versión 5, sin ir más lejos la guía de actualización oficial de Laravel deja bien en claro que su recomendación para pasar de la versión 4.2 a la versión 5 es una instalación limpia, nada de modificar algunos archivos, esto habla un poco sobre el cambio radical que existe entre estas versiones y las anteriores, recordemos que previamente se podían migrar de forma más “amena” y parcial.
Roles es probablemente la mejor opción si queremos un sistema que no nos deje de funcionar en las futuras versiones de Laravel. También es interesante ver como la comunidad de Laravel recibió con los brazos abiertos este proyecto, no hace falta más que ver la cantidad de interacciones que tiene este proyecto en Github.

Instalación

La instalación es bien sencilla si usamos composer, no hace falta más que agregar a nuestro archivo composer.json las siguientes lineas:
{
 "require": {
  "php": ">=5.5.9",
  "laravel/framework": "5.1.*",
  "bican/roles": "2.1.*"
 }
}
Por favor presten mucha atención a que la versión mínima de PHP es la 5.5.9, que es la misma que requiere Laravel 5.1. Versión que no todos los hosting tienen, por ejemplo MediaTemple tiene esta versión disponible solo para correr mediante PHP-CGI y no como FPM, detalle que hace que Laravel funcione BASTANTE más lento.
Roles también funciona para Laravel 5.0, simplemente vamos a utilizar la versión 1.7.* en lugar de 2.1.*.
Si todo esta bien actualizamos composer y mientras vamos a buscar un rico café.
composer update
Una vez que composer finalizó y tenemos nuestro café, vamos a sumar un nuevo provider en nuestro archivo de configuración ubicado en: config/app.php
  
'providers' => [

 /*
  * Laravel Framework Service Providers...
  */
 Illuminate\Foundation\Providers\ArtisanServiceProvider::class,
 Illuminate\Auth\AuthServiceProvider::class,
 ...

 /**
  * Third Party Service Providers...
  */
 Bican\Roles\RolesServiceProvider::class,

],
Ahora vamos a correr desde la terminal dos simples comandos, uno va a generar un archivo de configuración y el otro se va a encargar de crear todos los archivos de migración necesarios para insertar todas las tablas necesarias a nuestra base de datos:
php artisan vendor:publish --provider="Bican\Roles\RolesServiceProvider" --tag=config
php artisan vendor:publish --provider="Bican\Roles\RolesServiceProvider" --tag=migrations
Y corremos la migración:
php artisan migrate
Unas aclaraciones: antes de hacer esto ultimo tendríamos que tener creada nuestra tabla por defecto de usuarios.
La otra aclaración es que esta migración va a generar unas cuantas tablas distintas:
  • Roles: Se almacenan todos los roles disponibles, además del nivel que le asignemos a cada uno.
  • Role_user: Se guarda la relación entre usuario y role.
  • Permisos: Siguiendo la misma lógica que los roles se crean 3 tablas distintas (permissions, permission_user y permission_role)
Como verán son unas cuantas tablas pero sencillas, con una lógica muy fácil de comprender y sobretodo de interpretar.

Extendiendo el modelo de Usuario

Para completar la instalación vamos a incorporar estas nuevas funcionalidades al modelo de usuario (User.php) de nuestra aplicación, para ello vamos a agregar estas pocas líneas de código:
use Bican\Roles\Traits\HasRoleAndPermission;
use Bican\Roles\Contracts\HasRoleAndPermission as HasRoleAndPermissionContract;

class User extends Model implements AuthenticatableContract, CanResetPasswordContract, HasRoleAndPermissionContract
{
 use Authenticatable, CanResetPassword, HasRoleAndPermission;
Y con eso terminamos la instalación.

Creación de Roles

Lo primero que vamos a hacer es crear los distintos roles, para ello simplemente hacemos lo siguiente:
use Bican\Roles\Models\Role;

$adminRole = Role::create([
 'name' => 'Admin',
 'slug' => 'admin',
 'description' => '', // optional
 'level' => 1, // optional, set to 1 by default
]);
Esto lo podemos agregar en cualquier controlador, pero lo correcto seria hacerlo desde algún seed, esta creación es más bien una inicialización que una actividad que vamos a realizar de manera frecuente. Salvo que nuestro sistema permita crear roles de forma dinámica. De una u otra manera es sencillo hacerlo.

Asignación de Roles

Supongamos que ya tenemos creados nuestros roles, ahora simplemente debemos asignarlos, para ellos hacemos lo siguiente:
use App\User;

$user = User::find($id);
$user->attachRole($adminRole);
Donde $id es el numero con que identificamos un usuario previamente creado.
attachRole funciona si le pasamos directamente el objeto entero que hace referencia al role, pero también podemos asignar un role pasando su id. Por ejemplo si sabemos que el role Admin tiene como Id el numero 1 podemos hacer lo siguiente:
$user->attachRole(1);
En mi caso esto fue más sencillo que generar el objeto de role completo cada vez que tenia que hacer una asignación.
Ahora si lo que buscamos es eliminar una asignación basta con las líneas:
$user->detachRole($adminRole); 
$user->detachAllRoles();
detachRole funciona igual que attachRole en cuanto al parámetro que podemos pasarle.
Comprobando Roles
Ahora vamos a la parte más divertida, vamos a comprobar directamente si un usuario tiene un role determinado. Para realizar dicha comprobación podemos escribir:
if ($user->is('admin')) { 
 // Cuenta con el role admin
}
Cuando comprobamos si un usuario tiene determinado role podemos pasarle como parámetro el slug (valor que le dimos al momento de crearlo) o bien directamente el ID del role.
Podemos tener la necesidad de comprobar dos o más roles, funciona esto:
if ($user->is('admin|moderator')) {
// El usuario es admin o moderador
}

Comprobando el nivel

También puede ocurrir que necesitamos comprobar el nivel de un usuario, dependiendo de como planteemos nuestro proyecto puede ser una alternativa util, para ello simplemente hacemos:
if ($user->level() > 2) {
}

Creando permisos

Ya aprendimos a crear roles, ahora vamos a ver como se crea un permiso determinado:
use Bican\Roles\Models\Permission;

$createUsersPermission = Permission::create([
 'name' => 'Create users',
 'slug' => 'create.users',
 'description' => '', // optional
]);

$deleteUsersPermission = Permission::create([
 'name' => 'Delete users',
 'slug' => 'delete.users',
]);
Tanto la forma de crear como la de asignar es muy similar a la metologia empleada para hacer las mismas acciones en los roles, en el ejemplo anterior, presente en la documentación oficial, pueden ver que simplemente vamos a necesitar un nombre y un slug, luego tendremos que asignar:
use App\User;
use Bican\Roles\Models\Role;

$role = Role::find($roleId);
$role->attachPermission($createUsersPermission); 
// Asignamos un permiso a determinado Role

$user = User::find($userId);
$user->attachPermission($deleteUsersPermission); 
// asignamos un permiso a un usuario
En este punto vamos a encontrar una funcionalidad util y lógica, la asignación de permisos puede hacerse tanto para un role como para un usuario en particular. Esto es practico, ya que hay veces que simplemente queremos que un usuario puede realizar determinada actividad, por ejemplo, un usuario administrador que pueda borrar a otros administradores pero que no se pueda borrar así mismo, caso que vemos frecuentemente en sistema que permiten generar múltiples perfiles dentro de una organización.
Obviamente como podemos asignar también podemos quitar permisos:
$role->detachPermission($createUsersPermission); 
// Quitamos un permiso determinado a un role
$role->detachAllPermissions(); 
// Quitamos todos los permisos a un role

$user->detachPermission($deleteUsersPermission);
// Quitamos un permiso determinado a un usuario
$user->detachAllPermissions();
// Quitamos todos los permisos a un usuario

Comprobar permisos

if ($user->can('create.users') { 

}
Se puede pasar tanto el slug como el id del permiso que queremos comprobar.
Roles cuenta con distintos métodos para realizar las distintas comprobaciones según sea necesario, por ejemplo: CanOne, canAll o hasPermission. En la documentación van a encontrar una descripción sobre su utilización.

Comprobaciones desde Blade

Algo que me parece interesante de este proyecto es que se pueden realizar comprobaciones directamente desde el motor de templates, por ejemplo saber si un usuario tiene permisos o se encuentra categorizado en determinado role, un escenario podría ser mostrar opciones de un menú según el rango del usuario.
Para realizar las distintas comprobaciones directamente desde blade las opciones disponibles son:
@role('admin') // @if(Auth::check() && Auth::user()->is('admin'))
// Usuario administrador
@endrole

@permission('edit.articles') // @if(Auth::check() && Auth::user()->can('edit.articles'))
// El usuario puede editar
@endpermission

@level(2) // @if(Auth::check() && Auth::user()->level() >= 2)
// Nivel 2 o superior
@endlevel

@allowed('edit', $article) // @if(Auth::check() && Auth::user()->allowed('edit', $article))
//Muestra un botón de edición.
@endallowed

@role('admin|moderator', 'all') // @if(Auth::check() && Auth::user()->is('admin|moderator', 'all'))
//El usuario es administrador y moderador
@else
// caso contrario
@endrole

Middleware

Otro agregado que resulta de mucha utilidad es de realizar las comprobaciones desde las rutas de nuestra aplicación, de forma muy similar a la autorización nativa de Laravel, para lograrlo necesitamos agregar al array routeMiddleware que encontramos dentro del archivo app/Http/Kernel.php
protected $routeMiddleware = [
 'auth' => \App\Http\Middleware\Authenticate::class,
 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
 'role' => \Bican\Roles\Middleware\VerifyRole::class,
 'permission' => \Bican\Roles\Middleware\VerifyPermission::class,
 'level' => \Bican\Roles\Middleware\VerifyLevel::class,
];
Y con eso podremos directamente verificar roles y permisos desde nuestro archivo de rutas:
$router->get('/example', [
 'as' => 'example',
 'middleware' => 'role:admin',
 'uses' => 'ExampleController@index',
]);

$router->post('/example', [
 'as' => 'example',
 'middleware' => 'permission:edit.articles',
 'uses' => 'ExampleController@index',
]);

$router->get('/example', [
 'as' => 'example',
 'middleware' => 'level:2', // level >= 2
 'uses' => 'ExampleController@index',
]);
No tiene demasiada ciencia emplear este fantástico recurso, se instala rápido, se implementa fácil y cumple perfectamente con todas las características provistas por otros recursos similares pero mucho más complejos de utilizar.

publicado originalmente  en: Kabytes

No hay comentarios:

Publicar un comentario