slideshare quotation-marks triangle book file-text2 file-picture file-music file-play file-video location calendar search wrench cogs stats-dots hammer2 menu download2 question cross enter google-plus facebook instagram twitter medium linkedin drupal GitHub quotes-close
Magnifying glass

Le Drupal Session Inspector module permet à un utilisateur de visualiser ses sessions en cours sur un site Drupal. En permettant aux utilisateurs de visualiser ces informations, vous créez un système de sécurité en libre-service où les utilisateurs peuvent voir eux-mêmes s'il existe une activité inhabituelle sur leur compte. Le module permet également aux utilisateurs de supprimer toute session dont ils n'aiment pas l'aspect, ce qui protège leur compte.

Cette fonction de suppression de session est particulièrement utile dans les situations où l'utilisateur a négligé de se déconnecter de son ordinateur.  Il lui suffit de se connecter au site à partir d'un autre endroit et de révoquer cette session, protégeant ainsi son compte contre tout accès accidentel.

Dans le cadre de mon travail continu sur le module Session Inspector, j'ai voulu ajouter un système d'événements qui pourrait réagir à cette suppression de session.

Nous utilisons beaucoup de services d'authentification unique chez Code Enigma, il était donc logique d'ajouter des événements au module afin de pouvoir réagir à ces événements. Cela signifie que lorsqu'un utilisateur supprime une session, nous pouvons pousser cet événement dans le service d'authentification unique et détruire cette session globalement. Sans cette action, la suppression des données de session de Drupal n'aurait aucun effet puisque la session serait simplement régénérée si l'utilisateur visitait à nouveau le site Drupal via ce navigateur.

Ce qu'il advient de l'événement après son déclenchement ne relève pas du cadre de ce module, car l'ajout des réactions à l'événement créerait de nombreuses dépendances, soit avec d'autres modules, soit avec des paquets qui intègrent des systèmes d'authentification unique. Ces dépendances rendraient le module beaucoup plus complexe qu'il ne doit l'être.

Dans cet article, je vais configurer le système d'événements dans le module, puis créer un harnais de test pour m'assurer que les événements sont déclenchés correctement.

Tout d'abord, voyons comment ajouter des événements personnalisés au module Session Inspector.

Ajout d'événements personnalisés au module Session Inspector de Drupal

Quelques composants sont nécessaires pour créer des événements personnalisés dans Drupal, mais ils sont en fait assez simples et ne nécessitent pas beaucoup de code. J'ai déjà parlé du système d'événements de Drupal si vous êtes intéressés par les détails.

Les composants dont nous avons besoin pour ajouter des événements sont les suivants

  • Une classe qui contient le nom de l'événement comme constante.
  • Une classe qui élargit la classe d'événement de base de Drupal et ajoute toute information personnalisée nécessaire au fonctionnement de l'événement.
  • L'ajout de code pour distribuer l'événement à l'endroit approprié.

Lorsque nous déclenchons un événement dans Drupal, nous devons faire savoir au système quel type d'événement a été déclenché. Ceci est fait en utilisant une constante que nous gardons dans une classe appelée SessionInspectorEvents, qui n'existe que pour garder les choses en ordre.

Voici la classe SessionInspectorEvents.

<?php

namespace Drupal\session_inspector\Event;

/**
 * Contains all events thrown in the Session Inspector module.
 */
final class SessionInspectorEvents {

  /**
   * Event ID for when a session record is destroyed.
   *
   * @Event
   *
   * @var string
   */
  const SESSION_DESTROYED = 'session_inspector.session_destroyed';

}

Ce mot-clé PHP final est utilisé pour empêcher l'extension de cette classe et garantit que le nom de notre événement sera toujours le même.

Nous devons seulement définir la constante de l'événement SESSION_DESTROYED, que nous déclencherons lorsqu'une session sera détruite. La classe ne doit pas contenir d'autre code. Si nous voulons ajouter d'autres événements à l'avenir, nous devrons ajouter d'autres constantes à cette classe, puis les déclencher.

Non seulement la constante de l'événement est transmise à l'événement lorsqu'il est déclenché, mais des informations sur la session au cœur de l'événement sont également transmises. Pour ce faire, nous étendons la classe de base Drupal\Component\EventDispatcher\Event et y ajoutons les données dont nous avons besoin. Ce que nous créons ici est essentiellement un objet de données, dont le seul but est de transmettre des données sur l'événement à partir du point de déclenchement vers le gestionnaire d'événements.

