Archives Wild Code School
Symfony

24 - Symfony : Sécurisons nos routes.

Quête 27 sur 35 · 1 image archivée

Objectifs

  • ✅ Gérer l'accès à ton application en fonction des rôles.
  • ✅ Découvrir les différentes méthodes possibles pour y arriver.

Pré-requis

​ Avoir terminé la quête

Quêtes liées
title-book
Introduction

User stories 📋

en tant que visiteur, je souhaite avoir accès à un formulaire d'inscription afin de pouvoir me créer un compte

In progress 🤸

en tant que visiteur, je souhaite me connecter

Doing 🏗️

mise en place des rôles utilisateurs

Done ✅

création de l'entité User

création du formulaire de login

Tu t'en doutes, l'ajout d'une nouvelle série et les CRUDs liés à tes autres entités ne seront pas accessibles à tout le monde ! Ce serait ingérable si tout le monde pouvait écrire ou modifier n’importe quoi sur ton site internet. Les derniers qui ont fait ça ont donné naissance aux skyblogs...

Dans cette quête, tu vas découvrir comment il est possible, avec Symfony, de gérer les droits d'accès en fonction du type d’utilisateur et de leur rôle.

Sommaire

Présentation du fichier security.yaml

Comme beaucoup de choses dans Symfony, tu vas devoir éditer des fichiers .yaml (à prononcer yameule) pour configurer ton application. En ce qui concerne la sécurité, il y a, dans le répertoire config/packages/, le fichier security.yaml. Tu as déjà pu voir ce qu'il y avait dedans, et même l'éditer, en réalisant la quête précédente.

Tu peux y retrouver tout un tas d’options comme :

  • le type d’encodage du mot de passe utilisateur
  • la gestion de tes utilisateurs
  • etc.

Tu auras l’occasion de revenir dans ce fichier pour l’adapter à tes besoins.

↗
Security Configuration Reference

Si tu veux voir ce qu’il est possible de configurer via ce fichier.

symfony.com

Gestion des rôles

Dans certaines applications, tu peux avoir besoin de gérer plusieurs rôles avec des niveaux hiérarchiques.

Tu as le droit de définir autant de rôles que le nécessite ton application. La seule chose à respecter est la nomenclature. Il faut systématiquement les écrire en UPPER_SNAKE_CASE, préfixés avec ROLE_. Ainsi, ton application peut comporter des utilisateurs avec des rôles “ROLE_COMPANY”, “ROLE_STUDENT”, “ROLE_TEACHER”, etc. Sache que, par défaut, il y a un rôle prédéfini dans symfony pour tous les utilisateurs : c’est le rôle “ROLE_USER”.

Si tu souhaites récupérer les rôles que possède un utilisateur, tu peux le faire depuis l’objet User, à l’aide de $user->getRoles(); Et si jamais tu souhaites redéfinir ses rôles, car oui, un utilisateur peut parfois cumuler plusieurs rôles, tu dois passer par la méthode $user->setRoles($roles); où $roles serait un tableau comportant tous les rôles souhaités.

Définissons le niveau de hiérarchie suivant pour le Wild Series:

  • un simple utilisateur peut lire le résumé d’une série.
  • seuls les administrateurs peuvent créer une catégorie.
  • seuls les utilisateurs avec le role "contributeur" peuvent ajouter/modifier des séries.
  • un contributeur ne peut modifier une série que s'il en est l'auteur.
  • un administrateur peut modifier toutes les séries sans exception.

💻 Ainsi, en suivant la ressource ci-dessous, configure la hiérarchie des rôles comme ceci :

yml
# config/packages/security.yaml
security:
    # ...
    role_hierarchy:
        ROLE_CONTRIBUTOR: ROLE_USER
        ROLE_ADMIN: ROLE_CONTRIBUTOR

Cela sous-entend que tous les utilisateurs ayant un rôle ROLE_CONTRIBUTOR ont par défaut droit aux mêmes choses que les utilisateurs ayant le ROLE_USER. Et que les utilisateurs ayant le ROLE_ADMIN héritent des mêmes droits que tous les ROLE_CONTRIBUTOR et indirectement des mêmes droits que ceux ayant le ROLE_USER.

Pour rappel, ce ROLE_USER est le rôle par défaut de tous les utilisateurs connectés, ajouté automatiquement par Symfony. Regarde au niveau de la méthode getRoles() de l’entité App\Entity\User. Celle-ci renverra systématiquement au moins ce rôle.

Info

Au-delà des rôles que tu peux définir toi-même il existe aussi des rôles un peu particuliers prédéfinis par Symfony. Jette un oeil à la ressource suivante.

↗
Checking to see if a User is Logged In
symfony.com
↗
Hierarchical Roles
symfony.com

Sécurisation

💻 Tu vas protéger l'accès à la page de création d'une série /program/new et celle d'édition /program/{slug}/edit que tu as créé dans les précédentes quêtes.

Cela se passe dans la partie access_control de ton fichier security.yaml.

💻 Remplace le code existant :

yaml
access_control:
    # - { path: ^/admin, roles: ROLE_ADMIN }
    # - { path: ^/profile, roles: ROLE_USER }

par

yaml
access_control:
    - { path: ^/program/new, roles: ROLE_CONTRIBUTOR }
    - { path: ^/program/[a-zA-Z_0-9-]+/edit, roles: ROLE_CONTRIBUTOR }

Si tu passes en mode “déconnecté” (en navigation privée par exemple, ou bien via la fonction de déconnexion implémentée lors de la quête précédente) et que tu tentes d'accéder à la route /program/new], que se passe-t-il ?

Normalement, si tout est bien configuré, tu es redirigé vers la page de login. C'est tout à fait normal, car tu viens d'autoriser l'url /program/new uniquement aux utilisateurs ayant comme rôle le rôle "ROLE_CONTRIBUTOR" (au minimum).

Connecte-toi avec le compte utilisateur ‘contributor@monsite.com’ que tu as créé lors de la quête précédente.

Cette fois-ci, tu es autorisé à y accéder. En effet, si tu regardes dans la barre de debug, au niveau de la partie concernant l'utilisateur courant, tu peux constater que tu disposes bien du rôle ROLE_CONTRIBUTOR. Si tu as été attentif lors de la création des fixtures, il était défini pour cet utilisateur: $user->setRoles(["ROLE_CONTRIBUTOR"]);

Si tu tentes l'expérience avec l’autre compte utilisateur, “admin@monsite.com”, tu peux également accéder à la création d'une nouvelle série. Car, rappelle-toi, tu as fait hériter ROLE_ADMIN des mêmes droits que ROLE_CONTRIBUTOR.

Info

Tu noteras que dans le fichier yaml le path comprend un ^. C'est parce qu'il s'agit d'une expression régulière. Ainsi le path ^/program/[a-zA-Z_0-9-]+/edit dans notre exemple, matchera l'url /program/walking-dead/edit par exemple. Note 2: Si le path avait été ^/program/, il aurait matché TOUTES les URLs commençant par /program/.

Gestion côté controller

Seuls un contributeur ou un administrateur peut désormais créer une nouvelle série ou en éditer une existante. Il faut maintenant gérer le cas où le contributeur ne peut modifier qu'une série qu'il a lui même créée. Actuellement, aucune propriété de la classe Program ne permet d'identifier le créateur de la série.

💻 Ajouter une propriété owner dans ta classe Program. (Tu peux utiliser le Maker Bundle avec make:entity). Cette propriété owner sera une relation de type ManyToOne vers l'entité User. N'oublie pas de générer tes getter/setter.

💻 Modifie tes fixtures pour associer un utilisateur en tant que propriétaire de la série dans ta classe ProgramFixtures en faisant appel à $program->setOwner(...). Tu auras besoin d'utiliser les méthodes addReference() et getReference() vues à la quête "Fixtures avancées".

💻 Dans la méthode new() de ton ProgramController , définis l'utilisateur connecté comme étant le propriétaire de la série nouvellement créée.

Dans une classe de contrôleur, pour récupérer l'utilisateur courant, tu peux faire appel à la méthode $this->getUser().

Ainsi ton code devrait ressembler à ça :

php
// src/Controller/ProgramController.php

#[Route('/program/new', name: 'program_new')]
public function new(Request $request, ProgramRepository $programRepository) : Response
{
    // ...
    if ($form->isSubmitted() && $form->isValid()) {
        // Deal with the submitted data
        // ...
        // Set the program's owner
        $program->setOwner($this->getUser());
        $programRepository->save($program, true);

        return $this->redirectToRoute('program_index');
    }
    // Render the form
    return $this->render('program/new.html.twig', [
        "form" => $form,
    ]);
}

Maintenant que le propriétaire d'une série est clairement identifiable, il faut bloquer l'édition pour les contributeurs qui ne seraient pas propriétaires de la série concernée.

💻 Ajoute une vérification supplémentaire dans la méthode edit() nouvellement créée de ton ProgramController :

php
// src/Controller/ProgramController.php
use Symfony\Component\Security\Core\Exception\AccessDeniedException;

#[Route('/{slug}/edit', name: 'program_edit', methods: ['GET', 'POST'])]
#[Route('/', name: 'app_home')]

public function edit(Request $request, Program $program, ProgramRepository $programRepository): Response
{
    // Check wether the logged in user is the owner of the program
    if ($this->getUser() !== $program->getOwner()) {
        // If not the owner, throws a 403 Access Denied exception
        throw $this->createAccessDeniedException('Only the owner can edit the program!');
    }
        // ...
    }

