Comment utiliser les “Policies” dans Laravel

La sécurité est un aspect important de la plupart des sites Web, en particulier ceux qui contiennent des informations sensibles ou confidentielles. La protection d’un site commence par la sécurisation de la majorité du contenu derrière une page de connexion. Nous pouvons également ajouter une authentification à deux facteurs, des “reCAPTCHAS” et d’autres fonctions pour rendre la connexion plus sure.

Cependant, d’autres considérations entrent en ligne de compte. Par exemple, si notre site contient des informations sur des entreprises et qu’un ou plusieurs utilisateurs appartiennent à une entreprise, nous ne voulons pas qu’un utilisateur voie des informations sur une entreprise à laquelle il n’appartient pas. Nous ne voulons pas non plus qu’il supprime l’enregistrement de l’entreprise ou son propre enregistrement d’utilisateur, car cela peut entrainer des problèmes sur notre site Web. Si nous disposons également d’une table pour les commandes, et que les commandes sont associées à une société, un utilisateur ne doit pouvoir afficher ou modifier que les commandes associées à sa société.

Mise en place de la sécurité

Supposons que nous ayons deux rôles de base – Administrateur et Client. Nous pourrions vouloir configurer notre sécurité comme suit :

AdministrateurAfficher/Modifier/Supprimer tous les utilisateurs, Créer des utilisateurs
Afficher/Modifier/Supprimer les sociétés, Créer des sociétés
Afficher/Modifier/Supprimer toutes les commandes, Créer des commandes
ClientAfficher tous les utilisateurs associés à leur société, modifier leurs informations d’utilisateur.
Afficher/modifier l’enregistrement de la société
Afficher/modifier les commandes de leur société, créer des commandes.
Rôles de sécurité

Il y a plusieurs façons d’y parvenir. Nous pouvons placer la logique dans les contrôleurs des utilisateurs, des entreprises et des commandes. Par exemple, nous pourrions avoir cette méthode dans le contrôleur de la commande :

 public function index() {
        switch (Auth::user()->role) {
            case 'Client':
                $orders = Order::where('company_id', '=', Auth::user()->company_id)->get();
                break;
            case 'Administrator':
                $orders = Order::all();
            default:
                $orders = null;
                break;
        }
        return view('Orders.Index',['orders'=>$orders]);
    }

Cela fonctionne bien, mais ce ne sera pas le seul endroit où nous aurons besoin d’obtenir des enregistrements d’utilisateurs sur le site Web. Nous devrions donc modifier chaque requête pour les utilisateurs dans le système afin de vérifier le rôle de l’utilisateur connecté, et ajouter cette clause supplémentaire. Ce n’est pas très efficace, et il serait facile de rater une requête, surtout pour un nouveau développeur du projet qui ne connait pas toutes les facettes du système. En outre, nous aurons très probablement plusieurs tables qui nécessitent ce type de sécurité.

Création de Policies

Au lieu de cela, nous pouvons utiliser des Policies. Il s’agit de classes qui organisent la logique d’autorisation autour d’un modèle ou d’une ressource particulière. Dans notre exemple, nous allons en créer pour les utilisateurs et les entreprises. Les Policies sont générées en utilisant la commande make:policy Artisan. Cela créera une classe de Policies vide dans le répertoire app/Policies. Si vous voulez en créer une avec des exemples de méthodes de politique liées à la visualisation, la création, la mise à jour et la suppression de la ressource, vous pouvez utiliser l’option --model.

Pour générer les stratégies pour les modèles de notre exemple, nous utiliserons les commandes suivantes :

php artisan make:policy UserPolicy --model=User
php artisan make:policy CompanyPolicy --model=Company
php artisan make:policy OrderPolicy --model=Order

Politique de dénomination

En général, nous recommandons de nommer votre Policy en fonction du nom du modèle, par exemple UserPolicy. En procédant ainsi, Laravel associera automatiquement la Policy au bon modèle. Si vous souhaitez la nommer autrement, vous pouvez modifier App\Providers\AuthServiceProvider en mappant vos Policies au modèle correct.

