Drupal Grants, wat moet je met dit 'node access' systeem?

22 Dec 2014

Joris Snoek
Digital Consultant
+31 (0)20 - 261 14 99

Pagina’s in Drupal bekijken, bewerken en verwijderen

Wanneer je enigszins thuis bent in Drupal, zal het duidelijk zijn dat je rechten voor content management kan instellen in de permissie tabel (/admin/people/permissions)

Vink de juiste permissies aan en iedereen krijgt de gewenste rechten om content te bekijken, toe te voegen, te bewerken, of te verwijderen. Oftewel, de zogenaamde CRUD acties: Create, Read, Update, Delete.

So far so good.

Case: berichten in afgeschermde groepen

Maar wat nou als je in groepen wilt samenwerken en berichten binnen een afgeschermde groep wilt plaatsen? Alleen mensen die in betreffende groep zijn ingedeeld mogen deze berichten bekijken.

Onderstaand een voorbeeld: samenwerken aan een project binnen een groep. Binnen deze groep zijn 13 leden die alle 43 berichten mogen bekijken.

Gebruikers die niet lid zijn van deze groep, mogen deze berichten dus nergens te zien krijgen.

 

Aanvullende functies en veiligheid

Daarbij wil je graag globale overzichten, waarin de gebruiker alle berichten kan zien van alle groepen waar hij ingedeeld is. Én, uiteraard: je mag niet door het direct aanroepen van een link het bericht in de groep alsnog kunnen bekijken / bewerken.

Standaard tabel nog werkbaar?

Bij deze wensen wordt de standaard Drupal permissie tabel toch wat lastig.. Er zijn modules, zoals Organic Groups, die in deze vraag kunnen faciliteren. Maar als je lean and mean wilt zijn: programmeer dit met behulp van Drupal Grants.

'Grantify' jouw Drupal systeem

Wellicht is je eerste ingeving: ik maak een View aan en regel middels arguments dat de View alleen berichten uit een bepaalde groep laat zien.

Dat gaat misschien nog net, maar die View moet rekening houden met de ingelogde user: zit die in betreffende groep of niet?

Dat gaat dus niet werken in Views, enkele redenen:

  • De directe link naar de node is nog beschikbaar. 
  • De View vangt de update en delete acties niet af.
  • Als je het al voor elkaar krijgt, de View zal een zooitje worden: onbeheersbaar en niet schaalbaar, een Rube Goldberg machine.

De Drupal native way: Grants

De Drupal Grants API is hier het antwoord, je kunt deze API zien als ‘top level content management security’. Bedoelt voor alle CRUD (create, read, update, delete) acties op alle nodes. Het maakt onderdeel uit van de Drupal core.

En omdat de Drupal core architectuur schaalbaar en niet-redundant is opgezet, zal deze ‘top level security’ naar beneden doorwerken. Alle Drupal core acties omtrent nodes respecteren dit Grants systeem, maar ook de goed opgezette contrib modules houden er rekening mee.

(Een soortgelijke API voor users bestaat helaas nog niet in Drupal 7.)

Een voorbeeld hierin is de Views module: die respecteert het Drupal Grants systeem. Waardoor in alle views over je gehele Drupal systeem ‘de Grants’ in acht worden genomen.

Wat zijn Grants dan precies?

Een Grant kan je zien als een sleutel voor een slot op een deur. Wanneer je de deur openkrijgt dan krijg je toegang tot een bepaalde actie in Drupal, bijvoorbeeld:

  • Het bekijken van een bepaald bericht in een bepaalde groep
  • Het wijzigen van een bepaald bericht in een bepaalde groep
  • Het verwijderen van een bepaald bericht in een bepaalde groep

Toegang zodra

  • Je mag het bepaalde bericht bekijken wanneer je de Grant/sleutel (1) ontvangt
  • Je mag het bepaalde bericht wijzigen wanneer je de Grant/sleutel (2) ontvangt
  • Je mag het bepaalde bericht verwijderen wanneer je de Grant/sleutel (3) ontvangt

Loper

Het is ook mogelijk een Grant te definiëren die op meerdere deuren past, om zo redundantie te voorkomen. Het is bijvoorbeeld logisch als je rechten hebt om nodes te verwijderen, dat je die nodes ook mag bekijken en wijzigen. In dat geval kan je één Grant definiëren, waarmee je alle 3 de deuren openkrijgt.