Il y a une bonne dose de code passe-partout ici pour obtenir et définir différentes propriétés, mais la classe dont nous avons besoin ressemble essentiellement à ceci.

<?php

namespace Drupal\session_inspector\Event;

use Drupal\Component\EventDispatcher\Event;

/**
 * A data object to pass information about the session to the event.
 *
 * @package Drupal\session_inspector\Event
 */
class SessionEvent extends Event {

  /**
   * The user ID.
   *
   * @var int
   */
  protected $uid;

  /**
   * The (hashed) session ID.
   *
   * @var string
   */
  protected $sid;

  /**
   * The session hostname.
   *
   * @var string
   */
  protected $hostname;

  /**
   * The session timestamp.
   *
   * @var int
   */
  protected $timestamp;

  /**
   * Constructs a SessionEvent object.
   *
   * @param int $uid
   *   The user ID.
   * @param string $sid
   *   The (hashed) session ID.
   * @param string $hostname
   *   The session hostname.
   * @param int $timestamp
   *   The session timestamp.
   */
  public function __construct($uid, $sid, $hostname, $timestamp) {
    $this->uid = $uid;
    $this->sid = $sid;
    $this->hostname = $hostname;
    $this->timestamp = $timestamp;
  }

  // snipped out the getter and setter methods for brevity.
}

Avec ces deux classes, nous avons maintenant tout ce dont nous avons besoin pour commencer à déclencher l'événement.

L'événement peut être déclenché en modifiant le gestionnaire de soumission du formulaire de confirmation de suppression de session (appelé UserSessionDeleteForm). Dans le gestionnaire de soumission de ce formulaire, nous devons créer un nouvel objet SessionEvent qui contient les données relatives à la session avant de déclencher l'événement, en passant l'objet et le nom de l'événement à déclencher.

Pour ce faire, nous utilisons le service central event_dispatcher, qui est également injecté dans le formulaire à l'aide de l'interface d'injection de conteneur. L'appel de la méthode dispatch() transmettra les informations de ce point à tout abonné aux événements qui surveille le déclenchement de cet événement. Tout le reste du formulaire et le gestionnaire de soumission restent inchangés puisque le formulaire fait déjà le travail qu'il doit faire.

Je ne vais pas ajouter toute la classe de formulaire ici, car elle contient une bonne quantité de code passe-partout qui n'est pas pertinent. Ce qui est pertinent, c'est la méthode submitForm(), où nous déclenchons les événements importants. Vous pouvez voir que nous configurons l'objet SessionEvent, que nous supprimons la session de l'utilisateur, et que nous passons ensuite l'objet créé à l'événement en utilisant la méthode dispatch(). Le formulaire redirige ensuite l'utilisateur vers la page des sessions de la manière habituelle.

public function submitForm(array &$form, FormStateInterface $form_state) {
  // Get the session data we are about to delete.
  $sessionData = $this->sessionInspector->getSession($this->sessionId);
  $sessionEvent = new SessionEvent($sessionData['uid'], $sessionData['sid'], $sessionData['hostname'], $sessionData['timestamp']);

  // Destroy the session.
  $this->sessionInspector->destroySession($this->sessionId);

  // Trigger the session event.
  $this->eventDispatcher->dispatch($sessionEvent, SessionInspectorEvents::SESSION_DESTROYED);

  // Redirect the user back to the session list.
  $form_state->setRedirectUrl($this->getCancelUrl());
}

Si vous voulez voir l'intégralité du formulaire, vous pouvez trouver tout le code source dans le module Session Inspector.

C'est tout pour l'ajout d'événements au module, mais ajoutons un test au module pour nous assurer que tout est correctement configuré.

Tester les événement

Avec quelque chose d'aussi critique que la destruction d'une session, nous devons vraiment nous assurer que lorsque la session est détruite, l'événement est déclenché correctement.

La meilleure façon de le faire est de créer un module dans le répertoire tests du module Session Inspector. Le seul objectif de ce module est d'enregistrer un abonné à l'événement et d'exécuter une action sur l'événement. Ce module possède le fichier info.yml suivant.

name: 'Session Inspector Events Test'
description: 'Functionality to assist session inspector event testing.'
type: module
hidden: true
core_version_requirement: ^8.8 || ^9
dependencies:
  - session_inspector
package: Testing