<?php

namespace App\Providers;

use App\Models\Order;
use App\Policies\OrderPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * Le mapping des "Policies" pour l'application.
     *
     * @var array
     */
    protected $policies = [
        Order::class => OrderPolicy::class,
    ];

    /**
     * Pour enregistrer n'importe quel service d'authentification / d'autorisation.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        //
    }
}

Ajout de règles de sécurité à la Policy

Maintenant que la Policy a été créée, nous pouvons configurer la sécurité de nos modèles. Nous allons configurer l’OrderPolicy à titre d’exemple. Si l’on se réfère à l’exemple ci-dessus, les administrateurs peuvent afficher/modifier/supprimer tous les enregistrements de commande et créer des commandes, tandis que les clients peuvent afficher/modifier les commandes de leur société et créer des commandes.

En regardant l’exemple de code pour le contrôleur créé ci-dessus, les utilisateurs clients peuvent visualiser et modifier tous les enregistrements de commande dont la valeur dans la colonne company_id correspond à la valeur dans leur colonne company_id. Pour chaque méthode de l’OrderPolicy, deux paramètres sont passés. Le paramètre $user est le modèle de l’utilisateur connecté, tandis que le paramètre $order est le modèle de la commande.

Un exemple de OrderPolicy basé sur les règles que nous avons définies ressemblerait à ceci :

<?php

namespace App\Policies;

use App\Models\Order;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;

class OrderPolicy
{
    use HandlesAuthorization;

    /**
     * Détermine si l'usager peut voir le modèle.
     *
     * @param  \App\Models\User  $user
     * @param  \App\Models\Order  $order
     * @return \Illuminate\Auth\Access\Response|bool
     */
    public function view(User $user, Order $order)
    {
        switch ($user->role) {
            case 'Administrator':
                return true;
            case 'Client':
                return $order->company_id === $user->company_id;
            default:
                return false;
        }
    }

    /**
     * Détermine si l'usager peut créer des modèles.
     *
     * @param  \App\Models\User  $user
     * @return \Illuminate\Auth\Access\Response|bool
     */
    public function create(User $user)
    {
        return true;
    }

    /**
     * Détermine si l'usager peut mettre à jour le modèle.
     *
     * @param  \App\Models\User  $user
     * @param  \App\Models\Order  $order
     * @return \Illuminate\Auth\Access\Response|bool
     */
    public function update(User $user, Order $order)
    {
        switch ($user->role) {
            case 'Administrator':
                return true;
            case 'Client':
                return $order->company_id === $user->company_id;
            default:
                return false;
        }
    }

    /**
     * Détermine si l'usager peut détruire le modèle.
     *
     * @param  \App\Models\User  $user
     * @param  \App\Models\Order  $order
     * @return \Illuminate\Auth\Access\Response|bool
     */
    public function delete(User $user, Order $order)
    {
        switch ($user->role) {
            case 'Administrator':
                return true;
            default:
                return false;
        }
    }
}

Chaque fois qu’une requête est exécutée sur la table Order, la méthode appropriée est vérifiée dans la OrderPolicy. Si elle renvoie “True”, la requête aboutit (ou dans le cas d’une vue, l’enregistrement de la commande est inclus dans le résultat). Si elle renvoie “False”, la requête échoue (ou la commande ne sera pas incluse dans la vue).

Il s’agit d’un exemple assez simple, mais il nous donne une bonne idée de ce qui est possible avec les Policies. Pour plus de documentation et d’exemples, vous pouvez vous référer à la documentation de Laravel.

Cette technique est le résultat de nos explorations. Il se peut qu’elle ne s’adapte pas à vos besoins. Chaque technique a ses avantages et ses inconvénients et nous vous invitons à faire vos propres expériences et à tirer vos propres conclusions en fonction de votre situation.