Uitdelen sleutels

Vervolgens kan je bepalen wie bepaalde Grants (sleutels) krijgt.

Ik hou in onderstaande voorbeeld nog steeds het voorbeeld aan van mensen die berichten willen bekijken in afgeschermde groepen:

  • Grant (1) krijgen users die ingedeeld zijn in betreffende groep
    => zij mogen het bericht in de groep bekijken.
  • Grant (2) krijgen users die ingedeeld zijn in betreffende groep & de permissie ‘edit own message’ hebben
    => zij mogen het bericht wijzigen.
  • Grant (3) krijgen users die ingedeeld zijn in betreffende groep & de permissie ‘delete own message’ hebben
    => zij mogen het bericht verwijderen.

Een stap verder: werken met klanten in een groep

Wanneer je met externe mensen (bijvoorbeeld klanten) samenwerkt aan bijvoorbeeld een project, dan wil je binnen die projectgroep wellicht bepaalde berichten niet laten zien aan die externen.

Hiervoor zul je een extra slot moeten bouwen op de ‘Read' deur, waar gebruikers met de rol ‘klant’ dan géén sleutel van krijgen.

In dit geval voert de gebruiker die het bericht aanmaakt in, dat het betreffende bericht niet gelezen mag worden door klanten:

Aan de hand hiervan kan je een extra slot op het bericht bouwen. Onderstaand is het rode slot bedoeld voor deze case:

Internen (niet-klanten) krijgen de sleutel voor het rode slot, klanten dus niet.

De klanten krijgen alleen de sleutel voor het licht-blauwe slot(1) en niet de sleutel voor het rode slot. Zo kunnen klanten dus niet de berichten bekijken waar een extra (rood) slot op gezet is, oftewel: de ‘niet-klanten’ berichten.

Wanneer is iemand een ‘klant’?

Je kunt met behulp van een permissie bij een rol bepalen of iemand klant is of niet. Dus never nooit niet afvangen op role, dat is een bad practice in Drupal-land.

Technische implementatie

Alright, so far de functionele toelichting, op naar het echte werk: de technische implementatie. Hierin gebruik ik voorbeelden uit onze Drupal distributie OpenLucius.

Access records en grants

De sloten worden gedefinieerd met ‘access records’, welke opgenomen worden in de ‘node_access’ tabel. Grants zijn vervolgens de sleutels die een gebruiker krijgt, of dus juist niet.

Aanmaken access records:

/** * Implements hook_node_access_records(). */function openlucius_core_node_access_records($node) {  if ($node) {    // Get 'show clients' field.    $items = field_get_items('node', $node, 'field_shared_show_clients');    // Default to zero for now.    $client_switch = 0;    // Check if set.    if ($items != FALSE) {      foreach ($items as $item) {        $client_switch = $item['value'];      }    }    // Initialise grants array.    $grants = array();    $openlucius_core_types = openlucius_core_get_content_types();    // We start with setting grants for group.    if ($node->type == "ol_group") {      $grants[] = array(        'realm' => 'openlucius_core_node_access_view',        'gid' => $node->nid,        'grant_view' => 1,        'grant_update' => 0,        'grant_delete' => 0,        'priority' => 0,      );      // Add extra realm for non-client groups.      if ($client_switch == 0) {        $grants[] = array(          'realm' => 'openlucius_core_node_access_view_nonclients',          'gid' => $node->nid,          'grant_view' => 1,          'grant_update' => 0,          'grant_delete' => 0,          'priority' => 10,        );      }    }    // So now we've done Groups, it's time for other content types.    // We need the Group reference nid from these guys, not nid.    // Companies and teams are not referenced to a group in this context, skip    // them.    elseif (in_array($node->type, $openlucius_core_types)) {      // Get reference fields value.      $items = field_get_items('node', $node, 'field_shared_group_reference');      foreach ($items as $item) {        $nid = $item['nid'];      }      // Security message, when nid does not exist, there is no Group      // referenced, that can't be in OpenLucius.      if (!$nid) {        drupal_set_message(t("Security Warning! This node is inconsistent with the OpenLucius Grants system") . ' ' . l($node->title, "node/" . $node->nid), "error");      }      // Set Grants.      else {        $grants[] = array(          'realm' => 'openlucius_core_node_access_view',          'gid' => $nid,          'grant_view' => 1,          'grant_update' => 0,          'grant_delete' => 0,          'priority' => 0,        );        $grants[] = array(          'realm' => 'openlucius_core_node_access_edit',          'gid' => $nid,          'grant_view' => 1,          'grant_update' => 1,          'grant_delete' => 0,          'priority' => 0,        );        // Add extra realm for non-client nodes.        if ($client_switch == 0) {          $grants[] = array(            'realm' => 'openlucius_core_node_access_view_nonclients',            'gid' => $nid,            'grant_view' => 1,            'grant_update' => 0,            'grant_delete' => 0,            'priority' => 10,          );          $grants[] = array(            'realm' => 'openlucius_core_node_access_edit_nonclients',            'gid' => $nid,            'grant_view' => 1,            'grant_update' => 1,            'grant_delete' => 0,            'priority' => 10,          );        }      }    }    return $grants;  }}

Deze functie is een Drupal hook: 'hook_core_node_access_records' en wordt aangeroepen elke keer als een node wijzigt, of als je ‘rebuild permissions’ aanklikt onder /admin/reports/status/rebuild.

De code in de functie zorgt ervoor dat de sloten gedefinieerd worden: deze ‘access records’ worden opgeslagen in de ‘node_access’ tabel van de Drupal database.

Zodra een node wordt opgeslagen, dan wordt deze hook getriggert en doet de code het volgende (versimpeld gezegd):

  • Check of deze node zichtbaar mag zijn voor iemand met de rol ‘client
  • Checken wat voor type node het is: 'Group' node, of een node binnen een Group (bv een 'Message')
  • Veiligheid check: of er wel een Group gekoppeld is aan betreffende item. Binnen OpenLucius leven alle nodes in een Group; geen enkele node staat los van een Group.

Aan de hand van die checks bouwt hij de benodigde sloten op de nodes. Deze worden opgeslagen in de ‘node_access’ tabel, een voorbeeld:

Het uitdelen van de Grants (sleutels)

Het uitdelen van de Grants is tevens een Drupal hook: 'hook_node_grants' en wordt bij elke pagina aangeroepen. Het is dus zaak dat je de load van de code in deze functie zo klein mogelijkheid houdt, hij wordt niet gecachet (!) .

/** * Implements hook_node_grants(). */function openlucius_core_node_grants($account, $op) {  // Load user entity.  $user = user_load($account->uid);  // Load groups that user is granted.  $group_ids = field_get_items('user', $user, 'field_groups');  // Provide the user his group grants.  $grants = array();  foreach ($group_ids as $data) {    // Grants access for non-client nodes.    if (user_access("ol show non-client content")) {      $grants['openlucius_core_node_access_view_nonclients'][] = $data['nid'];    }    if (user_access("create ol_group content")) {      // Check if Group is Archived, then don't give edit perms.      // @TODO: for efficiency, do custom query.      $groupnode = node_load($data['nid']);      if (isset($groupnode->status) && $groupnode->status == 1) {        $grants['openlucius_core_node_access_edit'][] = $data['nid'];        $grants['openlucius_core_node_access_edit_nonclients'][] = $data['nid'];      }    }    // Grants access for all other the nodes in users group.    $grants['openlucius_core_node_access_view'][] = $data['nid'];  }  // Tell good old Drupal about users grants.  return $grants;}

De code in deze functie doet, eenvoudig gezegd, het volgende:

  • Laden van de gegevens van de ingelogde user en de groepen waar hij ingedeeld is;
  • Checken welke rol je hebt;
  • ‘Uitdelen’ van de Grants: de sleutels dus.

Waardoor Drupal ziet of ingelogde user een bepaalde node mag bekijken, bewerken, of verwijderen.

Probeer zelf

Download OpenLucius en experimenteer zelf: https://www.drupal.org/project/openlucius

Wrap up

Ok, pfew, that’s all folks. Vragen of feedback, let me know!

Comments

Nóg meer
kennis nodig?

Check ons ons blog archief >