Profile picture for user admin
Daniel Sipos
17 Jan 2017

The Drupal node access grants system has always been a powerful and flexible way to control access to your nodes. It's been there from Drupal 5 (if not earlier) and it continues to exist in Drupal 8 as we move forward. In this article, I want to quickly highlight this system from a D8 perspective and how I propose to use it in a OOP architecture.

What is it?

The node access grant system is a way by which you can control programatically and very granularly access to all four operations on your Drupal nodes (view, create, edit, delete). It allows to define certain realms of functionality (related to your access requirements) and a set of grants that are required for any of the four mentioned operations, within that realm. Users will then need to posses the grants in the respective realms in order to be granted access.

The two main components of this system are therefore:

  • The implementation of hook_node_access_records() which is called whenever a node is saved (or site-wide permissions rebuilt). It is responsible for storing the access requirements for that given node.
  • The implementation of hook_node_grants() which is called whenever a user is trying to access a node (or a query is being performed in the name of that user). It is responsible for presenting the grants for the current user, which if match the access requirements of the node, allows them access.

The great thing about this node access grants is that it's system-wide in the sense of who checks for the access. In contrast to implementing hook_node_access() which only is called when viewing a node on its canonical URL, the access grants are checked almost everywhere such as views or even custom queries with much ease.

Drupal 8

In Drupal 8 these 2 hooks remain the foundation of the node access grants system, albeit with type hinted parameters. This means that we need to place their implementation inside our .module files.

Node access grants are not used on every site because they serve relatively complex access rules. Complex access rules usually also require a fair bit of calculating what grants a particular node must have for a given realm, as well as whether a given user possesses them. For this very reason I am not so fond of having to put all this logic in my .module file.

So I came up with a basic developer module that defines an interface that has two methods: accessRecords() and grants(). Other modules which want to implement the access grants hooks can instead now create a service which implements this interface and tag it with node_access_grants. My module will do the rest and you won't have to touch any .module file. You can inject whatever dependencies from the container you need and perform whatever logic is needed for determining your grants and access records.

Let me what you think down in the comments. Would love your feedback.

Profile picture for user admin

Daniel Sipos

CEO @ Web Omelette

Danny founded WEBOMELETTE in 2012 as a passion project, mostly writing about Drupal problems he faced day to day, as well as about new technologies and things that he thought other developers would find useful. Now he now manages a team of developers and designers, delivering quality products that make businesses successful.

Contact us

Comments

Rob p 19 Jan 2017 03:29

Examples are like pictures they are worth a 1000 words

So I would love to know of an example of when you had to use it and how.

Matthew Oliveira 20 Jan 2017 16:37

So I see in your module, you

So I see in your module, you've defined a service, whose class is NodeAccessGrantsCollection, that has an addImplementation method. That adds to a $implementations member var on the class and the other methods loop over it collecting grants and accessRecords from all implementations. That seems pretty straightforward. What I don't understand is what calls the addImplementation method in the first place?

Daniel Sipos 21 Jan 2017 22:00

In reply to by Matthew Oliveira (not verified)

The compiler pass

Hey Matt,

That's a container compiler pass that looks at all services tagged with "service_collector" and calls the addImplementation() method on each (or whatever method you specify in the service definition under the call key), for each other service tagged node_access_grants (or whatever you define). It's a nifty feature that allows us to quickly set up scenarios like this without having to define our own compiler pass, making life unnecessarily complicated :)

Norman 06 Mar 2018 13:40

Documentation missing

I'd like to follow the OOP approach while granting access to edit nodes depending on a user's role and the current language.

Unfortunately I don't see where to start as there is no example, no README, no documentation.

Add new comment