Dans le cas où un contributeur tente d'éditer une série qui n'est pas la sienne, une exception avec un code HTTP 403 est levée. Attention à bien ajouter le use en haut de fichier.

👏 Bravo, la création et l'édition de séries sont maintenant correctement sécurisées.

Sécurisation de la page de création de catégorie

Si tu reprends ta classe CategoryController, tu te souviens que la partie "création de catégorie" est implémentée.

Tu as vu comment verrouiller l'accès d'une URL en particulier ou d'un pan entier de ton application via le fichier de configuration yaml.

Sache qu'il existe une deuxième méthode pour ne verrouiller qu'une petite partie de ton application qui ne correspondrait qu’à une seule méthode d'un controller. Tu peux le faire directement grâce aux attributs !

💻 Sur ta méthode new() de CategoryController, ajoute l'attribut suivant :

php
#[IsGranted('ROLE_ADMIN')]

et l'appel au use correspondant en haut de ta classe :

php
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;

Tout comme la page de création d'une nouvelle série, la page de création d'une catégorie ne s'affiche plus en mode déconnecté et te redirige vers le formulaire de connexion.

Tu peux ainsi gérer finement, méthode par méthode, les droits d’accès. Mais si tu préféres l’approche attribut à la configuration .yaml, à l’instar de #[Route], tu peux mettre cet attribut #[IsGranted] directement sur ta classe et verrouiller toutes ses méthodes.

↗
How to Use Voters to Check User Permissions

Si tu souhaites gérer plus finement les différents droits d’un utilisateur, tu peux consulter cette ressource. Celle-ci n’est cependant pas nécessaire pour accomplir le challenge.

symfony.com
↗
Security & IsGranted

Les attributs du composant "Security" de Symfony.

symfony.com

Côté vue

Tu peux avoir également besoin de cette notion d'autorisation, côté vue.

Imagine ! Tu parcours ton site et tu te rends compte qu'il y a une coquille dans le contenu d’une série. Tu devrais aller dans l'espace d'administration de ton site retrouver la série en question, puis corriger la faute.

Que dirais-tu d'ajouter dans ton application, sur chaque série, un bouton "Éditer" lorsque tu es reconnu comme "administrateur" ?

Il te suffirait de rajouter le code ci-dessous pour chaque série affichée :

html
{% if is_granted("ROLE_ADMIN") %}
    <a href="{{ path('program_edit', { 'slug': program.slug} ) }}">Éditer</a>
{%endif%}

Ainsi, tu aurais accès à ce bouton qui t'emmène vers la page d'édition des séries. Et ce, uniquement pour les utilisateurs identifiés en tant qu'administrateur (ROLE_ADMIN).

Info

Ci dessus, tu vois un exemple pour baser ta condition sur le rôle de l'utilisateur (s'il est administrateur, en l'occurence). Afin que ton bouton "Éditer" soit également visible pour le contributeur auteur de la série, il faut bien entendu modifier cette condition.

💪 Challenge

Gestion des utilisateurs et de leurs rôles.

Lors de cette quête, tu as vu comment sécuriser la création de catégories et la création et l'édition de séries.

L'objectif du challenge est de sécuriser la partie "commentaires" ajoutée lors de la quête précédente selon les critères suivants :

  • Un utilisateur non connecté ne peut, ni créer de commentaires, ni les supprimer.
  • Les contributeurs/administrateurs peuvent poster un commentaire.
  • Les administrateurs peuvent supprimer n'importe quel commentaire.
  • Les contributeurs ne peuvent supprimer que les commentaires dont ils sont l'auteur.

💡 Afin d'afficher/masquer certains éléments de ta vue en fonction de l'utilisateur connecté ou non (comme le formulaire pour poster un commentaire, bouton de suppression ... par exemple), rappelle toi de ce que tu as vu lors de cette quête et la précédente :

  • en twig :
    • {{ app.user }} pour accéder à l'utilisateur courant et le comparer à l'auteur du commentaire.
    • {{ is_granted("ROLE_ADMIN") }} pour vérifier le rôle de l'utilisateur.

Critères de validation

  • Un utilisateur reconnu comme "anonymous" ne peut pas poster de commentaire.
  • Un utilisateur reconnu comme "contributeur" peut poster un commentaire.
  • Un utilisateur reconnu comme ”contributeur” ne peut supprimer un commentaire que s’il est lui-même l’auteur.
  • Un utilisateur reconnu comme "administrateur" peut poster un commentaire et supprimer n'importe quel commentaire.
  • Le code est disponible sur un repository GitHub, avec une branche correspondant à cette quête.