Taylor tiene una capacidad de sorprendernos con nuevos features que poseen una interfaz más sencilla de lo que podrías haberte imaginado; y hoy es uno de esos días: con ayuda de Adam Wathan, Laravel estrena un nuevo componente de autorización y políticas de acceso, que te permitirá de una manera increíblemente fácil, bloquear (o permitir) el acceso a ciertas partes de tu aplicación.
Lo mejor es que puedes definirlo con closures o clases y usarlo dentro de los controladores, las plantillas de Blade o cualquier parte de tu sistema.
Setup inicial
Para comenzar a utilizar este feature, crearemos un nuevo proyecto de Laravel, por ejemplo a través de Composer, lo llamaremos «basic-blog»:
composer create-project laravel/laravel basic-blog
Luego vamos a crear un virtual host en Windows o crear un virtual host en Mac o Linux. Yo llamaré al mío «basic.blog» y por supuesto apuntará a la carpeta public/ de este nuevo proyecto.
A continuación creemos un nuevo modelo y migración para almacenar posts:
php artisan make:model Post –migration
En la migración (database/migrations/…create_posts_table.php) vamos a agregar 2 campos a la tabla posts, sólo el título y el usuario asignado:
Schema::create('posts', function (Blueprint $table) { $table->increments('id'); $table->string('title'); $table->integer('user_id')->unsigned(); $table->foreign('user_id')->references('id')->on('users'); $table->timestamps(); });
Luego en database/factories/ModelFactory.php, agreguemos un nuevo factory para el nuevo modelo Post:
$factory->define(App\Post::class, function (Faker\Generator $faker) { return [ 'title' => $faker->sentence(), 'user_id' => rand(1, 10) ]; });
Para nuestro demo, asumiremos que hay 10 usuarios en el sistema que tienen IDs del 1 al 10.
A continuación necesitamos agregar los seeders, para usuarios, crea un nuevo archivo en database/seeds/UserTableSeeder.php y agrega esto:
<?php use Illuminate\Database\Seeder; class UserTableSeeder extends Seeder { public function run() { factory(App\User::class)->create([ 'name' => 'Duilio', 'email' => '[email protected]', 'password' => bcrypt('admin'), ]); factory(App\User::class, 9)->create(); } }
Para posts, crea un archivo database/seeds/PostTableSeeder.php con lo siguiente:
<?php use Illuminate\Database\Seeder; class PostTableSeeder extends Seeder { public function run() { factory(App\Post::class, 50)->create(); } }
Puedes generar los seeders con el comando make:seeder, ejemplo:
php artisan make:seeder PostTableSeeder
Por último, no olvides registrar los seeders en database/seeders/DatabaseSeeder.php en el medio de Model::unguard y Model::guard coloca esto:
$this->call(UserTableSeeder::class); $this->call(PostTableSeeder::class);
Aprende más sobre migraciones, seeders y model factories en Laravel 5.1.
Muy bien ahora crea una nueva base de datos, por ejemplo «basic_blog», y configura tu archivo .env con los datos de conexión a la DB, en mi caso, con Homestead, es esto:
DB_HOST=localhost DB_DATABASE=basic_blog DB_USERNAME=homestead DB_PASSWORD=secret
Ahora ejecuta este comando para crear todas las tablas con sus datos de prueba:
php artisan migrate --seed
Deberías recibir un mensaje de confirmación como éste:
Hasta acá todo fue bastante estándar, ahora veamos cómo implementar los permisos:
Creación de nuevas reglas de acceso en Laravel
Imagina que estás creando el nuevo «Medium», cada usuario tiene un post y sólo puede editar sus posts, tendrías que implementar una lógica como esta, en algún lugar de tu aplicación:
$post->user_id == $user->id
¿Pero donde? Y esa es la respuesta principal de este nuevo componente de Laravel, porque la lógica para autorizar o denegar accesos es propia de tu aplicación, ningún framework ni ningún componente puede dictarte qué condiciones deben restringir o autorizar el acceso de un usuario X a una parte Y de tu proyecto. Esto debes conversarlo con tu cliente, o analizarlo si se trata de un proyecto propio.
Entonces este nuevo componente de Laravel te da un lugar y una interfaz muy sencilla para definir, organizar y posteriormente implementar las reglas de acceso de tu proyecto.
Si recién instalaste Laravel, ahora verás un nuevo service provider, en app/providers/AuthServiceProvider.php, donde puedes implementar esta lógica:
<?php namespace App\Providers; use Illuminate\Contracts\Auth\Access\Gate as GateContract; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; class AuthServiceProvider extends ServiceProvider { /** * Register any application authentication / authorization services. * * @param \Illuminate\Contracts\Auth\Access\Gate $gate * @return void */ public function boot(GateContract $gate) { parent::registerPolicies($gate); // } }
Dentro del método «boot» debajo de parent::registerPolicies($gate) podemos colocar lo siguiente:
$gate->define('update-post', function ($user, $post) { return $user->id === $post->user_id; });
Esto creará una nueva regla «update-post» que podrás aplicar en cualquier parte del proyecto.
Uso de la nueva autorización en proyectos actuales
Nota: Actualizar tu proyecto es opcional, y sólo si quieres usar este nuevo feature.
Si ya estás trabajando en un proyecto de Laravel 5.1 y quieres usar este nuevo feature, es muy sencillo, sólo tienes que ejecutar:
composer update
Crea una carpeta vacía en app/ llamada Policies: app/Policies/
Luego crea el service provider app/providers/AuthServiceProvider.php, puedes tomar el código de GitHub.
A continuación regístralo en el array $providers de config/app.php.
App\Providers\AuthServiceProvider::class
y también el facade en el array de $aliases en el mismo archivo config/app.php:
‘Gate’ => Illuminate\Support\Facades\Gate::class,
Recuerda que si tu proyecto se llama «Acme» debes reemplazar el namespace del proyecto de «App» a «Acme».
Cambios al modelo de Usuario
Asegúrate de estar implementando la interface AuthorizableContract y usando el trait Authorizable del modelo de usuarios, por ejemplo:
<?php namespace App; use Illuminate\Auth\Authenticatable; use Illuminate\Database\Eloquent\Model; use Illuminate\Auth\Passwords\CanResetPassword; use Illuminate\Foundation\Auth\Access\Authorizable; use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract { use Authenticatable, Authorizable, CanResetPassword; //codigo del modelo de usuario... }
Cambios al controlador base
Modifica el controlador base para agregar el nuevo trait AuthorizesRequests que aprenderemos en una próxima clase:
<?php namespace App\Http\Controllers; use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Routing\Controller as BaseController; use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; abstract class Controller extends BaseController { use AuthorizesRequests, DispatchesJobs, ValidatesRequests; }
Implementación de reglas de acceso con Laravel
Ahora dentro de app/Http/routes.php, definamos una prueba rápida, con la siguiente ruta, que hipotéticamente serviría para mostrar un formulario para editar un post, si el usuario tiene permiso, claro está:
Route::get('edit-post/{id}', function ($id) { // Let's just pretend we are logged in as the user with ID 1 Auth::loginUsingId(1); // Now let's try to find a post $post = App\Post::findOrFail($id); // Do we have access to update it? if (Gate::denies('update-post', $post)) { abort(403); } // Then we show the form, etc. but for now just the title is fine: return $post->title; });
Básicamente pretendemos que estamos conectados como el usuario 1, luego buscamos un post, y verificamos si el usuario conectado tiene permiso para editar el post, si no es así lanzamos un error 403 (no autorizado). Finalmente si la comprobación pasa mostraríamos el formulario, etc. y lo mismo para la ruta post para actualizar, validaríamos y guardaríamos el post etc.
El nuevo componente de control de acceso se puede implementar de muchas maneras
Por ejemplo puedes usar los métodos «can» y «cannot» del modelo User:
if (Auth::user()->cannot('update-post', $post)) { abort(403); }
Incluso puedes usar este nuevo feature dentro de Blade con la directiva @can o @cannot:
@can('update-post', $post) <a href="/post/{{ $post->id }}/edit">Edit Post</a> @endcan
Se lee tan fácil que no requiere mayor explicación… Pero sí fíjate que Laravel asume que quieres comprobar permisos para el usuario conectado, por lo tanto, no necesitas pasar la variable $user, haciendo así la interfaz mucho más rápida y fácil de usar.
Claro, si necesitaras comprobar los permisos de otro usuario diferente al usuario conectado, puedes hacerlo también, usando «forUser»:
if (Gate::forUser($user)->allows('update-post', $post)) { // }
Conclusión
Las reglas de acceso de tu aplicación, forman parte de la lógica del proyecto y deben ser analizadas y definidas por ti mismo, sin embargo, con este componente Laravel te da un lugar muy conveniente donde definirlas y luego una manera fácil, e incluso divertida, de implementar estas reglas dentro de tu aplicación. ¿Imaginas una interfaz aún más fácil de usar?
Aún queda mucho por cubrir, ¿qué te parece si creo un par de videos y los anexo a la parte 2 del Curso introductorio de Laravel 5.1 donde ya discutimos restricción de acceso por login fallidos, recuperación de contraseña, roles de usuario y más.
Mientras tanto puedes seguir aprendiendo de este nuevo feature si lees la documentación oficial de Laravel sobre autorización, está muy completa.
Aprende más sobre cómo proteger tu aplicación en Laravel
- Parte 4 de nuestro: Curso introductorio de Laravel 5.1
- Parte 1 de nuestro curso: Novedades en Laravel 5.2
- Introducción a los middleware en Laravel 5.1
- Crea tus propios middleware en Laravel 5.1
- Middleware en Laravel 5.1: parámetros y roles de usuario
Regístrate hoy en Styde y obtén acceso a todo nuestro contenido.
Lección anterior Cómo instalar proyectos existentes de Laravel Lección siguiente Uso de caché en Laravel 5.1