From f4108f88bff84e742a634d647adcf4c6e3a60bf7 Mon Sep 17 00:00:00 2001 From: Alex Pott <alex.a.pott@googlemail.com> Date: Sat, 13 Dec 2014 17:29:19 +0100 Subject: [PATCH] Issue #2204363 by Berdir, Wim Leers, tim.plunkett, chx: [sechole] Returning TRUE from hook_entity_access()/hook_ENTITYTYPE_access() must not bypass EntityAccessController::checkAccess() --- .../Entity/EntityAccessControlHandler.php | 14 ++++++---- .../Entity/EntityAccessControlHandlerTest.php | 28 +++++++++++++++++++ .../modules/entity_test/entity_test.module | 7 +++++ .../src/EntityTestAccessControlHandler.php | 6 ++++ 4 files changed, 49 insertions(+), 6 deletions(-) diff --git a/core/lib/Drupal/Core/Entity/EntityAccessControlHandler.php b/core/lib/Drupal/Core/Entity/EntityAccessControlHandler.php index 3def84ac573c..0994cba0aa10 100644 --- a/core/lib/Drupal/Core/Entity/EntityAccessControlHandler.php +++ b/core/lib/Drupal/Core/Entity/EntityAccessControlHandler.php @@ -76,9 +76,10 @@ public function access(EntityInterface $entity, $operation, $langcode = Language ); $return = $this->processAccessHookResults($access); - if ($return->isNeutral()) { - // No module had an opinion about the access, so let's the access - // handler check access. + + // Also execute the default access check except when the access result is + // already forbidden, as in that case, it can not be anything else. + if (!$return->isForbidden()) { $return = $return->orIf($this->checkAccess($entity, $operation, $langcode, $account)); } $result = $this->setCache($return, $entity->uuid(), $operation, $langcode, $account); @@ -231,9 +232,10 @@ public function createAccess($entity_bundle = NULL, AccountInterface $account = ); $return = $this->processAccessHookResults($access); - if ($return->isNeutral()) { - // No module had an opinion about the access, so let's the access - // handler check create access. + + // Also execute the default access check except when the access result is + // already forbidden, as in that case, it can not be anything else. + if (!$return->isForbidden()) { $return = $return->orIf($this->checkCreateAccess($account, $context, $entity_bundle)); } $result = $this->setCache($return, $cid, 'create', $context['langcode'], $account); diff --git a/core/modules/system/src/Tests/Entity/EntityAccessControlHandlerTest.php b/core/modules/system/src/Tests/Entity/EntityAccessControlHandlerTest.php index 76e87f4ebd6d..d11a9454fd55 100644 --- a/core/modules/system/src/Tests/Entity/EntityAccessControlHandlerTest.php +++ b/core/modules/system/src/Tests/Entity/EntityAccessControlHandlerTest.php @@ -66,6 +66,34 @@ function testEntityAccess() { ), $entity, $custom_user); } + /** + * Ensures default entity access is checked when necessary. + * + * This ensures that the default checkAccess() implementation of the + * entity access control handler is considered if hook_entity_access() has not + * explicitly forbidden access. Therefore the default checkAccess() + * implementation can forbid access, even after access was already explicitly + * allowed by hook_entity_access(). + * + * @see \Drupal\entity_test\EntityTestAccessControlHandler::checkAccess() + * @see entity_test_entity_access() + */ + function testDefaultEntityAccess() { + // Set up a non-admin user that is allowed to view test entities. + \Drupal::currentUser()->setAccount($this->createUser(array('uid' => 2), array('view test entity'))); + $entity = entity_create('entity_test', array( + 'name' => 'forbid_access', + )); + + // The user is denied access to the entity. + $this->assertEntityAccess(array( + 'create' => FALSE, + 'update' => FALSE, + 'delete' => FALSE, + 'view' => FALSE, + ), $entity); + } + /** * Ensures that the default handler is used as a fallback. */ diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module index 3e7cb73f2354..08fa56444074 100644 --- a/core/modules/system/tests/modules/entity_test/entity_test.module +++ b/core/modules/system/tests/modules/entity_test/entity_test.module @@ -497,6 +497,13 @@ function entity_test_entity_prepare_view($entity_type, array $entities, array $d function entity_test_entity_access(EntityInterface $entity, $operation, AccountInterface $account, $langcode) { \Drupal::state()->set('entity_test_entity_access', TRUE); + // Attempt to allow access to entities with the title forbid_access, + // this will be overridden by + // \Drupal\entity_test\EntityTestAccessControlHandler::checkAccess(). + if ($entity->label() == 'forbid_access') { + return AccessResult::allowed(); + } + // Uncacheable because the access result depends on a State key-value pair and // might therefore change at any time. $condition = \Drupal::state()->get("entity_test_entity_access.{$operation}." . $entity->id(), FALSE); diff --git a/core/modules/system/tests/modules/entity_test/src/EntityTestAccessControlHandler.php b/core/modules/system/tests/modules/entity_test/src/EntityTestAccessControlHandler.php index 2ae39ea556ff..14141273ad2b 100644 --- a/core/modules/system/tests/modules/entity_test/src/EntityTestAccessControlHandler.php +++ b/core/modules/system/tests/modules/entity_test/src/EntityTestAccessControlHandler.php @@ -30,6 +30,12 @@ class EntityTestAccessControlHandler extends EntityAccessControlHandler { * {@inheritdoc} */ protected function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) { + // Always forbid access to entities with the label 'forbid_access', used for + // \Drupal\system\Tests\Entity\EntityAccessHControlandlerTest::testDefaultEntityAccess(). + if ($entity->label() == 'forbid_access') { + return AccessResult::forbidden(); + } + if ($operation === 'view') { if ($langcode != LanguageInterface::LANGCODE_DEFAULT) { return AccessResult::allowedIfHasPermission($account, 'view test entity translations'); -- GitLab