Skip to main content

The node access system

The node access system determines who can do what to which nodes.

In determining access rights for an existing node, NodeAccessControlHandler first checks whether the user has the "bypass node access" permission. 
Such users have unrestricted access to all nodes. 
user 1 will always pass this check.

All implementations of hook_ENTITY_TYPE_access() for node will be called. 
Each implementation may explicitly allow, explicitly forbid, or ignore the access request. 
If at least one module says to forbid the request, it will be rejected. 
If no modules deny the request and at least one says to allow it, the request will be permitted.

If all modules ignore the access request, then the node_access table is used to determine access. 
All node access modules are queried using hook_node_grants() to assemble a list of "grant IDs" for the user.
This list is compared against the table. 
If any row contains the node ID in question (or 0, which stands for "all nodes"), one of the grant IDs returned, and a value of TRUE for the operation in question, then access is granted. 
Note that this table is a list of grants; any matching row is sufficient to grant access to the node.

In node listings (lists of nodes generated from a select query, such as the default home page at path 'node', an RSS feed, a recent content block, etc.), the process above is followed except that hook_ENTITY_TYPE_access() is not called on each node for performance reasons and for proper functioning of the pager system. 
When adding a node listing to your module, be sure to use an entity query, which will add a tag of "node_access". 
This will allow modules dealing with node access to ensure only nodes to which the user has access are retrieved, through the use of hook_query_TAG_alter()
See the Entity API topic for more information on entity queries. 
Tagging a query with "node_access" does not check the published/unpublished status of nodes, so the base query is responsible for ensuring that unpublished nodes are not displayed to inappropriate users.

Note: Even a single module returning an AccessResultInterface object from hook_ENTITY_TYPE_access() whose isForbidden() method equals TRUE will block access to the node. 
Therefore, implementers should take care to not deny access unless they really intend to. 
Unless a module wishes to actively forbid access it should return an AccessResultInterface object whose isAllowed() nor isForbidden() methods return TRUE, to allow other modules or the node_access table to control access.

Note also that access to create nodes is handled by hook_ENTITY_TYPE_create_access().

function hook_ENTITY_TYPE_access(\Drupal\Core\Entity\EntityInterface $entity, $operation, \Drupal\Core\Session\AccountInterface $account) {

  // No opinion.
  return AccessResult::neutral();
}

 

function hook_node_grants

Inform the node access system what permissions the user has.

This hook is for implementation by node access modules. 
In this hook, the module grants a user different "grant IDs" within one or more "realms". 
In hook_node_access_records(), the realms and grant IDs are associated with permission to view, edit, and delete individual nodes.

Grant IDs can be arbitrarily defined by a node access module using a list of integer IDs associated with users.

A node access module may implement as many realms as necessary to properly define the access privileges for the nodes. 
Note that the system makes no distinction between published and unpublished nodes. 
It is the module's responsibility to provide appropriate realms to limit access to unpublished content.

Node access records are stored in the {node_access} table and define which grants are required to access a node. 
There is a special case for the view operation -- a record with node ID 0 corresponds to a "view all" grant for the realm and grant ID of that record. 
If there are no node access modules enabled, the core node module adds a node ID 0 record for realm 'all'. 
Node access modules can also grant "view all" permission on their custom realms; 
for example, a module could create a record in {node_access} with:

$record = array(
  'nid' => 0,
  'gid' => 888,
  'realm' => 'example_realm',
  'grant_view' => 1,
  'grant_update' => 0,
  'grant_delete' => 0,
);
\Drupal::database()
  ->insert('node_access')
  ->fields($record)
  ->execute();

 

And then in its hook_node_grants() implementation, it would need to return:

if ($op == 'view') {
  $grants['example_realm'] = array(
    888,
  );
}

If you decide to do this, be aware that the node_access_rebuild() function will erase any node ID 0 entry when it is called, so you will need to make sure to restore your {node_access} record after node_access_rebuild() is called.

 

function hook_node_grants(\Drupal\Core\Session\AccountInterface $account, $operation) {
  if ($account
    ->hasPermission('access private content')) {
    $grants['example'] = [
      1,
    ];
  }
  if ($account
    ->id()) {
    $grants['example_author'] = [
      $account
        ->id(),
    ];
  }
  return $grants;
}

 

abstract class AccessResult

Value object for passing an access result with cacheability metadata.

The access result itself — excluding the cacheability metadata — is immutable. 
There are subclasses for each of the three possible access results themselves:

When using ::orIf() and ::andIf(), cacheability metadata will be merged accordingly as well.

Namesort descending Modifiers Type Description Overrides
AccessResult::allowed public static function Creates an AccessResultInterface object with isAllowed() === TRUE.  
AccessResult::allowedIf public static function Creates an allowed or neutral access result.  
AccessResult::allowedIfHasPermission public static function Creates an allowed access result if the permission is present, neutral otherwise.  
AccessResult::allowedIfHasPermissions public static function Creates an allowed access result if the permissions are present, neutral otherwise.  
AccessResult::andIf public function Combine this access result with another using AND.Overrides AccessResultInterface::andIf  
AccessResult::cachePerPermissions public function Convenience method, adds the "user.permissions" cache context.  
AccessResult::cachePerUser public function Convenience method, adds the "user" cache context.  
AccessResult::forbidden public static function Creates an AccessResultInterface object with isForbidden() === TRUE.  
AccessResult::forbiddenIf public static function Creates a forbidden or neutral access result.  
AccessResult::getCacheContexts public function The cache contexts associated with this object.Overrides CacheableDependencyTrait::getCacheContexts  
AccessResult::getCacheMaxAge public function The maximum age for which this object may be cached.Overrides CacheableDependencyTrait::getCacheMaxAge  
AccessResult::getCacheTags public function The cache tags associated with this object.Overrides CacheableDependencyTrait::getCacheTags  
AccessResult::inheritCacheability public function Inherits the cacheability of the other access result, if any.  
AccessResult::isAllowed public function Overrides AccessResultInterface::isAllowed 1
AccessResult::isForbidden public function Overrides AccessResultInterface::isForbidden 1
AccessResult::isNeutral public function Overrides AccessResultInterface::isNeutral 1
AccessResult::neutral public static function Creates an AccessResultInterface object with isNeutral() === TRUE.  
AccessResult::orIf public function Combine this access result with another using OR.Overrides AccessResultInterface::orIf  
AccessResult::resetCacheContexts public function Resets cache contexts (to the empty array).  
AccessResult::resetCacheTags public function Resets cache tags (to the empty array).  
AccessResult::setCacheMaxAge public function Sets the maximum age for which this access result may be cached.  
CacheableDependencyTrait::$cacheContexts protected property Cache contexts.  
CacheableDependencyTrait::$cacheMaxAge protected property Cache max-age.  
CacheableDependencyTrait::$cacheTags protected property Cache tags.  
CacheableDependencyTrait::setCacheability protected function Sets cacheability; useful for value object constructors.  
RefinableCacheableDependencyTrait::addCacheableDependency public function   1
RefinableCacheableDependencyTrait::addCacheContexts public function    
RefinableCacheableDependencyTrait::addCacheTags public function    
RefinableCacheableDependencyTrait::mergeCacheMaxAge public function    

 

Developer Notes

  • If you need to create a selective permission on operation (view, update, delete) , you can use both
  • If you need to create a selective permission on entity bundle type with or without operation then hook_ENTITY_TYPE_access is preferable