Understanding Node Access Control in Drupal

This post contains the (pseudo-code) algorithm used by Drupal 5 to determine whether or not a given user may view/edit/delete a given node. The description is based on the Pro Drupal Development book (p. 104), while later parts about access control modules are based on source code. It assumes familiarity with Drupal's concepts of nodes, modules, and hooks.

Node access control algorithm in Drupal core

Columns of node_access:
nid
gid
realm
grant_view
grant_update
grant_delete

First, the user may be granted access based on the information obtained from modules implementing the hook_access callback. If no information is obtained that way, then per-node permissions stored in the node_access table are consulted. This table's contents are maintained incrementally by modules implementing the hook_node_access_records hook, which is invoked whenever a node is saved. Each row in that table has the following meaning: "If the user owns the grant $gid, then she may view/edit/delete node $nid". It is sufficient that the user matches one such row to be allowed access. Accordingly, it is not possible for one module to deny access allowed by another module using this mechanism (unless it implements hook_access to have the first say and skip the grants-based access altogether - which is unfortunately impossible for CCK node types without patching). The set of grants owned by the current user is determined by modules implementing the hook_node_grants callback. This set is tied to the user and its content is independent of any particular node.

Note that the table node_access is shared by multiple independent modules. To avoid collisions, each module's grant IDs are only unique within its namespace, called a realm. Realms play no further role in access control.

The "intrinsic" meaning of a grant ID depends on the module which contributed it and is not relevant for the generic access control module. For example, the Taxonomy Access Control Lite module uses term IDs as grant IDs ("the user has such-and-such terms associated with her; the node can be viewed/updated/deleted by any user who has such-and-such terms associated with her"), while the Workflow module uses role IDs as grant IDs ("the user has such-and-such roles associated with her; the node can be [in its current workflow state] viewed/updated/deleted by any user who has such-and-such roles associated with her").

The incremental updating of the node_access table may (and does) lead to inconsistency if updating a node fails in a non-atomic fashion (that is, without rolling back the database updates that have been already triggered by the update). As a practical example, consider a change of a node's workflow state. This triggers a node update, but may also trigger a user-defined action associated with the state transition. If the action fails (e.g. due to an error in the PHP code), then execution is aborted even before hook_node_access_records has done its job. Drupal 5 lets the administrator rebuild node_access upon request. This is semantically equivalent to clearing the table and saving each node once. Of course, it doesn't help with detecting why the data corruption has occured in the first place, so it should be considered a half-measure.

Here is the simplified access control algorithm (the real implementation can be found in function node_access in node.module):

if ($op == 'update' && !has_access_to_input_format($user))
{
    return 0; // access denied
}

if (has_administer_nodes_permission($user))
{
    return 1; // access granted
}

if (!has_access_content_permission($user))
{
    return 0;
}

if (is_published($node))
{
    $result = hook_access($user, $op, $node); // for node type

    if (!is_null($result))
    {
        return $result;
    }

    // get user's grants
    $grants = hook_node_access_grants($user);

    // check whether the user may view/edit/delete node based on her grants
    return is_permission_granted($grants, $op, $node);
}
else
{
    return is_node_author($user, $node);
}

The ACL module

The ACL module provides a slightly higher-level API for maintaining user-node permissions. It is intended to be used by client modules that need to maintain (possibly directly editable) sets of individual users and grant node access permissions based on whether a user is a member of a particular set. Like any access control module in Drupal, it relies on the node_access mechanism described earlier, and can be thought as an "adapter" or translation layer above it. It offers the following functionality:

  • Provides an API for other (client) modules to maintain so-called ACLs. An ACL is simply a named set of users. The acl_id designating an ACL is automatically generated, while the name of the ACL - and its meaning - can be chosen freely by a client module. The ACL definitions are stored in tables acl and acl_user.
  • Provides an API to associate ACLs with nodes and view/update/delete permissions. For example, a client module might define that users on a particular ACL may update a particular node. This information is stored in table acl_node.
  • Translates contents of the acl and acl_user tables into grant IDs and contents of the acl_node table into node_access records. Here, the string 'acl' is used as the realm name and acl_ids are used as grant IDs.
  • Contains some functions that man be used by client modules to produce UI forms for manual editing of the ACLs.

Despite being a small API module intended for other clients, the ACL module is poorly documented and mainly used by other modules of the same maintainer, one of which is discussed next.

The Forum Access module

The Forum Access module, maintained by the same person as the ACL module, is one of its few known clients. Let us briefly examine the relationship between these two modules.

The simplistic assumption that Forum Access "builds upon" ACL is proved wrong by the fact that it maintains its own 'forum_access' realm in the node_access table and does it without going through the ACL module. User role IDs are used as grant IDs in this realm and the role-forum permissions are maintained privately in the forum_access table (again, not an ACL). This mechanism is used to enforce role-based access control.

However, in addition to that the Forum Access module maintains one ACL per forum, which is named using the forum's ID (which, by the way, happens to be a taxonomy term ID). Users on that ACL are the "forum moderators", with full view/update/delete permissions on all forum nodes. Thus, forum moderation privileges may be granted regardless of the user's system-wide role.

No comments:

Post a Comment