my_module.module
use Drupal\views\ViewExecutable;
use Drupal\my_module\Access\NodePageAccessControlHandler;
use Drupal\my_module\Access\RestrictedMediaAccessControlHandler;
/**
* Implements hook_entity_type_alter().
*/
function my_module_entity_type_alter(array &$entity_types) {
if (isset($entity_types['node'])) {
$entity_types['node']->setAccessClass(NodePageAccessControlHandler::class);
}
if (isset($entity_types['media'])) {
$entity_types['media']->setAccessClass(RestrictedMediaAccessControlHandler::class);
}
}
/**
* Implements hook_views_post_execute().
*/
function my_module_views_post_execute(ViewExecutable $view) {
// Only execute if we are on the right view display.
if ($view->id() === 'MY_VIEW_ID' && $view->current_display === 'MY_VIEW_DISPLAY' ) {
// Mark this view results as token-allowed in the session for a short time.
$ttl = 3600; // 1 hour; adjust to taste.
$now = \Drupal::time()->getRequestTime();
$session = \Drupal::service('request_stack')->getCurrentRequest()->getSession();
$grants = (array) $session->get('my_module.token_mids', []);
foreach ($view->result as $item) {
$grants[(int) $item->mid] = $now + $ttl;
}
$session->set('my_module.token_mids', $grants);
}
}
NodePageAccessControlHandler.php
namespace Drupal\my_module\Access;
/**
* Override Node Access Control Handler.
*/
class NodePageAccessControlHandler extends NodeAccessControlHandler {
...
/**
* {@inheritdoc}
*/
public function access(EntityInterface $entity, $operation, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
if ($operation === 'view') {
// Mark this node as token-allowed in the session for a short time.
$ttl = 3600; // 1 hour; adjust to taste.
$now = $this->time->getRequestTime();
$session = $this->requestStack->getSession();
$grants = (array) $session->get('my_module.token_nids', []);
$grants[(int) $entity->id()] = $now + $ttl;
$session->set('my_module.token_nids', $grants);
}
}
RestrictedMediaAccessControlHandler.php
namespace Drupal\my_module\Access;
/**
* Override Media Access Control Handler.
*/
class RestrictedMediaAccessControlHandler extends MediaAccessControlHandler {
/**
* {@inheritdoc
*
* Restrict anonymous view of Media (bundle "restricted_file") to cases where
* the user can view the host Node (via paragraph field
* 'field_download_restricted' or a view listing media entities),
* OR they have a valid short-lived token grant for that Node stored in session.
*/
public function access(EntityInterface $entity, $operation, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
if (
$operation !== 'view'
|| !$account
|| $entity->bundle() !== 'restricted_file'
) {
return parent::access($entity, $operation, $account, $return_as_object);
}
$paragraphs = $this->entityTypeManager
->getStorage('paragraph')
->loadByProperties(['field_download_restricted' => $entity->id()]);
// Check if we have a media entity id and a cookie for it does exist.
$media_entity = NULL;
if (array_key_exists($entity->id(), $mgrants = (array) $this->requestStack->getSession()->get('my_module.token_mids', []))) {
$media_entity = $this->entityTypeManager
->getStorage('media')
->loadByProperties(['mid' => $entity->id()]);
$now = $this->time->getRequestTime();
// Session-granted access via token.
$mid = (int) $entity->id();
if (!empty($mgrants[$mid]) && $mgrants[$mid] >= $now) {
return AccessResult::allowed()
->addCacheableDependency($entity)
->addCacheableDependency(reset($media_entity))
// Important for anon/session-based grants.
->mergeCacheMaxAge(0);
}
}
$result = AccessResult::forbidden()
->addCacheableDependency($entity)
->cachePerPermissions();
if (!$paragraphs && !$media_entity) {
return $result;
}
// Read session grants set by my_module_node_access().
$now = $this->time->getRequestTime();
$grants = (array) $this->requestStack->getSession()->get('my_module.token_nids', []);
foreach ($paragraphs as $paragraph) {
if ($paragraph instanceof ParagraphInterface) {
$result = $result->addCacheableDependency($paragraph);
}
$node = $this->getAncestorEntity($paragraph);
if (!$node instanceof NodeInterface) {
continue;
}
// Always add the node as a dependency.
$result = $result->addCacheableDependency($node);
// Session-granted access via token.
$nid = (int) $node->id();
if (!empty($grants[$nid]) && $grants[$nid] >= $now) {
return AccessResult::allowed()
->addCacheableDependency($entity)
->addCacheableDependency($node)
// Important for anon/session-based grants.
->mergeCacheMaxAge(0);
}
// Existing rule: inherit from node view access.
if ($node->access('view', $account)) {
return AccessResult::allowed()
->addCacheableDependency($entity)
->addCacheableDependency($node)
->cachePerPermissions();
}
return $result;
}
}
}