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. Así pues 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. Incluso me era suficiente con emplear el sistema de autenticación básico y nativo del framework. Pues esta vez no, si o si precisaba una solución un poco más avanzada.

En seguida, comencé a investigar, casi todas las opciones disponibles no terminaban de convencerme. Como resultado me brindaban excesivas opciones que nunca iba a utilizar o directamente no funcionaban en Laravel en su quinta versión. En ese momento encontré Roles, esto es 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 es un sistema de permisos y roles que hace todo lo que promete y más. Desde mi punto de vista es muy fácil de incorporar a cualquier tipo de proyecto y que, por cierto, 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 con el objeto de 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

De tal modo corremos la migración:

php artisan migrate

Unas aclaraciones: con el propósito 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

Como decíamos, 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;

De tal modo 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, así y todo, lo correcto seria hacerlo desde algún seed. Esta creación es por cierto 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 tal modo es sencillo hacerlo.

Asignación de Roles

Hasta aquí, supongamos que ya tenemos creados nuestros roles, con el propósito de asignarlos 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 aún más sencillo que generar el objeto de role completo cada vez que tenia que hacer una asignación.

De pronto 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 bien vamos a la parte más divertida, comprobaremos directamente si un usuario tiene un role determinado. En seguida para realizar dicha comprobación podemos escribir:

if ($user->is('admin')) { 
	// Cuenta con el role admin
}

Siempre y cuando comprobamos si un usuario tiene determinado rol podemos pasarle como parámetro el slug (valor que le dimos al momento de crearlo) o bien directamente el ID del role.

De pronto 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

Así pues puede ocurrir que necesitamos comprobar el nivel de un usuario dependiendo de como planteemos nuestro proyecto puede ser una alternativa útil. De tal modo simplemente hacemos:

if ($user->level() > 2) {
}

Creando permisos

Ahora bien, ya aprendimos a crear roles, ahora bien, 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',
]);

En este sentido, tanto la forma de crear como la de asignar es muy similar a la metodología empleada para hacer las mismas acciones en los roles. Ahora bien, en el ejemplo anterior, presente en la documentación oficial, pueden ver que simplemente vamos a necesitar primero que todo un nombre y un slug, más adelante tendremos que asignar:

use App\User;
use Bican\Roles\Models\Role;

$role = Role::find($roleId);
$role->attachPermission($createUsersPermission); 
// En este sentido asignamos un permiso a determinado Role

$user = User::find($userId);
$user->attachPermission($deleteUsersPermission); 
// De tal modo asignamos un permiso a un usuario

En este sentido 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 práctico, ya que hay veces que simplemente queremos que un usuario puede realizar determinada actividad sin más. Pongamos por caso 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.

Por cierto, 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') { 

}

Así pues, se puede pasar tanto el slug como el id del permiso que queremos comprobar.

Hasta aquí Roles cuenta con distintos métodos para realizar las distintas comprobaciones según sea necesario, por ejemplo: CanOne, canAll o hasPermission. Ahora bien, en la documentación van a encontrar una descripción sobre su utilización.

Comprobaciones desde Blade

Por cierto, algo que me parece interesante de este proyecto es que se pueden realizar comprobaciones directamente desde el motor de templates. Incluso saber si un usuario tiene permisos o se encuentra categorizado en determinado rol, un escenario podría ser mostrar opciones de un menú según el rango del usuario.

Por lo que 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'))
// Así pues 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'))
// Así pues el usuario es administrador y moderador
@else
// contrariamente
@endrole

Middleware

Como decíamos, 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. Así pues, 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,
];

Por cierto 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',
]);

Finalmente, 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.

Categorizado en: