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

Drupal-Knotenzugriff

Jeder, der Drupal-Entwicklung macht, wird früher oder später auf die Notwendigkeit stoßen, den Zugriff auf Inhalte strenger zu kontrollieren. Die Standardmechanismen für Rollen und Berechtigungen sind sehr flexibel, können jedoch in komplexen Projekten unzureichend sein. Wenn der Zugriff auf Knoten beispielsweise von Feldern abhängt, die einem bestimmten Benutzer zugewiesen sind, müssen Sie auf fortschrittlichere Lösungen zurückgreifen. In Drupal 7 und 8 können wir einen Hook verwenden – hook_node_access() oder einen sogenannten Grants-Mechanismus.

Berechtigungssteuerung mit hook_node_access()

In Fällen, in denen eine erweiterte Berechtigungssteuerung erforderlich ist, ist hook_node_access() die beliebteste Wahl. Es gibt einige geringfügige Unterschiede zwischen Drupal 7 und 8, aber die Funktionsweise ist mehr oder weniger dieselbe. Es nimmt drei Argumente an: $node, $op und $account.

Bei jedem Versuch, auf einen bestimmten Knoten zuzugreifen, wird Drupal mymodule_node_access() in allen Modulen aufrufen und prüfen, ob ein bestimmter Benutzer $account alle notwendigen Berechtigungen hat, um $op in einem $node auszuführen. Der Hook sollte eine der folgenden Antworten zurückgeben:

return AccessResult::allowed(); // (oder NODE_ACCESS_ALLOW in Version 7)
return AccessResult::forbidden(); // (oder NODE_ACCESS_DENY in Version 7)
return AccessResult::neutral(); // (oder NODE_ACCESS_IGNORE in Version 7)

Einfach, aber effektiv, nicht wahr? So sieht es in der Praxis aus (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();
}

Es scheint, dass die Zugriffskontrolle mit hook_node_access() selbst für die fortschrittlichsten Anforderungen ausreichen würde, aber wie man so sagt, gibt es keine Rose ohne Dornen. Das Überprüfen einer langen Liste von Knoten nacheinander wird sicherlich die Effizienz negativ beeinflussen. Insbesondere die Views- und Menu-Module können es sich nicht leisten, diesen Hook zu oft zu nutzen. Aus Optimierungsgründen beschränkten die Entwickler von Drupal den Aufruf von hook_node_access() nur auf Knoten, die im „Voll“-Modus angezeigt werden. In allen anderen Fällen müssen Sie sich auf das standardmäßige Rollen- und Berechtigungssystem verlassen oder weniger ressourcenintensive Zugriffskontrollen verwenden.

Access Grants

Obwohl das Funktionsprinzip von hook_node_access() sehr einfach zu verstehen ist, ist Access Grants ein weitaus komplexeres Thema. Einige Blogger versuchen, es mit einer Metapher für Türen, Schlösser und Schlüssel darzustellen. Persönlich denke ich, dass dies die Wahrheit etwas zu sehr strapaziert. Deshalb werde ich einen Vergleich zu einer geheimen Militärbasis ziehen.

Achtung! Was hat eine Militärbasis mit Drupal gemeinsam? Nun, beide haben zum Beispiel eingeschränkte Bereiche, auf die man nur mit entsprechenden Berechtigungen und Genehmigungen zugreifen kann. Was noch wichtiger ist, in beiden Fällen erhalten die Benutzer und Besucher Pässe, die den Umfang dessen, was sie nach dem Betreten sehen oder tun können, genau definieren.

Im standardmäßigen Berechtigungssystem von Drupal, haben wir einen einzelnen Wächter, der die Rolle eines Benutzers auf seinem Ausweis überprüft. Die Rolle definiert alle Aktionen, die der Benutzer ausführen kann (z. B. Lesen eines Knoten des Typs A, Lesen und Schreiben eines Knoten des Typs B...)

In mymodule_node_access() gibt es mehrere Wächter, einen für jedes Modul. Jeder identifiziert den Benutzer und bestimmt anhand ausgewählter Kriterien, ob ihm der Zugriff gewährt wird. Jeder Wächter hat vollen Zugriff auf die Benutzerdaten sowie auf den geschützten Bereich (Knoten) – ihre Entscheidung basiert auf all diesen Faktoren.

Was ist die Rolle der erwähnten Access Grants? Sie teilen den Autorisierungsprozess in zwei separate Teile, die von zwei separaten Hooks bedient werden. Die Art und Weise, wie sie in Drupal 7 und 8 verwendet werden, ist fast identisch.

1) hook_node_access_records()

Der erste Hook wird aktiviert, wenn der Knoten gespeichert wird. Er hat ein Argument – $node. Die Rolle eines Hooks besteht darin, die Liste der sogenannten Grants zu bestimmen – Pässe, die vorgezeigt werden müssen, wenn versucht wird, auf den Knoten zuzugreifen.

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;
}

Jeder Grant hat seinen eigenen Bereich. Der Benutzer, der versucht, auf einen bestimmten Knoten zuzugreifen, muss in jedem definierten Bereich mindestens einen Grant (Pass) haben. Wenn Drupal basierend auf allen gefundenen Hooks die folgende Liste von Grants erstellt:

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

Der Benutzer muss mindestens einen realm1-Pass und einen realm2-Pass haben, um auf den Knoten zuzugreifen.

Kompliziert? Ich werde versuchen, es mit meinem militärischen Vergleich zu veranschaulichen. Angenommen, unsere geheime Basis hat mehrere Abteilungen (wir werden sie nutzen, um das Konzept von Bereichen zu vermitteln). Jede Abteilung hat ihren eigenen Wächter am Eingang zu jedem eingeschränkten Bereich. Ein Benutzer, der Zugriff auf einen bestimmten Bereich haben möchte, muss zu jedem Wächter gehen und nachweisen, dass er über einen entsprechenden Pass (also ein Grant) verfügt. Wenn der Wächter feststellt, dass der Pass des Benutzers den Anforderungen seiner Abteilung entspricht, lässt er den Benutzer weitergehen. Wenn der Benutzer keinen Pass hat, kann er den Bereich nicht betreten und wird von anderen Wächtern nicht geprüft.

Warum ist dieser Ansatz schneller? Weil der Wächter die Details der Autorisierung (oder überhaupt keine Zugriff auf den Knoten) nicht kennen muss, er muss nur die Pässe vergleichen.

Was ist ein Realm?

Ein Realm (oder wie ich es in meiner militärischen Metapher nannte – eine Abteilung) kann jede Zeichenkette sein, in der Regel wird der Name eines Moduls verwendet. Ein solcher Ansatz macht das Grants-System irgendwie sauberer und verständlicher – der Benutzer muss für jedes Modul, das seine eigenen Zugriffsregeln für Knoten mit hook_node_access_records() definiert, einen Grant (Pass) haben. Sie können diese Konvention verwenden, wenn Sie sie nicht für zu stark vereinfacht halten.

Alles oder nichts

Bei der Verwendung von Autorisierungsmechanismen entsteht manchmal die Notwendigkeit, zwei extreme Szenarien zu verwenden: Zugang für alle oder nur für den Administrator. Im ersten Fall muss lediglich eine leere Liste von Grants in einem Hook zurückgegeben werden. Dies bedeutet, dass die Benutzer in einem bestimmten Bereich keine Pässe benötigen. Im zweiten Fall muss ein Grant definiert werden, der keinem Benutzer gewährt wird.

2) hook_node_grants()

Wer gewährt die Grants (Pässe) an die Benutzer? Die Antwort ist – wieder – ein Hook. In diesem Fall ist es hook_node_grants(), der bei jedem Versuch, auf einen bestimmten Knoten zuzugreifen, ausgeführt wird. Er verwendet zwei Argumente – Benutzerkonto $account und die Aktion – $op. Mit diesen beiden Argumenten wird die Liste der Grants ermittelt, die mit Realm (Schlüssel) und Bezeichner (Wert) identifiziert werden.

use Drupal\Core\Session\AccountInterface;

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

Beachten Sie, dass der oben genannte Hook keinen Zugriff auf $node hat. Aus diesem Grund ist der Grants-Mechanismus viel schneller, da der Knoten nicht jedes Mal geladen werden muss, wenn darauf zugegriffen wird. Dank dessen können Grants, die Sie definieren, bei der Erstellung von Ansichten und beim Generieren von Menüs berücksichtigt werden.

Berechtigungszurücksetzung

Wenn Sie beginnen, mit dem Grants-Mechanismus zu arbeiten, können Sie auf mehrere unerwartete Probleme stoßen. Wenn Ihre Autorisierung nicht ordnungsgemäß funktioniert, sollten Sie in erster Linie versuchen, die Tabelle node_access in der Drupal-Datenbank neu aufzubauen. Sie können dies mithilfe der Funktion node_access_rebuild() tun oder indem Sie zu /admin/reports/status/rebuild gehen (Sie finden diesen Link im Statusbericht in Ihrem Administrationsbereich).

Wenn Ihre Grants nach einem Neuaufbau nicht wie beabsichtigt funktionieren, lesen Sie die node_access-Tabelle gründlich durch und prüfen Sie, ob sie eine Zeile mit nid=0, gid=0, realm=all enthält. Dieser Eintrag wird standardmäßig während der Drupal-Installation hinzugefügt und schaltet das gesamte Grant-System ab. Diese Zeile weist nicht auf einen Fehler hin – ohne diesen Eintrag wäre der Zugriff auf Inhalte auf der gesamten Website nur für Administratoren verfügbar.

Arten von Operationen

Wahrscheinlich haben Sie bemerkt, dass ich in meiner Beschreibung der Grants die Arten von Operationen auf einem Knoten weggelassen habe. Sie können in beiden oben vorgestellten Hooks entscheiden, welche Arten von Operationen (anzeigen/aktualisieren/löschen) für jeden Benutzer verfügbar sein sollen (siehe einfach die Beispiele!) Sie müssen eine Sache beachten: Wenn mehrere Grants für einen bestimmten Bereich verfügbar sind, wird immer derjenige mit dem höchsten Zugriffslevel ausgewählt.

Zusammenfassung

Am Ende dieses Artikels möchte ich Sie daran erinnern, dass alle Berechtigungsmechanismen in Drupal parallel arbeiten. Sie können daher alle Autorisierungs- und Zugriffskontrollmethoden miteinander kombinieren. Es ist auch erwähnenswert, dass auf drupal.org eine laufende Diskussion darüber herrscht, das Grant-System in etwas zu verwandeln, das leichter zu verstehen und besser mit einem objektorientierten Ansatz im Einklang steht. Allerdings sind in naher Zukunft keine revolutionären Änderungen zu erwarten.

Entlassen! :-)

3. Best practices for software development teams