For the past two years, Amherst College has been working on a module which provides a granular, hierarchical, menu system with Unix-like access controls. This is more fully described here: http://groups.drupal.org/node/10231

As mentioned on that page, we would like to share this code with the Drupal community. The level of interest we've received from other educational and corporate institutions, as well as from the Boston user group, seems to imply that it has the potential to be widely adopted.

However, there is one issue that concerns us. As currently implemented, a patch to the node_access() function is required. We have been following some other discussions about changes in the node access scheme for D7, but have yet to see any that would preclude the need for this patch. Here's why:

Because our content is created by thousands of users of vastly differing ability and position within the organizational structure of the college, our module uses a hierarchical access control system -- similar to what Unix uses for subdirectories. Access to a particular node depends on a user's permissions to its container and all of that container's parents.

It would, therefore, be extremely difficult, if not impossible, to represent this as a single SQL query to the node_access table. Even if it could be done, the storage requirements for all the possible permutations would be astronomically huge, given that we have 36,000 users, 16,000 groups, 58,000 containers, and 41,000 nodes on our site.

The current hook_access() is also not applicable to our situation, since a module's hook_access() only gets called when it is the module that defines the particular node type. Our module needs to be able to return a definitive yes/no decision about any node.

Instead, we are proposing a simple extension of hook_nodeapi(), which adds the mode 'access'. In this scheme, the $a3 parameter receives the access mode being requested and $a4 receives the user account. If any module returns a boolean value, that value is interpreted as the "end all and be all", and the node_access table is not considered:

--- node.module.orig	2008-06-18 09:43:11.000000000 -0400
+++ node.module	2008-06-18 10:41:25.000000000 -0400
@@ -2080,16 +2080,22 @@ function node_access($op, $node, $accoun
   $module = node_get_types('module', $node);
   if ($module == 'node') {
     $module = 'node_content'; // Avoid function name collisions.
   }
   $access = module_invoke($module, 'access', $op, $node, $account);
   if (!is_null($access)) {
     return $access;
   }
+  $access = node_invoke_nodeapi($node, 'access', $op, $account);
+  foreach ($access as $bool) {
+    if (is_bool($bool)) {
+      return $bool;
+    }
+  }
 
   // If the module did not override the access rights, use those set in the
   // node_access table.
   if ($op != 'create' && $node->nid && $node->status) {
     $grants = array();
     foreach (node_access_grants($op, $account) as $realm => $gids) {
       foreach ($gids as $gid) {
         $grants[] = "(gid = $gid AND realm = '$realm')";

While we would be interested in discussing the possibility of alternate schemes which improve upon the existing node_access system, we know that this would require a substantial amount of debate and code change.

On the other hand, the approach outlined above is an easy win. It involves minimal code change, and has no effect upon backward-compatibility. Since there is a good chance that other modules would also benefit from this change, I would like to propose that it be made to core in D7.

CommentFileSizeAuthor
#4 node_access.patch.txt946 bytesgribnif

Comments

catch’s picture

Status: Needs review » Needs work

Thanks for submitting this, however it doesn't look like your patch made it.

moshe weitzman’s picture

somebodysysop’s picture

@Gribnif:

I took this path some time ago when I was trying to come up with a way to get TAC and OG working in a way that made sense. I used the Extensible Node Access/Authorisation Capability http://drupal.org/node/122173 to great effect.

The problem is, hook_nodeapi only took care of 'view'. I ran into a whole ton of problems when it came to create, update and list operations. These required some fairly athletic hook_db_rewrite_sql excercises in order to get the node access system I was looking for. I ended up re-organizing my access using node_access_grants() and the Multiple Node Access patch: http://drupal.org/node/196922. At least in my case, this worked much better within the Drupal scheme of things.

My question is: How is node access for 'create', 'update' and 'list' operations handled in your application? hook_nodeapi('access'), unless I am mistakened, is not used for any of these. That is, if I create a view of all nodes on my site, hook_nodeapi('access') will not prevent any user from seeing a 'list' of nodes that he otherwise would not have the ability to 'view'. So, how do you handle this?

gribnif’s picture

StatusFileSize
new946 bytes

@catch, I'm attaching the patch though, as Moshe has pointed out, it turns out to be almost an exact duplicate of what others had already proposed. I still need to take some time to fully digest the final version of the patch in http://drupal.org/node/196922, though I suspect it will not achieve what we need to do.

gribnif’s picture

@SomebodySysop, create and update operations are fairly easy: our module uses hook_menu_alter() to take full control over them. It prevents URLs like the built-in node/add from working, and instead creates URLs like mm/[container ID]/node/add, where it presents the user with a slightly different version of the regular node/add form. We pretty much had to do it this way, because we also allow admins to limit what content types can be associated with a container.

List operations are another story entirely. We basically try to avoid using them in the context of the current user, and instead maintain a list of the anonymous user's read permission to each node, which gets updated during a cron run. Currently, the only place where this is used is in a "recently added content" list. It returns only nodes that can be read by the anonymous user (a subset of what the current user can read.) This is obviously a far from perfect solution, but we have yet to get any complaints from users.

somebodysysop’s picture

I still need to take some time to fully digest the final version of the patch in http://drupal.org/node/196922, though I suspect it will not achieve what we need to do.

Again, having used both patches extensively, and having done a great deal of work on the http://drupal.org/node/196922 patch, I can say that the latter patch is not going to get you where you want to go without a significant redesign of your architecture.

The first patch, Extensible Node Access/Authorisation Capability, allows you to implement access control on a per-node 'view' basis. I used it with great success in easily controlling what nodes users could see. But, as you recognize, it does nothing with respect to listing and updating controls.

The second patch. Multiple Node Access, allows you to implement access control on the node_access level. In order to use the second patch, your module must first generate node_access_grants and establish their realm(s). This, effectively, resolves all of your node_access concerns, but creates a new one: How to implement Monster Menus using node_access rather than nodeapi? Judging by the success of your current implementation, doesn't sound like a path you want to go down.

slosa’s picture

sub