Using multiple node access modules just got a lot easier, maybe

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

<?php
function node_access_acquire_grants($node) {
 
$grants = module_invoke_all('node_access_records', $node);
 
// Let modules alter the grants.
 
drupal_alter('node_access_records', $grants, $node);
 
/*** Some code dealing with the default grant and grant priorities. ***/
 
node_access_write_grants($node, $grants);
}
?>

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

<?php
function node_access_grants($op, $account = NULL) {
 
/*** Some code populating $account. ***/
  // Fetch node access grants from other modules.
 
$grants = module_invoke_all('node_grants', $account, $op);
 
// Allow modules to alter the assigned grants.
 
drupal_alter('node_grants', $grants, $account, $op);

  return

array_merge(array('all' => 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.

agentrickard wrote 39 weeks 4 days ago

For examples of how these might be used, look at the D6 releases of both Domain Access and OG, which have versions of these hooks. (They were introduced in Domain Access for D5 to provide the Domain Strict module, which is one of two current examples I know of; the other example is Domain Access Advanced, which totally discards the default DA behavior in favor of its own grant mechanism.)

The theory is that node access is such a complex issue, we should enable default behaviors for our modules, but not lock sites into using them.

The grand vision is something like Module Grants without the menu_allters, as you suggest.

Marco Carbone wrote 39 weeks 4 days ago

Thanks, I'll take a look at those modules' implementations. Your examples seem to be more in the per-module realm than the generic node access options realm.

Berdir wrote 39 weeks 4 days ago

Maybe using the features that DBTNG provides could help?

For example, it would be possible to add a specific alter tag to the query in node_access, and all queries that access nodes have the node_access tag anyway.

It would require quite a bit code, but it should be possible to replace the db_or() conditions with db_and(). And adding additional conditions would be easy.

http://api.drupal.org/api/function/node_access/7

Marco Carbone wrote 39 weeks 4 days ago

True, using hook_query_alter is yet another way to offer node access options and is less invasive then a menu_alter.

Moshe Weitzman wrote 39 weeks 3 days ago

yeah, node access control is actually implemented with query_alter in d7. i had not thought of changing the db_or thing but thats a good idea.

Donny Nyamweya wrote 36 weeks 3 days ago

My experience with the issue of access permissions defaulting to OR has been mainly with the use of Organic Groups and Domain Access modules in Drupal 5. The same phenomenon (outllined in the examples above) happens and OG restrictions are ignored in preference to Domain Access (TRUE) settings.

Unfortunately for me (and my current project/issue at hand), Module Grants is only for Drupal 6 and so it cannot help in my situation. The Domain Access module author provides/recommends a patch to Drupal core to solve this problem, but patching core introduces update issues, especially in mass maintenance setups.

Marco Carbone wrote 36 weeks 2 days ago

Since Module Grants depends on a menu_alter, it can't be backported to Drupal 5. I think your only option here is to apply that core patch. If you use CVS to manage your Drupal core installation, dealing with a few small core patches isn't a problem. And the likelihood of an upcoming security update to Drupal 5 interfering with that patch is rather small.

Seth Cohn wrote 35 weeks 5 days ago

Actually, Marco, after playing with Module Grants, which is a brilliant idea, I think a D5 version is (maybe) possible, especially because MG is also lacking a piece of the puzzle, which I realized when I discovered that db_rewrite_sql wasn't implemented by MG leading to very confusing results.

Essentially, and I'll follow up on this in the MG queue:

The normal node_access check being an OR is always going to be more lenient than any MG style AND check of node access. This means that implementing hook_access will also work correctly: even if the normal node access check says "Ok, it's got a grant view", if you hook in a more limited (ie an AND) check, it can fail correctly, and need never worry about the main access check failing when the MG check is going to pass.

That's the current problem with db_rewrite_sql: MG doesn't implement this (yet - patch coming I hope), meaning that the normal node_db_rewrite_sql runs with 'normal' node access checks, and says Yes in some places, Node X is granted viewable to the user, but when the viewer attempts to view it, it fails by the MG node access check. Implementing db_rewrite_sql in MG, so MG adds additional joins (for the ANDs) for each realm should mean that the SQL now will correctly match the actual nodes that will pass an individual access check.

Since D5 implements both hook_access (but lacking the account arg, which might be the critical failure here), and hook_db_rewrite_sql, it might be possible to do a limited D5 version, but I suspect it'll be missing something in a few places, if you need to check things beyond the current user (due to the above missing arg) The menu_alters are a problem, but not for this piece of the MG functionality.

Contact Us

About Marco Carbone

Marco Carbone is a developer with Advomatic, and is the maintainer of the Slot Machine module. He lives in Reno, NV.

Advomatic on Twitter