Score:1

How can I control node access based on entity reference to multiple users

pe flag

I have multiple uses for this functionality now but mainly for a private message system.

What I want to do:

  • Configure a content type with an entity reference to users
  • Create nodes of that type and select multiple users using the entity reference field
  • Limit visibility of each node of that type to the users selected in that field

I believe there was a module for this in D7, though I can't find it now. But I've had no success finding one for D9.

Is there a module or set of modules anyone is aware of that could provide this kind of access control?

If a module was to be built, any suggestions for best way to approach it? (Is it a really bad idea to put some conditional logic in a template... "if logged in uid = one of values in field x, print, otherwise don't" ? (I don't know the twig syntax for that))

(Since I mentioned private messages, I'm aware of the D9 PM module. But for a lot of reasons, I'd rather just use nodes and comments system. The node+comment system is already well developed and the only thing missing really is multiple-user visibility control.)

Score:2
ne flag

Yes, it's a bad idea to do this in templates as their output will be cached by default.

If it's just for viewing the full node via its canonical URL (/node/123 or /my-node-alias) you can achieve that easily by implementing hook_ENTITY_TYPE_access in a custom module.

use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;

/**
 * Implements hook_ENTITY_TYPE_access().
 */
function MYMODULE_node_access(EntityInterface $entity, $operation, AccountInterface $account) {

  // Check node type and operation.
  if ($entity->bundle() === 'article' && $operation === 'view') {

    $uids = [];
    // Get the allowed users.
    if ($users = $entity->get('field_users')->getValue()) {
      $uids = array_column($users, 'target_id', 'target_id');
    }
    // Add the node author.
    $uids[$entity->getOwnerId()] = $entity->getOwnerId();

    // Return "forbidden" if current user is not in that list.
    return AccessResult::forbiddenIf(!isset($uids[$account->id()]))
      ->cachePerUser()
      ->addCacheableDependency($entity);
  }

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

But note that this hook is not called from Views or entity queries for performance reasons and for proper functioning of the pager system. For that you need to implement a different access strategy, see [Drupal API] Node access rights and [Question] Views Search Ignoring Custom Node Access permissions.

Note also that this hook is bypassed for user 1 (if you during development wonder why breakpoints/dump() are ignored).

pe flag
Much appreciated! The additional views info is also a huge help. On views: in practical terms that means users without access to the node will be able to see it in a view (unless the view is otherwise configured to hide those nodes)? But a node link in a view would still go through hook_ENTITY_TYPE_access if you click it. Am I right?
leymannx avatar
ne flag
@aharown07 – Yes
pe flag
Maybe this should be a separate post, but the context here helps: Can anyone steer me toward a hook (or other method) that would be called from views? I'm using regular views access controls and block controls as a solution now, but I'm a little paranoid, so... what if some view breaks and links are visible to users who should not see them? So I'd like a layer that would do a similar access check on the link from views also.
Score:0
pe flag

I added condition to give the node author access also, without including the author in the entity reference field:

<?php
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;

/**
 * Implements hook_ENTITY_TYPE_access().
 */
function MYMODULENAME_node_access(EntityInterface $entity, $operation, AccountInterface $account) {
  if ($entity->bundle() === 'pm' && $operation === 'view') {
    if ($users = $entity->get('MYFIELDNAME')->getValue()) {
      $uids = array_column($users, 'target_id', 'target_id');
      $author_id = $entity->getOwnerID();
      if (isset($uids[$account->id()]) || $author_id == $account->id()) {
        return AccessResult::neutral();
      }
    }
 return AccessResult::forbidden();
  }
}
pe flag
Maybe there's a more efficient way to do that?
pe flag
Answer 2 seems obsolete after updates to Answer 1: at the moment, with Node View Permissions installed, the author of the node also has visibility when not listed in the entity reference field.
I sit in a Tesla and translated this thread with Ai:

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.