The illustration shows Site with Drupal logo guarded securely hidden behind a net

Les autorisations de nœud Drupal

Tous ceux qui font du développement Drupal rencontreront tôt ou tard le besoin de définir un contrôle d'accès au contenu plus strict. Les mécanismes standard de rôles et d'autorisations sont très flexibles, mais ils peuvent être insuffisants dans des projets complexes. Lorsque l'accès aux nœuds commence à dépendre, par exemple, de champs attribués à un utilisateur donné, vous devez recourir à des solutions plus avancées. Dans Drupal 7 et 8, nous pouvons utiliser un hook – hook_node_access() ou un mécanisme de subventions dit grants.

Contrôle des permissions en utilisant hook_node_access()

Dans les cas où un contrôle de permission avancé est nécessaire, hook_node_access() est le choix le plus populaire. Il existe quelques légères différences entre Drupal 7 et 8, mais le fonctionnement est plus ou moins le même. Il prend trois arguments : $node, $op et $account.

A chaque tentative d'accès à un nœud donné, Drupal appellera mymodule_node_access() dans tous les modules et vérifiera si un utilisateur donné $account a toutes les permissions nécessaires pour exécuter $op dans un $node. Le hook doit retourner l'une des réponses suivantes :

return AccessResult::allowed(); // (ou NODE_ACCESS_ALLOW en version 7)
return AccessResult::forbidden(); // (ou NODE_ACCESS_DENY en version 7)
return AccessResult::neutral(); // (ou NODE_ACCESS_IGNORE en version 7)

Simple, mais efficace, n'est-ce pas? Voici à quoi cela ressemble en pratique (D8) :

use Drupal\Core\Access\AccessResult;
use Drupal\node\NodeInterface;
use Drupal\Core\Session\AccountInterface;

function mymodule_node_access(NodeInterface $node, $op, AccountInterface $account) {
  $type = $node->getType();
  if ($type == 'foo' && $op == 'view') {
    if(strstr($account->getEmail(), '@example.com')) {
        return AccessResult::allowed();
    }
    else {
      return  AccessResult::forbidden();
    }
  }
  return AccessResult::neutral();
}

Il semblerait que le contrôle d'accès utilisant hook_node_access() serait suffisant même pour les besoins les plus avancés, mais comme on dit, il n'y a pas de rose sans épines. Vérifier une longue liste de nœuds un par un aura sûrement un impact négatif sur l'efficacité. En particulier, les modules Views et Menu ne peuvent pas se permettre d'utiliser ce hook trop souvent. Pour des raisons d'optimisation, les développeurs de Drupal ont limité l'appel de hook_node_access() uniquement aux nœuds affichés en mode “Complet”. Dans tous les autres cas, vous devez vous fier au système standard de rôles et de permissions ou utiliser des Access Grants moins gourmands en ressources.

Access Grants

Bien que le principe de fonctionnement de hook_node_access() soit très facile à comprendre, Access Grants est un sujet beaucoup plus complexe. Certains blogueurs essaient de le présenter en utilisant une métaphore de portes, serrures et clés. Personnellement, je pense que cela déforme un peu trop la vérité. C'est pourquoi je vais opter pour une comparaison avec une base militaire secrète.

Attention ! Qu'est-ce qu'une base militaire a en commun avec Drupal ? Eh bien, par exemple, tous deux ont des zones restreintes où vous ne pouvez entrer que si vous avez les autorisations et permissions appropriées. Ce qui est plus important, dans les deux cas, les utilisateurs et les visiteurs reçoivent des passes qui définissent clairement ce qu'ils peuvent voir ou faire à l'entrée.

Dans le système de permissions standard utilisé par Drupal, nous avons un seul garde, qui vérifie le rôle d'un utilisateur sur son pass. Le rôle définit toutes les actions que l'utilisateur peut effectuer (par exemple, lire un nœud de type A, lire et écrire dans un nœud de type B...)

Dans mymodule_node_access(), il y a plusieurs gardes, un pour chaque module. Chacun d'eux identifie l'utilisateur et détermine s'il lui sera accordé l'accès en fonction de critères sélectionnés. Chaque garde a un accès complet aux données de l'utilisateur, ainsi qu'à la zone protégée (le nœud) – leur décision est basée sur l'ensemble de ces facteurs.

Quel est le rôle des Access Grants mentionnés ? Ils divisent le processus d'autorisation en deux parties distinctes, qui sont servies par deux hooks distincts. La façon dont ils sont utilisés dans Drupal 7 et 8 est presque identique.

1) hook_node_access_records()

Le premier hook est activé lorsque le nœud est enregistré. Il a un argument – $node. Le rôle d'un hook est de déterminer la liste des subventions dites grants – des passes qui doivent être montrées lorsque l'accès au nœud est tenté.

function mymodule_node_access_records(\Drupal\node\NodeInterface $node) {
  $grants = [
    [
      'realm' => 'realm1',
      'gid' => 12345,
      'grant_view' => 1,
      'grant_update' => 1,
      'grant_delete' => 1,
      'priority' => 0,
    ],
  ];
  return $grants;
}

Chaque grant (subvention) a son propre domaine. L'utilisateur, qui tente d'accéder à un nœud donné, doit avoir au moins une subvention (pass) dans chaque domaine défini. Par exemple, si Drupal construit la liste suivante de subventions basées sur tous les hooks qu'il trouve :

$grants[] = 
[ 'realm' => 'realm1', 'gid' => 123, … ],
[ 'realm' => 'realm1', 'gid' => 456, … ],
[ 'realm' => 'realm2', 'gid' => 789, … ];

L'utilisateur doit avoir au moins un pass realm1 et un pass realm2 pour pouvoir accéder au nœud.

Compliqué ? Je vais essayer de le présenter à l'aide de ma métaphore militaire. Supposons que notre base secrète ait plusieurs départements (nous les utiliserons pour transmettre le concept de domaines). Chaque département a son propre garde à l'entrée de chaque zone restreinte. Un utilisateur qui veut accéder à une zone donnée doit se rendre à chaque garde et montrer qu'il a un pass approprié (c'est-à-dire, un grant). Si le garde détermine que le pass de l'utilisateur est conforme aux exigences de son département, il permet à l'utilisateur de passer plus loin. Si l'utilisateur n'a pas de pass, il ne pourra pas accéder à la zone et ne sera pas vérifié par les autres gardes.

Pourquoi cette approche est-elle plus rapide ? C'est parce que le garde n'a pas besoin de connaître les détails de l'autorisation (ou d'avoir accès au nœud du tout), il doit seulement comparer les passes.

Qu'est-ce qu'un domaine ?

Un domaine (ou comme je l'ai appelé dans ma métaphore militaire – un département) peut être n'importe quelle chaîne, généralement, le nom d'un module est utilisé. Cette approche rend le système de subventions plus propre et plus compréhensible – l'utilisateur a besoin d'une subvention (pass) pour chaque module qui définit ses propres règles d'accès aux nœuds en utilisant hook_node_access_records(). Vous pouvez utiliser cette convention si vous ne la considérez pas comme trop simplifiée.

Tout ou rien

En utilisant des mécanismes d'autorisation, parfois le besoin survient d'utiliser deux scénarios extrêmes : accès pour tous ou seulement pour l'administrateur. Dans le premier cas, il suffit de retourner une liste vide de subventions dans un hook. Cela signifie que dans un domaine donné, les utilisateurs n'ont pas besoin de passes. Dans le second cas, il faut définir une subvention qui ne sera accordée à aucun utilisateur quel qu'il soit.

2) hook_node_grants()

Qui accorde les subventions (passes) aux utilisateurs ? La réponse est – encore une fois – un hook. Dans ce cas, c'est hook_node_grants(), qui est exécuté à chaque tentative d'accès à un nœud donné. Il utilise deux arguments – le compte utilisateur $account et l'opération – $op. En utilisant ces deux arguments, il détermine la liste des subventions identifiées à l'aide de domaine (clé) et d'identifiant (valeur).

use Drupal\Core\Session\AccountInterface;

function mymodule_node_grants(AccountInterface $account, $op) {
  if ($op == 'view') {
    $grants['realm1'] = array(123);
    return $grants;
  }
}

Remarquez que le hook ci-dessus n'a pas accès à $node. C'est pourquoi le mécanisme des subventions est beaucoup plus rapide, car il ne nécessite pas que le nœud soit chargé à chaque fois qu'il doit être accédé. Grâce à cela, les subventions définies par vous peuvent être prises en compte lors de la construction de vues et de la génération de menus.

Réinitialisation des permissions

Quand vous commencez à travailler avec le mécanisme des subventions, vous pouvez rencontrer plusieurs problèmes inattendus. Lorsque votre autorisation ne fonctionne pas correctement, vous devez d'abord et avant tout essayer de reconstruire la table node_access dans la base de données de Drupal. Vous pouvez le faire en utilisant la fonction node_access_rebuild() ou en allant sur /admin/reports/status/rebuild (vous pouvez trouver ce lien dans le Rapport d'état de votre panneau d'administration).

Si vos subventions ne fonctionnent pas comme prévu après une reconstruction, lisez attentivement la table node_access et vérifiez si elle contient une ligne avec nid=0, gid=0, realm=all. Cette entrée est ajoutée par défaut lors de l'installation de Drupal et elle désactive tout le système de subventions. Cette ligne n'indique pas d'erreur – sans cette entrée, l'accès au contenu de tout le site serait disponible uniquement pour l'administrateur.

Types d'opérations

Vous avez probablement remarqué que j'ai omis les types d'opérations sur un nœud dans ma description des subventions. Vous pouvez décider quels types d'opérations (view/update/delete) seront disponibles pour chaque utilisateur dans les deux hooks présentés ci-dessus (regardez simplement les exemples !) Vous devez vous souvenir d'une chose : si un certain nombre de subventions sont disponibles pour un domaine donné, celle avec le niveau d'accès le plus élevé sera toujours sélectionnée.

Résumé

À la fin de cet article, je veux vous rappeler que tous les mécanismes d'autorisation dans Drupal fonctionnent en parallèle. Vous pouvez donc mélanger toutes les méthodes d'autorisation et de contrôle d'accès ensemble. Il convient également de noter qu'il y a une discussion en cours sur drupal.org pour transformer le système de subventions en quelque chose de plus compréhensible et plus en ligne avec une approche orientée objet. Cependant, nous ne prévoyons pas de changements révolutionnaires dans un avenir proche.

Renvoyé ! :-)

3. Best practices for software development teams