16th October 2015

Drupal 7 Node Access Grants

John Ennew
Technical Director

This labs post describes how to control access to nodes programatically. The node module provides two methods for this.

hook_node_access()

The first is a basic access hook which fires when the full node is accessed. In the example below we grant access to view nodes of type member_article to all users with the custom defined permission 'can view member articles'.

  /**
 * Implements hook_node_access().
 */
function mymodule_node_access($node, $op, $account) {

  // Check that the node is being viewed
  // and is of type member_article and is
  // published.
  if ($op == 'view' && $node->type == 'member_article' && $node->status) {
    if (user_access('can view member articles', $account)) {
        // Grant access to users with the right permission.
    	return NODE_ACCESS_ALLOW;
    }
    else {
        // Deny access to users without the permission.
    	return NODE_ACCESS_DENY;
    }
  }
  
  // This function has nothing to say about
  // all other node access operation and all
  // other node types.
  return NODE_ACCESS_IGNORE;
}

Here we can see that hook_node_access will explicitly allow or deny access to a specific node for the given user if they have the permission.

If any hook_node_access() implementation returns NODE_ACCESS_DENY for a given node then the user will not be able to see it.
If at least one hook_node_access() implementation returns NODE_ACCESS_ALLOW for a given node then the user will be able to see it, as long as noone has returned NODE_ACCESS_DENY.
If no module returns NODE_ACCESS_ALLOW then the default behaviour applies which is normally to allow access if its published but depends on what Grants are defined (more on this later).

The problem with this hook is that it does not fire for every node in a list - only when visiting a node's view, create or edit pages. To fire it for every node in a list would be also be a slow process. So if you created a View listing nodes, it would list nodes which our hook_node_access() function says are unavailable to the user. The user would see the node, click on it and then get an access denied message.

Node Access Grants

Node access grants are the second method for node access control is much more powerful but more complicated to use.

It works by altering queries for nodes to make sure that only nodes the current user has access to are retrieved from the database. Most listing tools in Drupal such as Views will correctly tag their queries so that they only retrieve accessible nodes.  This is done transparently so you don't have to do anything to yourself - it just works.

Here are the basic concepts you need to get your head around.

There are three parts to the node access grants system:

1. Realms
2. Node access records
3. User Grants

1. Realms

A realm can be thought of as a grouping of nodes and users.  It has a name and it also defines the level of access the users in the realm have to nodes within the realm. 

A node can be within a realm. A user can be within a realm. If a user is in the same realm as a node then that user has access to that node. What level of access is defined by the realm.

Nodes and users can be within multiple realms. As long as there is at least one realm a node and user occupy together then the user will have access to the node as defined by that realm. If there are multiple overlapping realms the user will have the highest level of access defined by the realms they are both in.

Access levels defined by a realm are view, update and delete. A realm will simply say that it allows one or more of these operations for all users on all nodes within the realm. A realm must provide at least 1 level of access, either view, update or delete or some combination of the three.

Realms can be partitioned so that users in the realm only have access to a subset of the nodes in the realm. Within a realm, nodes and users can be provided with grant ids. Users must therefore be both within the same realm as a node and be granted the same grant id as a node in order to access it.

If a user is not in any realms then they will have no access to any nodes which are within realms.

If a node is in no realm and is published then the node module will create a special realm for it which all users are a member of and grants view access. Therefore if nothing specifies access to a node by putting it into a realm then it will be accessible by everyone.

2. Node Access Records

So thats enough theory. The first hook we need to define is hook_access_records(). This is run for every node on the system and we are invited to define our realms for each node

  /**
 * Implements hook_node_access_records().
 */
function mymodule_node_access_records($node) {

  $grants = array();

  if ($node->type === 'member_article' && $node->status) {
    $grants[] = array(
      // Say this node is in our realm.
      // By convention we prefix the name of the realm
      // with the module name.
      'realm' => 'mymodule_myrealm',
      // Define the grant id - users in the realm must
      // have this grant id to access this node.
      'gid' => 1,
      // Users who match can view the node.
      'grant_view' => 1,
      // Users who match cannot update the node.
      'grant_update' => 0,
      // Users who match cannot delete the node.
      'grant_delete' => 0,
      // In the case another module provides exactly
      // this access record, the one with the highest
      // priority wins. Usually leave this as 0.
      'priority' => 0,
    );
  }

  return $grants;
}

So what are we defining here?

We have made a realm called mymodule_myrealm in which every node that is of type member_article and is also published will be within. We'll skip over gid as I'll describe it in more detail later. The three grant values define that this realm allows access to view the node but does not grant access to update or delete it.  Note that this does not mean a user will be denied the ability to update or delete a node, only that this realm is not providing that ability. Another realm defined elsewhere may grant the user those abilities for this node.  Unlike hook_node_access() we are not denying access here, only saying access is not provided by this realm.

3. Node grants

The second hook you need to define in order to use Node Access Grants is hook_node_grants(). This hook provides a user account and allows you to specify what realms that user shoud be in.

  /**
 * Implements hook_node_grants().
 */
function mymodule_node_grants($account, $op) {
  $grants = array();

  if (user_access('can view member articles', $account)) {
    // Within our realm users with the correct
    // permission have a gid of 1.
    $grants['mymodule_myrealm'][] = array(1);
  }
  
  return $grants;
}

So what are we defining here?

We have said that users with our "can view member article" permission get put in the mymodule_myrealm and get a single grant id (gid) of 1. This matches the realm and gid provided in the previous hook and so any user with our permission will be able to view nodes of type member_article.

Grant ids

In the simple example above, we have have specified a single grant id (gid) of 1. This could be any number and will not collide with gids in other realms as it is only relevant in our realm. If you are not sub partitioning your realm then just leave this as 1. However, the sub partitioning of a realm is one of the more advanced features of node access records so its useful to understand what this could be used for.

The more complex example below provides a second realm for our member_article nodes which allows update and delete access to the node if the user is the nodes author and also allows the author to view the node when it is unpublished. We do this by specifying the author's uid as the grant id (gid) for this second realm.

  /**
 * Implements hook_node_access_records().
 */
function mymodule_node_access_records($node) {

  $grants = array();

  if ($node->type === 'member_article' && $node->status) {
      $grants[] = array(
      'realm' => 'mymodule_myrealm',
      'gid' => 1,
      'grant_view' => 1,
      'grant_update' => 0,
      'grant_delete' => 0,
      'priority' => 0,
    );
  }
   
  if ($node->type === 'member_article') { 
    $grants[] = array(
      'realm' => 'mymodule_myrealm_author',
      'gid' => $node->uid,
      'grant_view' => 1,
      'grant_update' => 1,
      'grant_delete' => 1,
      'priority' => 0,
    );
  }

  return $grants;
}

/**
 * Implements hook_node_grants().
 */
function mymodule_node_grants($account, $op) {
  $grants = array();	

  if (user_access('can view member articles', $account)) {
    // In the first realm, provide the standard
    // gid to allow view access.
    $grants['mymodule_myrealm'][] = array(1);
    
    // In the second realm, provide the user's uid.
    // For nodes whose grant record matches then
    // the user will have update and delete access as well.
    $grants['mymodule_myrealm_author'][] = array($account->uid);
  }
  
  return $grants;
}
Visual representation of realm and grants

Rebuilding Node Permissions

Whenever a module is enabled which provides both hook_node_access_records() and hook_node_grants() implementations then the node access records need to be rebuilt.  You can do this from the Reports --> Status Screen from within the Drupal interface. Normally enabling such a module sets a variable called node_access_needs_rebuild to TRUE which causes admins to see a message about access permissions needing rebuild within the site.

If as a developer you are not allowed to login to the site as an admin you can force a permission rebuild inside of an update hook like this:

  /**
 * Rebuild node access permissions.
 */
function mymodule_update_7001(&$sandbox) {

  if (!isset($sandbox['sandbox'])) {
    db_delete('node_access')->execute();
  }

  _node_access_rebuild_batch_operation($sandbox);

  $sandbox['#finished'] = $sandbox['sandbox']['progress'] >= $sandbox['sandbox']['max'];

  if ($sandbox['#finished']) {
    node_access_needs_rebuild(FALSE);
  }
}

Conclusion

The Node Access Grants system is a powerful tool. It allows you to define access rights to nodes in Drupal in a way that can be made use of by the whole Drupal eco system. We have mentioned how Views will use it to make sure that only nodes you are have access to appear in the View. Another example is the apache solr module has an add on module called apachesolr_access which ties into the node access grants system so that it will only retrieve results from solr which the user has access to. 

Hopefully this has helped describe the slightly complex world of the Drupal node access grants system and the power it can provide you with for access control on a Drupal website.