1

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.)

2 Answers 2

2

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).

3
  • 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?
    – aharown07
    Nov 19, 2022 at 14:41
  • 1
    @aharown07 – Yes 👍🏻
    – leymannx
    Nov 19, 2022 at 14:45
  • 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.
    – aharown07
    Feb 20, 2023 at 3:24
0

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();
  }
}
2
  • Maybe there's a more efficient way to do that?
    – aharown07
    Nov 22, 2022 at 0:29
  • 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.
    – aharown07
    Nov 23, 2022 at 22:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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