~ 4 min read

Implementing user-specific, role-based access control per node type, per group. (Part 4)

share this story on
Understanding Drupal's node access system and how to hook into it to implement

Implementing role-based node access control in groups.

Overview

As mentioned earlier, Drupal is pretty permissive by default and don’t offer a view page kind of permission for page content type. Moreover, it’s attempt to call other modules to control access to the content type is limited only to the module that created this node so this is not really helpful.

Now consider an example use case – each member of a community should be granted a different role, where-as the roles are given to users per groups. This means that users will not be granted roles site-wide from the user/roles administration page. Rather, each group’s admin will be able to list users and give them some role, resulting in a scenario where users will have different roles in different groups.

To meet this example use case, which is out of the scope of default Drupal or OG behavior, there’s OG Users Roles module. Without diving into the internals of it, the way that it works is by detecting the group context and then altering the global user object’s roles attribute with those that are set for the user in the current group.

Use case put to action

So far so good. OG Users Roles does the job and integrates with OG’s node access just fine.
At this point, it doesn’t really matter what roles the user have inside a group, as long as the user is a member of the group he is able to view all of the groups’ created content.

Let’s take this further – what if we wanted to control the view permission of each content type per role? Imagine the ability to define which roles are able to see (access) which content type. This is now getting interesting, it brings in a more fine-grained access control that provides the site admin to selectively choose which roles may access certain content types, resulting in a use case where even if 2 users are members of the same groups, having different roles each of them, allow them to access content types which the other user can’t.

How to approach this?

Remember the node access scales illustration?

The concept to attach such use case is to compose a grants response with all of the user’s roles across all of the groups he is a member of (the left side of the scales), and compare that against the node_access table which now holds not only the group id, but rather the group id and the role that is allowed to access this node (the right side of the scales).
an array of grants from it (this is illustrated in the left side)

blog_-_implementing_user-specific_role-based_access_control_per_node_type_per_group._2

Looks like a straight-forward approach, except for a little tiny detail – the gid field in the node_access table is an int, not a char, so we can’t actually use the string format of a 50_2 to represent <group_nid>_<role_id>.

Relying on just saving plain numbers for group_nid and role_id, such as 502 doesn’t make much sense either. Think of an entry like 512 => does it represent group id 5 and role id 12 or group id 51 and role id 2?

Further down the rabbit hole

One way to deal with this is to brutally change the gid field from integer to say char(32) but then you have to patch a few more places (a views handler and node.module) to properly use this field type (i.e: saving records as string and not integer).

It’s a very small patch to apply and I’ve tested with this configuration and a node_access table of roughly 65K size against a user grants response of 300 group_nid/role_id options and around these numbers I didn’t find any performance hit. (of course if you do this, don’t forget to rebuild the indexes on node_access)

Back to our story. To deal with this we’re looking for a hashing function where we can encode the ‘50_2’ string.

MD5 you say? nope. Too large for a 32bit integer.
Remember this old one called CRC32 ? Yep, that’s the one that you used back in the day and now it’s coming back for the rescue. It’s right in size and faster than the alternatives: MD5, SHA1/2 etc.

To implement crc32, return the grants from your module already encoded as crc32 as well as define node grant records where the gid field is encoding the <group_nid>_<role_id>.