0

On a Vanilla Drupal install (tested on D8 and D9), this will print "1":

drush eval 'echo node_access_view_all_nodes() . PHP_EOL;'

Stepping through the code reveals that it happens because there is a default grant for ALL nodes (nid = 0) for anonymous users in the node_access table (as far as I can see).

My question is about the rationale of behind this. The function description states:

Determines whether the user has a global viewing grant for all nodes.

Why would the anonymous user have the permission to view all nodes by default? Why does this not correspond to what I see when trying to access various nodes as the anonymous user? Some nodes (e.g. unpublished nodes or anything blocked by a node_access hook) do return 403 when trying to load them as the anonymous user, so it obviously does not have access to all nodes. Is there another level of access checking hidden somewhere?

2
  • I just looked through the code for the function (api.drupal.org/api/drupal/core%21modules%21node%21node.module/…). It does not contain any rules about anonymous users. It's a function that acts dynamically based on hooks implemented (or not) by the modules on your system. We cannot know the modules implemented on your system, so it's impossible to answer this question. – Jaypan Jun 2 '20 at 15:10
  • I will rephrase the question! – rudolfbyker Jun 2 '20 at 15:14
2

whenever drupal needs to check if a current user has the right or not to see a specific node it passes through this process :

1. check if current user bypass node access

In determining access rights for a node, \Drupal\node\NodeAccessControlHandler first checks whether the user has the "bypass node access" permission. Such users have unrestricted access to all nodes. user 1 (administrator) will always pass this check. enter image description here

2.check Hook_node_access implementations

Next, all implementations of hook_node_access() 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,

in the Example bellow whenever any user(except admin) tries to view a node we will check if field_prevent_webmaster is checked the user will get access forbidden message.

/**
 * Implements hook_ENTITY_TYPE_access
 */
function modul_name_node_access(\Drupal\node\NodeInterface $node, $operation, AccountInterface $account) {

  // 1.limit the action according to the  operation performed on the requested entity
  switch($operation){
    case 'view': // when a user visit a node page ex : /node/52

      // 2.limit the current operation according to the current account information
      if( in_array('webmaster',$account->getRoles()) ){
        //3. then further limit the current operation according to the current requested node entity field
        // ex : if field_prevent_webmaster is checked prevent a user form
        // viewing the node through the /node/{id}
        if($node->hasField('field_prevent_webmaster') ) {
          if ($node->field_prevent_webmaster->value == TRUE) {
            return AccessResult::forbidden(); // send Access denied message to the client
          }
        }

        if($node->hasField('field_allow_webmaster') ) {
          if ($node->field_allow_webmaster->value == TRUE) {
            return AccessResult::allowed(); // send Access denied message to the client
          }
        }

      }
      break;

    case 'delete':// in /admin/content page u won't find the 'delete' button
      if( in_array('webmaster',$account->getRoles()) ){

        if($node->hasField('field_prevent_webmaster') ){
          if(  $node->field_prevent_webmaster->value == true){
            return AccessResult::forbidden();
          }
        }

        if($node->hasField('field_allow_webmaster') ) {
          if ($node->field_allow_webmaster->value == TRUE) {
            return AccessResult::allowed(); // send Access denied message to the client
          }
        }

      }
      break;

    case 'update': // in /admin/content page u won't find the 'update' button
      if( in_array('webmaster',$account->getRoles()) ){

        if( $node->hasField('field_prevent_webmaster')){
          if(  $node->field_prevent_webmaster->value == true){
            return AccessResult::forbidden();
          }
        }

        if($node->hasField('field_allow_webmaster') ) {
          if ($node->field_allow_webmaster->value == TRUE) {
            return AccessResult::allowed();
          }
        }

      }
      break;
  }
}

3.Getting permissions from node_access table

If all modules ignore the access request, then the node_access table is used to determine access and this more complicated then the previous 2 so we need to understand :

the node access table

  • nid: The node id that will be affected by this permission line.
  • gid(like user): the grant id, is simply a way to associate a user with a node.
  • realm(like user type): It can be pretty much anything you want to consider it as a user type that groups a set of grant ids (gid).
  • grant_view: Boolean indicating whether a user with the realm/grant pair can view this node.
  • grant_update: Boolean indicating whether a user with the realm/grant pair can edit this node.
  • grant_delete: Boolean indicating whether a user with the realm/grant pair can delete this node.

