From a6b84bfbf2090e19d7871561f3f20adede3e2a87 Mon Sep 17 00:00:00 2001 From: Alex Pott Date: Sat, 29 Apr 2017 21:27:29 +0100 Subject: [PATCH] Issue #2843756 by vaplas, Wim Leers, brentschuddinck, jamesdesq: EntityResource: Provide comprehensive test coverage for FieldStorageConfig entity --- .../field/src/Entity/FieldStorageConfig.php | 1 + .../src/FieldConfigAccessControlHandler.php | 23 +- ...FieldStorageConfigAccessControlHandler.php | 33 +++ .../FieldConfigAccessControlHandlerTest.php | 43 ++++ ...dStorageConfigAccessControlHandlerTest.php | 200 ++++++++++++++++++ .../FieldStorageConfigHalJsonAnonTest.php | 30 +++ ...FieldStorageConfigHalJsonBasicAuthTest.php | 35 +++ .../FieldStorageConfigHalJsonCookieTest.php | 35 +++ .../FieldStorageConfigJsonAnonTest.php | 24 +++ .../FieldStorageConfigJsonBasicAuthTest.php | 34 +++ .../FieldStorageConfigJsonCookieTest.php | 29 +++ .../FieldStorageConfigResourceTestBase.php | 104 +++++++++ 12 files changed, 579 insertions(+), 12 deletions(-) create mode 100644 core/modules/field/src/FieldStorageConfigAccessControlHandler.php create mode 100644 core/modules/field/tests/src/Unit/FieldConfigAccessControlHandlerTest.php create mode 100644 core/modules/field/tests/src/Unit/FieldStorageConfigAccessControlHandlerTest.php create mode 100644 core/modules/hal/tests/src/Functional/EntityResource/FieldStorageConfig/FieldStorageConfigHalJsonAnonTest.php create mode 100644 core/modules/hal/tests/src/Functional/EntityResource/FieldStorageConfig/FieldStorageConfigHalJsonBasicAuthTest.php create mode 100644 core/modules/hal/tests/src/Functional/EntityResource/FieldStorageConfig/FieldStorageConfigHalJsonCookieTest.php create mode 100644 core/modules/rest/tests/src/Functional/EntityResource/FieldStorageConfig/FieldStorageConfigJsonAnonTest.php create mode 100644 core/modules/rest/tests/src/Functional/EntityResource/FieldStorageConfig/FieldStorageConfigJsonBasicAuthTest.php create mode 100644 core/modules/rest/tests/src/Functional/EntityResource/FieldStorageConfig/FieldStorageConfigJsonCookieTest.php create mode 100644 core/modules/rest/tests/src/Functional/EntityResource/FieldStorageConfig/FieldStorageConfigResourceTestBase.php diff --git a/core/modules/field/src/Entity/FieldStorageConfig.php b/core/modules/field/src/Entity/FieldStorageConfig.php index a63e54f98be6..494540abb5b2 100644 --- a/core/modules/field/src/Entity/FieldStorageConfig.php +++ b/core/modules/field/src/Entity/FieldStorageConfig.php @@ -18,6 +18,7 @@ use Drupal\field\FieldStorageConfigInterface; * id = "field_storage_config", * label = @Translation("Field storage"), * handlers = { + * "access" = "Drupal\field\FieldStorageConfigAccessControlHandler", * "storage" = "Drupal\field\FieldStorageConfigStorage" * }, * config_prefix = "storage", diff --git a/core/modules/field/src/FieldConfigAccessControlHandler.php b/core/modules/field/src/FieldConfigAccessControlHandler.php index e27f99cce6fd..6ee0e05cb655 100644 --- a/core/modules/field/src/FieldConfigAccessControlHandler.php +++ b/core/modules/field/src/FieldConfigAccessControlHandler.php @@ -2,13 +2,12 @@ namespace Drupal\field; -use Drupal\Core\Access\AccessResult; use Drupal\Core\Entity\EntityAccessControlHandler; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Session\AccountInterface; /** - * Defines the access control handler for the field entity type. + * Defines the access control handler for the field config entity type. * * @see \Drupal\field\Entity\FieldConfig */ @@ -18,16 +17,16 @@ class FieldConfigAccessControlHandler extends EntityAccessControlHandler { * {@inheritdoc} */ protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) { - if ($operation == 'delete') { - $field_storage_entity = $entity->getFieldStorageDefinition(); - if ($field_storage_entity->isLocked()) { - return AccessResult::forbidden()->addCacheableDependency($field_storage_entity); - } - else { - return AccessResult::allowedIfHasPermission($account, 'administer ' . $entity->getTargetEntityTypeId() . ' fields')->addCacheableDependency($field_storage_entity); - } - } - return AccessResult::allowedIfHasPermission($account, 'administer ' . $entity->getTargetEntityTypeId() . ' fields'); + // Delegate access control to the underlying field storage config entity: + // the field config entity merely handles configuration for a particular + // bundle of an entity type, the bulk of the logic and configuration is with + // the field storage config entity. Therefore, if an operation is allowed on + // a certain field storage config entity, it should also be allowed for all + // associated field config entities. + // @see \Drupal\Core\Field\FieldDefinitionInterface + /** \Drupal\field\FieldConfigInterface $entity */ + $field_storage_entity = $entity->getFieldStorageDefinition(); + return $field_storage_entity->access($operation, $account, TRUE); } } diff --git a/core/modules/field/src/FieldStorageConfigAccessControlHandler.php b/core/modules/field/src/FieldStorageConfigAccessControlHandler.php new file mode 100644 index 000000000000..da48556fab98 --- /dev/null +++ b/core/modules/field/src/FieldStorageConfigAccessControlHandler.php @@ -0,0 +1,33 @@ +isLocked()) { + return AccessResult::forbidden()->addCacheableDependency($entity); + } + else { + return AccessResult::allowedIfHasPermission($account, 'administer ' . $entity->getTargetEntityTypeId() . ' fields')->addCacheableDependency($entity); + } + } + return AccessResult::allowedIfHasPermission($account, 'administer ' . $entity->getTargetEntityTypeId() . ' fields'); + } + +} diff --git a/core/modules/field/tests/src/Unit/FieldConfigAccessControlHandlerTest.php b/core/modules/field/tests/src/Unit/FieldConfigAccessControlHandlerTest.php new file mode 100644 index 000000000000..0ad8265f895b --- /dev/null +++ b/core/modules/field/tests/src/Unit/FieldConfigAccessControlHandlerTest.php @@ -0,0 +1,43 @@ +entity = new FieldConfig([ + 'field_name' => $this->fieldStorage->getName(), + 'entity_type' => 'node', + 'fieldStorage' => $this->fieldStorage, + 'bundle' => 'test_bundle', + 'field_type' => 'test_field', + ], 'node'); + + $this->accessControlHandler = new FieldConfigAccessControlHandler($this->entity->getEntityType()); + $this->accessControlHandler->setModuleHandler($this->moduleHandler); + } + + /** + * Ensures field config access is working properly. + */ + public function testAccess() { + $this->assertAllowOperations([], $this->anon); + $this->assertAllowOperations(['view', 'update', 'delete'], $this->member); + } + +} diff --git a/core/modules/field/tests/src/Unit/FieldStorageConfigAccessControlHandlerTest.php b/core/modules/field/tests/src/Unit/FieldStorageConfigAccessControlHandlerTest.php new file mode 100644 index 000000000000..4ed751233305 --- /dev/null +++ b/core/modules/field/tests/src/Unit/FieldStorageConfigAccessControlHandlerTest.php @@ -0,0 +1,200 @@ +anon = $this->getMock(AccountInterface::class); + $this->anon + ->expects($this->any()) + ->method('hasPermission') + ->will($this->returnValue(FALSE)); + $this->anon + ->expects($this->any()) + ->method('id') + ->will($this->returnValue(0)); + + $this->member = $this->getMock(AccountInterface::class); + $this->member + ->expects($this->any()) + ->method('hasPermission') + ->will($this->returnValueMap([ + ['administer node fields', TRUE], + ])); + $this->member + ->expects($this->any()) + ->method('id') + ->will($this->returnValue(2)); + + $storageType = $this->getMock(ConfigEntityTypeInterface::class); + $storageType + ->expects($this->any()) + ->method('getProvider') + ->will($this->returnValue('field')); + $storageType + ->expects($this->any()) + ->method('getConfigPrefix') + ->will($this->returnValue('field.storage')); + + $entityType = $this->getMock(ConfigEntityTypeInterface::class); + $entityType + ->expects($this->any()) + ->method('getProvider') + ->will($this->returnValue('node')); + $entityType + ->expects($this->any()) + ->method('getConfigPrefix') + ->willReturn('node'); + + $this->moduleHandler = $this->getMock(ModuleHandlerInterface::class); + $this->moduleHandler + ->expects($this->any()) + ->method('getImplementations') + ->will($this->returnValue([])); + $this->moduleHandler + ->expects($this->any()) + ->method('invokeAll') + ->will($this->returnValue([])); + + $storage_access_control_handler = new FieldStorageConfigAccessControlHandler($storageType); + $storage_access_control_handler->setModuleHandler($this->moduleHandler); + + $entityManager = $this->getMock(EntityManagerInterface::class); + $entityManager + ->expects($this->any()) + ->method('getDefinition') + ->willReturnMap([ + ['field_storage_config', TRUE, $storageType], + ['node', TRUE, $entityType], + ]); + $entityManager + ->expects($this->any()) + ->method('getStorage') + ->willReturnMap([ + ['field_storage_config', $this->getMock(EntityStorageInterface::class)], + ]); + $entityManager + ->expects($this->any()) + ->method('getAccessControlHandler') + ->willReturnMap([ + ['field_storage_config', $storage_access_control_handler], + ]); + + $container = new Container(); + $container->set('entity.manager', $entityManager); + $container->set('uuid', $this->getMock(UuidInterface::class)); + $container->set('cache_contexts_manager', $this->prophesize(CacheContextsManager::class)); + \Drupal::setContainer($container); + + $this->fieldStorage = new FieldStorageConfig([ + 'field_name' => 'test_field', + 'entity_type' => 'node', + 'type' => 'boolean', + 'id' => 'node.test_field', + 'uuid' => '6f2f259a-f3c7-42ea-bdd5-111ad1f85ed1', + ]); + + $this->entity = $this->fieldStorage; + $this->accessControlHandler = $storage_access_control_handler; + } + + /** + * Assert method to verify the access by operations. + * + * @param array $allow_operations + * A list of allowed operations. + * @param \Drupal\Core\Session\AccountInterface $user + * The account to use for get access. + */ + public function assertAllowOperations(array $allow_operations, AccountInterface $user) { + foreach (['view', 'update', 'delete'] as $operation) { + $expected = in_array($operation, $allow_operations); + $actual = $this->accessControlHandler->access($this->entity, $operation, $user); + $this->assertSame($expected, $actual, "Access problem with '$operation' operation."); + } + } + + /** + * Ensures field storage config access is working properly. + */ + public function testAccess() { + $this->assertAllowOperations([], $this->anon); + $this->assertAllowOperations(['view', 'update', 'delete'], $this->member); + + $this->fieldStorage->setLocked(TRUE)->save(); + // Unfortunately, EntityAccessControlHandler has a static cache, which we + // therefore must reset manually. + $this->accessControlHandler->resetCache(); + + $this->assertAllowOperations([], $this->anon); + $this->assertAllowOperations(['view', 'update'], $this->member); + } + +} diff --git a/core/modules/hal/tests/src/Functional/EntityResource/FieldStorageConfig/FieldStorageConfigHalJsonAnonTest.php b/core/modules/hal/tests/src/Functional/EntityResource/FieldStorageConfig/FieldStorageConfigHalJsonAnonTest.php new file mode 100644 index 000000000000..1b9019342aea --- /dev/null +++ b/core/modules/hal/tests/src/Functional/EntityResource/FieldStorageConfig/FieldStorageConfigHalJsonAnonTest.php @@ -0,0 +1,30 @@ +grantPermissionsToTestedRole(['administer node fields']); + } + + /** + * {@inheritdoc} + */ + protected function createEntity() { + $field_storage = FieldStorageConfig::create([ + 'field_name' => 'true_llama', + 'entity_type' => 'node', + 'type' => 'boolean', + ]); + $field_storage->save(); + return $field_storage; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedNormalizedEntity() { + return [ + 'cardinality' => 1, + 'custom_storage' => FALSE, + 'dependencies' => [ + 'module' => ['node'], + ], + 'entity_type' => 'node', + 'field_name' => 'true_llama', + 'id' => 'node.true_llama', + 'indexes' => [], + 'langcode' => 'en', + 'locked' => FALSE, + 'module' => 'core', + 'persist_with_no_fields' => FALSE, + 'settings' => [], + 'status' => TRUE, + 'translatable' => TRUE, + 'type' => 'boolean', + 'uuid' => $this->entity->uuid(), + ]; + } + + /** + * {@inheritdoc} + */ + protected function getNormalizedPostEntity() { + // @todo Update in https://www.drupal.org/node/2300677. + } + + /** + * {@inheritdoc} + */ + protected function getExpectedUnauthorizedAccessMessage($method) { + if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) { + return parent::getExpectedUnauthorizedAccessMessage($method); + } + + switch ($method) { + case 'GET': + return "The 'administer node fields' permission is required."; + + default: + return parent::getExpectedUnauthorizedAccessMessage($method); + } + } + + /** + * {@inheritdoc} + */ + protected function getExpectedCacheContexts() { + return [ + 'user.permissions', + ]; + } + +}