Two new hooks were recently committed to HEAD that will give Drupal 7 unprecedented flexibility when it comes to managing multiple node access modules on one site. Up until now, enabling multiple node access modules usually leads to unwanted behavior, most often exhibited by access being granted unexpectedly. This is partially because Drupal ORs access grants, so that a user is granted access to a node so long as one node access module allows it to do so, regardless of the opinion of the other access modules. (Note: Since the node_access table is one of the less understood components of Drupal core, you may want to check out John VanDyk’s Pro Drupal Development for a good introduction to how it works, but diving into api.drupal.org or an existing node access module’s code is probably the best way to grok it.)
For example, consider a site using both Workflow Access (bundled with Workflow) and Taxonomy Access Control Lite (TAC Lite). Suppose that these have been configured such that only role R can access a node in the “Draft” workflow state, and only role S can access a node associated with term T. What happens when a user with only role R tries to access a “Draft” node having term T? You might expect that user to be denied access since they don’t have role S, but not with default core functionality — since Workflow Access grants the user access, it does not matter what TAC Lite has to say about it and the user is in.
This has the unfortunate consequence of discouraging users from using multiple node access modules. There are some workarounds out there that allow it, such as the Module Grants module available for Drupal 6, but the two new hooks for Drupal 7 will put the power in the hands of any node access module maintainer to support compatibility. Let’s take a closer look at these two new hooks.
But first, a little background
These two hooks came out of a node_access working group discussion that took place at DrupalCon Szeged nearly a year ago. Moshe Weitzman started the issue with a patch implementing one of the hooks (hook_node_access_records_alter), but it languished in the issue queue after Dries requested SimpleTests. This is when I discovered the issue, having encountered the problem on a project I’m currently working on that uses both Workflow Access and a second custom node access module. I wrote some SimpleTests (my first!) for the patch and added some improvements based on comments by Dave Cohen and other members of the community, at which point it caught the attention of webchick and reached its critical moment. Ken Rickard, who had already added some great documentation, added the second hook (hook_node_grants_alter) and some upgrade documentation before the final commit.
hook_node_access_records_alter
When a node is saved, node_access_acquire_grants($node)
is called, which writes grants to the node_access table depending on the contents of the $node variable. So, continuing the example from above, if a node is saved with term T, TAC Lite will add a row to node_access indicating which types of access (view, update, or delete) will be granted. TAC Lite may also insert other rows for other terms, and additional node access modules may add other rows as well. (You can usually tell which module inserted a row based on the contents of the ‘realm’ column.) Modules indicate which grants they want to provide with hook_node_access_records, which returns an array of grants. With this new hook, a single drupal_alter
is called after the initial grant array is retrieved to allow modules to modify it by reference:
node.module
Therefore, by providing a mymodule_node_access_records_alter function, you could strip out or alter grants provided by other modules, or add your own. See the API documentation for an example where all grants are stripped out except the module’s own if a node has a term from a particular vocabulary.
hook_node_grants_alter
Even if you don’t want to mess with how grants are written to node_access, with this hook you can modify which grants a user has on the fly. When Drupal is checking whether or not a user has access to a node, it calls hook_node_grants to see what grants a user has for each realm. For example, TAC Lite will let Drupal know if a particular user has access to term T, and if so, it will grant access to that node for that user. So just like the first hook, a single drupal_alter
is called after retrieving the user’s grants array to allow modules to modify it by reference:
node.module
array(0)), $grants);
}
?>
Therefore, by providing a mymodule_node_grants_alter function, you could strip out or alter grants given to the user by other modules, or add your own. See the API documentation for an example that removed all edit and delete grants to users with a certain role, no matter what.
How will these be used?
It’s not entirely clear yet how the community will take advantage of these two hooks. They can be used for very specific and custom modifications, but it’s likely there will be a need for more general functionality such as modifying written grants so that they are AND’d instead of OR’d. But it also doesn’t seem like it would terribly easy task to write a module that does such a thing generically for all node access modules — more likely, maintainers of existing node access modules will use these hooks to add support for other node access modules. But at this point, I’m not entirely sure what standard practice for using these will emerge — will mediation modules appear that handle multiple node access modules in configurable ways? Will it fall to existing maintainers to support other node access modules, and if so, how do they make sure not to conflict with hooks by other maintainers? I have a feeling these questions will be answered by those who use these hooks for the use cases most in demand first.
Using menu_alter instead
These hooks aren’t the only way to modify how Drupal node_access works. For an example of this, check out Rik de Boer’s Module Grants module available for Drupal 6, which uses hook_menu_alter
to override node.module’s node_access
function. By being able to override that function directly, Module Grants does two main things: 1) AND’s grants instead of OR’s; and 2) allows the node_access table to be used for unpublished nodes in addition to published nodes. (Drupal by default disallows access to unpublished nodes except to the node author and to users with the all-powerful ‘administer nodes’ (D6) or ‘bypass node access’ (D7) permissions.)
Module Grants is very useful, and it’s required for the also useful Revisioning module. The problem with this approach is that overriding core functions is a fairly invasive way to modify core functionality — for example, upgrades/fixes to those core functions will not apply to a site using Module Grants until it incorporates the upgrades/fixes itself. Ideally, this functionality would be added using less invasive hooks, which these two new node_access hooks should provide.
WARNING (10/13/2009): Module Grants currently has a critical issue that makes it unsafe to use without some modifications. See http://drupal.org/node/592164.
Does core need more configuration options?
Still, I wonder if it would be a good idea to add more configuration options to core’s node access functionality. For example, I contributed a patch to Module Grants that allows the user to choose whether or not they want grants to be AND’d explicitly (requiring the explicit OK of all node access modules) or implicitly (requiring the OK of node access modules that have something to say about a certain node). I don’t see why core couldn’t add similar configuration options, allowing the user to choose between ORing and ANDing (explicitly or implicitly) grants, or whether or not to apply grants to unpublished nodes. Of course these would have to be treated as advanced options, as this stuff can get quite complex, but I think that they would also be very useful to have without requiring big menu_alter’s such as those provided by Module Grants. The two powerful node_access hooks recently committed to HEAD may end up solving these problems in the long run, but as of yet this remains to be seen.