Example according to the node_access table bellow:

  • the user that have gid=2 and realm=node_atuthor, he can view and update the node 52.
  • the user that have gid=2 but a realm=node_super_user, he can view , update and delete the node 52. enter image description here

Hook_node_access_records()

when a node is saved this hook will be executed, which enables us to fill the node_access table with the permission privileges that have already explore in the previous step.

/**
 * Implements HOOK_node_access_records
 **/
function entity_access_node_access_records(\Drupal\node\NodeInterface $node) {
   $grants = [];
   if( $node->isPublished() == TRUE) {
     $grants = [
       [
         'realm' => 'node_author',
         'gid' => $node->getOwnerId(),
         'grant_view' => 1,
         'grant_update' => 1,
         'grant_delete' => 0,
         'priority' => 0,
       ],
       [
         'realm' => 'node_viewer',
         'gid' => $node->getOwnerId(),
         'grant_view' => 1,
         'grant_update' => 0,
         'grant_delete' => 0,
         'priority' => 0,
       ],
       [
         'realm' => 'node_super_user',
         'gid' => $node->getOwnerId(),
         'grant_view' => 1,
         'grant_update' => 1,
         'grant_delete' => 1,
         'priority' => 0,
       ]
     ];
   }
  return $grants;
}

Hook_node_grants()

is called every time access to a node is requested to determine access and this is what is used to check the node_access table and decide who what nodes the current user can see:

/**
 * Implements Implements HOOK_node_grants
 */
function entity_access_node_grants(AccountInterface $account, $op) {

  // Allow the user that have 'see_omar_is_nodes' permission and that is also not omar :
  if ($account->hasPermission('see_omar_is_nodes') && $account->id() != 3){
      // to get This gid realm grants ( to see his content + Omar is content)
      $grants = [
        'node_super_user' => [$account->id()],
        'node_viewer' => [3]
      ] ;
  }


 // else if the user acount id =3 (omar)
  else if ($account->id() == 3){
    // get This gid realm grants of current user
    $grants =[
      'node_author' => [3]
    ] ;
  }
2
  • Great outline of the process! – Jaypan Jun 3 '20 at 16:30
  • @Jaypan thank u for the appreciation – Marwen Amri Jun 3 '20 at 16:31
1

The node grants system allows for SQL queries to select multiple nodes, limiting them according to a user's access level. Without the grants system, when creating lists of nodes, all nodes would have to be first loaded with an SQl query, then looped through in PHP with an access checker. This is very inefficient, and also makes it nearly impossible to have any kind of efficient method of pagination. Node grants allow for loading nodes with an included permission check as part of the SQL query.

When any module implements node grants, nodes become blacklisted, and permission to perform actions on them needs to be created through a grant. If a node grant exists, permission is given.

However, node grants are added to a system as part of modules. It's very possible that a system will not have any modules that implement any node grants. If I recall correctly, that is the case on a vanilla install, and I believe your comments support that.

With no node grants system, there is no system by which to restrict access to nodes in SQL queries, and therefore it means all nodes are accessible in SQL queries.

node_access_view_all_nodes() is a helper function that determines if an access query even needs to be made, bypassing the access part of the SQL query when no modules implement a grants system.

Some nodes (e.g. unpublished nodes or anything blocked by a node_access hook) do return 403 when trying to load them as the anonymous user, so it obviously does not have access to all nodes. Is there another level of access checking hidden somewhere?

Once nodes have been loaded, each node passes through a different access control system in PHP; hook_node_access(). That system of access control is independent from node grants. Node access is a PHP process that determines whether an operation (view, update, delete) can be performed on a loaded node. Node grants are a system that is used when querying nodes, to allow for SQL queries that implement access controls, without requiring PHP.

2
  • Good answer (+1) but not 100% comprehensive. From what I have read since asking the question, the node_access table ignores the published state by default. When you install a module that implements grants, the default grant for all nodes disappears as soon as you rebuild permissions. Then node_access_view_all_nodes starts to return 0 for the anonymous user. Feel free to include this in your answer. Otherwise I'll write an additional answer with some examples later. – rudolfbyker Jun 3 '20 at 8:38
  • Also: The default behaviour is documented at api.drupal.org/api/drupal/core%21modules%21node%21node.module/… ("Tagging a query with "node_access" does not check the published/unpublished status of nodes") but it doesn't mention that this behaviour changes once you have a module implementing grants. – rudolfbyker Jun 3 '20 at 8:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.