Writing a module that handles node access

Last updated on
27 April 2022

This page has not yet been reviewed by Drupal APIs maintainer(s) and added to the menu.

Using Drupal’s built-in node grants and realm access system, you can control which users or user roles can perform different operations such as view, update, and delete on a per node basis. You can apply similar access control to non-node entities via access hooks.

This page provides an example that limits viewing of particular nodes to authenticated users. It should provide an outline and illustrate how to write your own access module. Alternatively you may want to watch Drupal Camp presentation of node access (Youtube).

Let's look first at the node_access table. By default, it looks like this:

  nid | gid | realm | grant_view | grant_update | grant_delete 
 -----+-----+-------+------------+--------------+--------------
    0 |   0 | all   |          1 |            0 |            0

This single row determines that all nodes can be viewed but not updated or deleted. If you use a module that implements node access, this row is usually replaced with one or more rows per node.

If the node_access table becomes out of sync with what is defined in code, you may experience nodes that are unexpectedly hidden or visible. If that happens, or if you change the modules that implement node access, use the admin/reports/status page to rebuild it.

There are two hooks you'll want to implement: hook_node_grants() and hook_node_access_records().

Custom code

We'll start by defining a realm and two grant IDs. Ignore the realm, for now, we'll just use the module name. A grant ID is an arbitrary integer that groups content together in some way. It's often the same as a role ID or taxonomy term, but it doesn't have to be. In this example, we'll define two grant IDs, one for public content and one for private content.

 define('MYMODULE_REALM', 'mymodule');
 define('MYMODULE_GRANT_ID_PUBLIC', 0);
 define('MYMODULE_GRANT_ID_PRIVATE', 1);

In our example, we'll consider node 2 to be private, and all other nodes to be public. We'll allow all users to access public content, and only those with the authenticated user role to access private content.

hook_node_grants()

The hook_node_grants() function is responsible for giving out grants for a given user and operation (view/update/delete). We will return grants for the view operation. We ignore other operations and leave those decisions to another module.

All we do here is examine the user as specified in $account, and provide an array of grant IDs for our realm.

/**
 * Implementation of hook_node_grants().
 */
function mymodule_node_grants($account, $op) {
  // we're only interested in providing rules for viewing content,
  // update and delete can be handled elsewhere by the content module
  // and it's permissions

  if ($op == 'view') {
    if (array_key_exists(DRUPAL_AUTHENTICATED_RID, $account->roles)) {
      // this is an authenticated user, give them both the 'public' and
      // 'private' grant IDs to allow them access to everything
      $grants[MYMODULE_REALM] = array(
        MYMODULE_GRANT_ID_PUBLIC,
        MYMODULE_GRANT_ID_PRIVATE,
      );
    }
    else {
      // this is an anonymous user, give them the 'public' grant ID
      // that allows them access to public nodes
      $grants[MYMODULE_REALM] = array(
        MYMODULE_GRANT_ID_PUBLIC,
      );
    }

    return $grants;
  }
}

hook_node_access_records()

The hook_node_access_records() function is responsible for populating the node_access table. It does this whenever a node is saved, or if the node permissions are rebuilt.

How this works:

  • First, we examine the node to see if it is private or public.
  • Secondly, we return grants accordingly. We could return multiple grants, but we don't need to because our hook_node_grants will return multiple grant IDs if appropriate.
/**
 * Implementation of hook_node_access_records().
 */
function mymodule_node_access_records($node) {
  // use $node to make a decision as to which grants to make.
  // this is a trivial example based on the node ID but illustrates
  // where you would put more meaningful business logic.

  if ($node->nid == 2) {
    $private = TRUE;
  }
  else {
    $private = FALSE;
  }

  if ($private) {
    // this is a private node, so add a rule that allows it to be viewed
    // by those with the 'private' grant ID

    $grants[] = array(
      'realm' => MYMODULE_REALM,
      'gid' => MYMODULE_GRANT_ID_PRIVATE,
      'grant_view' => 1,
      'grant_update' => 0,
      'grant_delete' => 0,
      'priority' => 0,
    );
  }
  else {
    // this is not a private node, so add a rule that allows the
    // 'anonymous user' grant ID view access. We'll assume that
    // mymodule_node_grants() gives authenticated users both
    // 'authenticated user' and 'anonymous user' grant IDs, so there
    // is no need for more than one rule here.

    $grants[] = array(
      'realm' => MYMODULE_REALM,
      'gid' => MYMODULE_GRANT_ID_PUBLIC,
      'grant_view' => 1,
      'grant_update' => 0,
      'grant_delete' => 0,
      'priority' => 0,
    );
  } 

  return $grants;
}

Now let's rebuild the permissions and have a look at the node_access table:

  nid | gid |  realm   | grant_view | grant_update | grant_delete 
 -----+-----+----------+------------+--------------+--------------
    1 |   0 | mymodule |          1 |            0 |            0
    2 |   1 | mymodule |          1 |            0 |            0

The important thing to note here is that authenticated users will have both grant IDs 0 and 1. Therefore they will see both node 1 and node 2. Anonymous users will only have grant ID 0, so will only see node 1.

This does what we want. Log in and you should see nodes 1 and 2. Log out and you should only see node 1.

Help improve this page

Page status: No known problems

You can: