Drupal Grants, what to do with this node access system?

Open Source Drupal


Node Author

Viewing, editing and deleting pages in Drupal

When you have some experience with Drupal it will be clear that you can set your rights for content management in the permission table (/admin/people/permissions).

Check the appropriate permissions and everyone will get the required rights to view, add, edit or delete content. In other words the so-called CRUD actions: Create, Read, Update, Delete.

So far so good.

Case: messages in protected groups

But what if you want to work in groups and want to post messages within a protected group? Only people who are part of this group can view these messages.

Below you will find an example: working on a project with a group. This group consists of 13 members who are allowed to view all the 43 messages.

Users who are not a member of this group, cannot see these messages anywhere.

Additional functions and safety

Additionally you would like to have global overviews in which users can view all the messages of all the groups he or she is a member of. And, of course: it should not be possible to view/edit the message in the group anyway by clicking on a link.

Is the standard table still working?

Combined with these needs the standard Drupal permission table becomes a bit tricky... There are modules, such as Organic Groups, that can facilitate this demand. But if you want to be lean and mean then program this with Drupal Grants.

'Grantify' your Drupal system

Perhaps your first thought is to make a View and rule via arguments that the View only shows messages from a certain group.

This could work, but this View has to take into account the logged in user: is he or she part of that group or not?

So this will not work in Views. Below you will find a few reasons why:

  • The direct link to the node is still available.
  • The View is not covering the update and delete actions.
  • If you would be able to manage this then the View will become a mess: uncontrollable and not scalable, a Rube Goldberg machine.

The Drupal native way: Grants

The Drupal Grants API is the answer, you can see this API as ‘top level content management security’, intended for all CRUD (create, read, update, delete) actions on all nodes. It is part of the Drupal core.

And because the Drupal core architecture is scalable and non-redundant, this top level security will trickle down. All Drupal core actions on nodes respect this Grants system, but also the well-designed contrib modules are taking it into account.

(A similar API for users is unfortunately not yet existing in Drupal 7.)

An example is the Views module: it respects the Drupal Grants system. 'The Grants' are therefore taken into account in all the views in your Drupal system.

What exactly are Grants?

You can compare a Grant with a key for a door lock (we saw this analogy at Phase2). When opening the door you will be given access to a certain action in Drupal, for example:

  • Viewing a certain message in a group
  • Editing a certain message in a group
  • Deleting a certain message in a group

When is access given?

  • You can view the message when receiving the Grant/key (1)
  • You can edit the message when receiving the Grant/key (2)
  •  You can delete the message when receiving the Grant/key (3)

Master key

It is also possible to define a Grant fitting on multiple doors in order to avoid redundancy. For example, it is logical that when you have rights to delete nodes, that you can also view and change them. In this case you can define one Grant, with which you can open all 3 doors.

Assigning keys

Then you can decide who obtains certain Grants (keys).

The below example is still based on people who want to view messages in protected groups:

  • Grant (1) is obtained by users who are assigned to that group
    => they can view the message in the group.
  • Grant (2) is obtained by users who are assigned to that group & have the permission to ‘edit own message’
    => they can edit the message.
  • Grant (3) is obtained by users who are assigned to that group & have the permission to ‘delete own message’
    => they can delete the message.

One step further: working with clients in a group

For example, when you are working with external people (for example clients) on a project, you might not want to show certain messages to those third parties.

You will need to build an extra lock on the ‘Read' door for this that cannot be accessed by users with the role of ‘client’.

In this case the user creating the message will enter that this message cannot be read by clients:

Then you can make an extra lock on the message. Below you will see the red lock intended for this case:

Internals (non-clients) will receive the key for the red lock, clients not.

Clients only receive the key of the light blue lock (1) and not the key of the red lock. In this way clients cannot view the messages with an additional (red) lock, or the ‘non-client’ messages.

When is a person a ‘client’?

It can be determined if a person is a client or not by assigning a permission for a role. So never rely on the role, this is a bad practice in the world of Drupal.

Technical implementation

Alright, this was the functional explanation. Now we are continuing with the real thing: the technical implementation. I will use examples from our Drupal distribution platform OpenLucius.

Access records and grants

The locks are defined with ‘access records’, which are included in the ‘node_access’ table. Grants are the keys a user will receive or not.

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

This feature is a Drupal hook: 'hook_core_node_access_records' and is activated every time a node is changing, or when clicking ‘rebuild permissions’ under /admin/reports/status/rebuild.

The code in the function ensures that the locks are defined: these ‘access records’ are stored in the ‘node_access’ table of the Drupal database.

Once a node is stored, this hook is triggered and the code will (simply put) do the following:

  • Check whether this node should be visible for anyone with the role ‘client’
  • Check the type of node: 'Group' node or a node within a group (f.e. a 'Message')
  • Safety check: whether there is a Group linked to that item. In OpenLucius all nodes are existing in a Group; not a single node is disconnected from a Group.

On the basis of these checks the required locks will be built on the nodes. These are stored in the ‘node_access’ table. An example:

Assigning the Grants (keys)

Assigning the Grants is also a Drupal hook: 'hook_node_grants' and is activated at each page. It is therefore important to keep the load of the code in this feature as little as possible, it is not cached (!)


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

Simply put, the code in this function is doing the following:

  • Loading the data of the logged in user and groups to which he/she is assigned;
  • Checking your role;
  • ‘Assigning’ the Grants: the keys.

This allows Drupal to see if the logged in user may view, edit or delete a particular node.

Try it

Download OpenLucius and experiment yourself: https://www.drupal.org/project/openlucius

Wrap up

Ok, phew, that’s all folks. Questions or feedback? Let me know!