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.
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
AccessResult:: |
public static | function | Creates an AccessResultInterface object with isAllowed() === TRUE. | |
AccessResult:: |
public static | function | Creates an allowed or neutral access result. | |
AccessResult:: |
public static | function | Creates an allowed access result if the permission is present, neutral otherwise. | |
AccessResult:: |
public static | function | Creates an allowed access result if the permissions are present, neutral otherwise. | |
AccessResult:: |
public | function | Combine this access result with another using AND.Overrides AccessResultInterface:: |
|
AccessResult:: |
public | function | Convenience method, adds the "user.permissions" cache context. | |
AccessResult:: |
public | function | Convenience method, adds the "user" cache context. | |
AccessResult:: |
public static | function | Creates an AccessResultInterface object with isForbidden() === TRUE. | |
AccessResult:: |
public static | function | Creates a forbidden or neutral access result. | |
AccessResult:: |
public | function | The cache contexts associated with this object.Overrides CacheableDependencyTrait:: |
|
AccessResult:: |
public | function | The maximum age for which this object may be cached.Overrides CacheableDependencyTrait:: |
|
AccessResult:: |
public | function | The cache tags associated with this object.Overrides CacheableDependencyTrait:: |
|
AccessResult:: |
public | function | Inherits the cacheability of the other access result, if any. | |
AccessResult:: |
public | function | Overrides AccessResultInterface:: |
1 |
AccessResult:: |
public | function | Overrides AccessResultInterface:: |
1 |
AccessResult:: |
public | function | Overrides AccessResultInterface:: |
1 |
AccessResult:: |
public static | function | Creates an AccessResultInterface object with isNeutral() === TRUE. | |
AccessResult:: |
public | function | Combine this access result with another using OR.Overrides AccessResultInterface:: |
|
AccessResult:: |
public | function | Resets cache contexts (to the empty array). | |
AccessResult:: |
public | function | Resets cache tags (to the empty array). | |
AccessResult:: |
public | function | Sets the maximum age for which this access result may be cached. | |
CacheableDependencyTrait:: |
protected | property | Cache contexts. | |
CacheableDependencyTrait:: |
protected | property | Cache max-age. | |
CacheableDependencyTrait:: |
protected | property | Cache tags. | |
CacheableDependencyTrait:: |
protected | function | Sets cacheability; useful for value object constructors. | |
RefinableCacheableDependencyTrait:: |
public | function | 1 | |
RefinableCacheableDependencyTrait:: |
public | function | ||
RefinableCacheableDependencyTrait:: |
public | function | ||
RefinableCacheableDependencyTrait:: |
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