martes, 16 de enero de 2018

Laravel 5.1 usando policies

 Buenas, en esta publicación indicaré como implementar sencillamente las policies en Laravel 5.1


¿Qué son las policies en Laravel?


 Las policies como indica su nombre son las políticas relacionadas con los permisos y/o autorizaciones. Van encaminadas a ofrecer una alternativa al midleware de Laravel, pues se puede hacer casi lo mismo con estas, pero con la diferencia de la actuación, pues mientras que con el midleware uno puede tratar la excepción dentro de la clase casi de forma opaca para el que la usa, en el Policy se maneja en el lugar, o función, donde queremos que actúe. Realmente al final se resume en devolver verdadero o falso según las condiciones de la operación.

 Bueno, conformes o no con mi definición de las policies siempre podrán consultarla directamente desde la documentación oficial de Laravel e interpretarla al gusto o literal.

https://laravel.com/docs/5.1/authorization#policies

Implementación y explicación


 Como habrán visto en la documentación el procedimiento es simple. Se crea una clase por medio de artisan, luego se añade (registro) a la clase AuthServiceProvider y luego la usamos en el lugar o función que queremos que actúe.

 Uno de los principales problemas que se nos puede presentar es la ausencia de esta clase, AuthServiceProvider, al recién instalar Laravel 5.1. Para ello podemos solucionar esto de forma sencilla. Nos vamos a la carpeta donde Composer aloja las librerías Php, la vendor de nuestro directorio de trabajo. Luego desde la ruta laravel/framework/src/Illuminate/Auth/ copiamos la clase citada a la ruta app/providers, cambiamos su espacio de trabajo por el de App\Providers y por último registramos esta clase en la configuración del laravel, config\app.php , en la clave 'providers' añadimos al array App\Providers\AuthServiceProvider::class . Podemos luego limpiar la clase y solamente dejarla así de momento para este propósito :

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)
    {
        $this->registerPolicies($gate);
    }
}

 Una vez hecho esto ya simplemente es ejecutar el comando artisan, php artisan make:policy y el nombre de la política que queremos aplicar. 

 Como esto era para clase he usado un proyecto que ya tenía hecho que trata de un portal de anuncios. En el proyecto originalmente el problema de impedir que editaran anuncios de los que los usuarios no fueran propietarios lo resolvía con un midleware. Pero como el profe nos pidió implementarlo para experimentar con este pues aplicaré el Policy para resolver el problema antes indicado.

 Creo entonces el Policy denominado PoliticaDePublicacion, el cual una vez ejecutado el comando artisan me aloja la clase en la ruta app\policies.

 Dentro de esta clase ya creada, como la idea es que en el momento después de la edición y al actualizar el anuncio se evalúe si el usuario puede o no modificar, pues creo un método denominado actualizar y le indico los dos parámetros que participan en esta evaluación o comprobación que no son otros que el usuario registrado y el anuncio a editar. Y al final queda una cosa así :


namespace App\Policies;

use Illuminate\Auth\Access\HandlesAuthorization;

use App\User;
use App\Anuncio;

class PoliticaDePublicacion
{
    
    public function actualiza(User $usuario,Anuncio $anuncio){
        return $usuario->id == $anuncio->id_usuario;
    }
}

 En el método o función se observa que simplemente se compara el id del usuario con el id del anunciante. Que en caso de corresponder devolverá verdadero y en caso contrario falso.

 Hecho esto ahora agregamos nuestra Policy al AuthServiceProvider. Para ello usaremos la propiedad protegida policies que toma una array asociativo. Donde en la parte de la clave usamos la ruta y el nombre de "una" clase entre comillas (o comilla simple), vinculante a nuestra Policy que será el valor que también se asignará por medio de la ruta completa con el nombre de la clase. Yo como clave escogí el controlador pero puedes usar también un modelo como se aprecia en la documentación pues eso depende de su uso.

namespace App\Providers;

use App\Anuncio;
use App\Policies\PoliticaDePublicacion;
        
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    protected $policies = [
        'App\Http\Controllers\AnuncioController' => 'App\Policies\PoliticaDePublicacion',
    ];

    /**
     * Register any application authentication / authorization services.
     *
     * @param  \Illuminate\Contracts\Auth\Access\Gate  $gate
     * @return void
     */
    public function boot(GateContract $gate)
    {
        $this->registerPolicies($gate);
    }
}

 Ya hecho esto simplemente queda usarla dentro de la función donde vaya actualizar por medio del helper o función policy(), a la cual indicamos nuestra clase, en este caso el controlador, y ésta nos devolverá nuestra Policy vinculada pudiendo así usar los métodos definidos, en este caso actualiza().

namespace App\Http\Controllers;

...
use App\Http\Requests\ValidaAnuncio;

class AnuncioController extends Controller
{
...
    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(ValidaAnuncio $request, $id)
    {
        $anuncio = Anuncio::find($id);
        if (policy('App\Http\Controllers\AnuncioController')->actualiza($request->user(),$anuncio)){ 
            $anuncio->update(["titulo"=>$request->titulo, "cuerpo"=>$request->mensaje, "categoria"=>$request->categoria]);
            return "";
        }
        
        return redirect('/'); // en caso de no poderse modficar saltamos al inicio de la pa�gina   
    }
...
 
 También como alternativa podremos usar el $this en el argumento de la función policy() para indicar la clase en vez de escribir el nombre completo de esta(tal como muestro arriba en el código), pues estamos además dentro del controlador para cual se definió la política.

 Y ya por último, también indicar que podríamos usar otra forma que indica la documentación y que engaña mucho con el ejemplo que nos muestra, aunque en una notita lo deja bien claro. Se trata del método authorize(). Dicho método funciona de la siguiente forma; cuando devuelve true el flujo del programa continúa con normalidad y en caso contrario genera una excepción que responde con un código 403 (No autorizado), que en Laravel podremos definir con una vista personalizada. Para usar el authorize() hay que incluir la clase AuthorizesRequests dentro de nuestro controlador o clase pues sino el servidor nos lanza el mensaje de método no encontrado.

By default, the base App\Http\Controllers\Controller class included with Laravel uses the AuthorizesRequests trait. This trait provides the authorize method, which may be used to quickly authorize a given action and throw a HttpException if the action is not authorized.

 Además también habrá que definir los métodos a usar en el método boot() de la clase AuthServiceProvider. Usando a su vez el método define() del objeto GateContract de la citada función. Todo para que el authorize() pueda encontrar los métodos pues de lo contrario siempre generará la excepción 403.


Conclusión


En mi humilde opinión esto puede ser prescindible en el desarrollo de una aplicación con un framework en el que abundan todo tipo de funcionalidades.

No hay comentarios:

Publicar un comentario