entity_clone_entity_access() implementation prevent other module to alter the access to the clone operation

Created on 14 December 2022, almost 2 years ago
Updated 25 July 2024, 4 months ago

Problem/Motivation

The entity_clone_entity_access() implementation has been updated in beta7 version.

/**
 * Implements hook_entity_access().
 */
function entity_clone_entity_access(EntityInterface $entity, $operation, AccountInterface $account) {
  if ($operation !== 'clone') {
    return AccessResult::neutral();
  }

  $cache = new CacheableMetadata();
  $cache->addCacheContexts(['user.permissions']);

  // Deny access if the user cannot clone the entity.
  $access = AccessResult::forbiddenIf(!$account->hasPermission('clone ' . $entity->getEntityTypeId() . ' entity'));
  if ($access->isForbidden()) {
    return $access->addCacheableDependency($cache);
  }

  // Deny access if the user can clone but cannot create new entities of this
  // type. However, we have some exceptions in which the access control handler
  // doesn't have a say in things. In these cases, we go based on the clone
  // permission only.
  $exceptions = [
    'file',
    'paragraph',
  ];

  if (in_array($entity->getEntityTypeId(), $exceptions)) {
    return AccessResult::allowed()->addCacheableDependency($cache);
  }

  $handler = \Drupal::entityTypeManager()->getAccessControlHandler($entity->getEntityTypeId());
  $access = $handler->createAccess($entity->bundle(), $account, [], TRUE);
  if (!$access->isAllowed()) {
    $cache->addCacheableDependency($access);
    $forbidden = AccessResult::forbidden();
    return $forbidden->addCacheableDependency($cache);
  }

  return AccessResult::allowed()->addCacheableDependency($cache);
}

This implementation prevent others (custom or contrib) modules to alter the access to the clone operation because this implementation return explicity an AccessResult::forbidden(), so even if any other module give access to the operation then the access is still denied because there is at least one access forbidden. See processAccessHookResults() in https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21...

Proposed resolution

Inverse the logic. Return an access result allowed if condition are met (permissions, etc), and at the end return an access neutral, to give possibilities to other modules to give an access

This could be this implementation

/**
 * Implements hook_entity_access().
 */
function entity_clone_entity_access(EntityInterface $entity, $operation, AccountInterface $account) {
  if ($operation !== 'clone') {
    return AccessResult::neutral();
  }

  $cache = new CacheableMetadata();
  $cache->addCacheContexts(['user.permissions']);

  // Allow access only if the user can clone and can create new entities of this
  // type. However, we have some exceptions in which the access control handler
  // doesn't have a say in things. In these cases, we go based on the clone
  // permission only.
  $exceptions = [
    'file',
    'paragraph',
  ];

  if (in_array($entity->getEntityTypeId(), $exceptions)) {
    return AccessResult::allowed()->addCacheableDependency($cache);
  }
  // Check access if the user can clone the entity.
  $clone_access = AccessResult::allowedIf($account->hasPermission('clone ' . $entity->getEntityTypeId() . ' entity'));
  
  $handler = \Drupal::entityTypeManager()->getAccessControlHandler($entity->getEntityTypeId());
  $create_access = $handler->createAccess($entity->bundle(), $account, [], TRUE);
  $cache->addCacheableDependency($clone_access)->addCacheableDependency($create_access);
  if ($clone_access->isAllowed() && $create_access->isAllowed()) {
    $allowed = AccessResult::allowed();
    return $allowed->addCacheableDependency($cache);
  }

  return AccessResult::neutral()->addCacheableDependency($cache);
}
🐛 Bug report
Status

RTBC

Version

2.0

Component

Code

Created by

🇫🇷France flocondetoile Lyon

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

Production build 0.71.5 2024