Notez que nous avons défini le paramètre "hidden" sur "true" afin de cacher ce module de test du fonctionnement courant du site. Cela signifie que les administrateurs du site ne peuvent pas activer le module, ce qui est une bonne chose puisque les plugins de test ne produiront pas de résultats significatifs.

Ce module de test contient un seul service, que nous définissons dans le fichier services.yml du module. Ici, nous enregistrons la classe d'abonné aux événements avec Drupal en utilisant la balise event_subscriber, mais nous passons également l'API d'état du noyau de Drupal comme dépendance.

services:
  session_inspector_events_test.session_inspector_event_subscriber:
    class: \Drupal\session_inspector_events_test\EventSubscriber\SessionInspectorEventsTest
    arguments: ['@state']
    tags:
      - { name: event_subscriber }

L'API d'état de Drupal est un réservoir de stockage à court terme que nous pouvons utiliser pour stocker des valeurs dans le récepteur d'événements du module de test que nous pouvons ensuite récupérer et lire dans le test. Cela nous donne un moyen pratique de nous assurer que l'événement s'est exécuté et qu'il contient les bonnes informations.

La classe SessionInspectorEventsTest implémente l'interface EventSubscriberInterface, ce qui signifie que nous devons ajouter une méthode appelée getSubscribedEvents(). Cette méthode doit renvoyer un tableau associatif des événements auxquels nous voulons que cet abonné aux événements réagisse, ce qui, dans ce cas, n'est que l'événement de destruction de session. Ce mécanisme est utilisé par Drupal pour découvrir tous les abonnés aux événements disponibles sur le site.

Drupal appellera la méthode onSessionDestroyed lorsqu'il détectera qu'un événement de destruction de session est déclenché.

public static function getSubscribedEvents() {
  $events[SessionInspectorEvents::SESSION_DESTROYED] = ['onSessionDestroyed'];
  return $events;
}

La méthode onSessionDestroyed() accepte un objet événement comme paramètre unique. Il s'agit en fait de l'objet SessionEvent que nous avons créé dans le gestionnaire de soumission UserSessionDeleteForm. Tout ce que nous devons faire dans cet récepteur d'événement est d'utiliser l'API d'état pour enregistrer l'utilisateur et l'ID de session de la session.

/**
 * Event callback when a session is destroyed.
 *
 * @param \Drupal\session_inspector\Event\SessionEvent $event
 *   The event data.
 */
public function onSessionDestroyed(SessionEvent $event) {
  // Set a state so that we can detect that the event triggered.
  $this->state->set('session_event.uid', $event->getUid());
  $this->state->set('session_event.sid', $event->getSid());
}

Le module Session Inspector contient déjà quelques tests qui examinent la fonctionnalité de base du module. Il s'agit notamment de l'exécution d'une étape de suppression de session. Le harnais de test dont nous avons besoin est donc déjà présent pour ajouter l'assertion selon laquelle l'événement a été déclenché.

Ce que nous devons faire pour tester notre système d'événements est d'ajouter le module session_inspector_events_test à la liste des modules installés utilisés pendant le test. Puisque le module contient le service nécessaire pour écouter l'événement, nous n'avons pas besoin d'ajouter d'autres configurations.

protected static $modules = [
  'session_inspector',
  'session_inspector_events_test',
];

Dans le test qui vérifie si un utilisateur peut supprimer une session, nous ajoutons quelques lignes de code pour nous assurer que l'API d'état contient les valeurs définies pendant le récepteur d'événements. Plus important encore, nous vérifions également que les valeurs correspondent aux valeurs attendues de la session de l'utilisateur. Voici la section modifiée du test.

// Delete on the delete.
$this->click('[data-test="session-operation-0"] a');

$url = $this->getUrl();
preg_match('/user\/(?<uid>\d*)\/sessions\/(?<sid>.*)\/delete/', $url, $urlMatches);
$uid = $urlMatches['uid'];
$sid = $urlMatches['sid'];

// Click confirm.
$this->click('[id="edit-submit"]');

// Ensure that the session event was triggered and that it
// contains the correct information.
$this->assertEquals($uid, $this->container->get('state')->get('session_event.uid'));
$this->assertEquals($sid, $this->container->get('state')->get('session_event.sid'));

Les tests du module prouvent maintenant que lorsqu'un utilisateur supprime une session, l'événement est déclenché et que les informations relatives à cette session sont transmises correctement à l'événement.

Si vous souhaitez en savoir plus sur le module Drupal Session Inspector, vous pouvez consulter la page du module à l'adresse suivante https://www.drupal.org/project/session_inspector.