diff options
author | Lee Rowlands | 2017-10-18 22:37:34 (GMT) |
---|---|---|
committer | Lee Rowlands | 2017-10-18 22:37:34 (GMT) |
commit | a13555adf663bee72fe88fc1da2d5c1efc2a51e5 (patch) | |
tree | aef8ec0ee09a67faf3db2253d6b2f46cbcfd2f8d | |
parent | 5abe312c943e2b5b62fcbe85588201560c635163 (diff) |
Issue #2866666 by vaplas, Wim Leers, dawehner, gaurav.kapoor, pk188, tstoeckler, effulgentsia: Add proper access handlers for view and form displays
8 files changed, 347 insertions, 13 deletions
diff --git a/core/lib/Drupal/Core/Entity/Entity/Access/EntityFormDisplayAccessControlHandler.php b/core/lib/Drupal/Core/Entity/Entity/Access/EntityFormDisplayAccessControlHandler.php new file mode 100644 index 0000000..d48a619 --- /dev/null +++ b/core/lib/Drupal/Core/Entity/Entity/Access/EntityFormDisplayAccessControlHandler.php @@ -0,0 +1,24 @@ +<?php + +namespace Drupal\Core\Entity\Entity\Access; + +use Drupal\Core\Access\AccessResult; +use Drupal\Core\Entity\EntityAccessControlHandler; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Session\AccountInterface; + +/** + * Provides an entity access control handler for form displays. + */ +class EntityFormDisplayAccessControlHandler extends EntityAccessControlHandler { + + /** + * {@inheritdoc} + */ + protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) { + /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $entity */ + return parent::checkAccess($entity, $operation, $account) + ->orIf(AccessResult::allowedIfHasPermission($account, 'administer ' . $entity->getTargetEntityTypeId() . ' form display')); + } + +} diff --git a/core/lib/Drupal/Core/Entity/Entity/Access/EntityViewDisplayAccessControlHandler.php b/core/lib/Drupal/Core/Entity/Entity/Access/EntityViewDisplayAccessControlHandler.php new file mode 100644 index 0000000..41122a2 --- /dev/null +++ b/core/lib/Drupal/Core/Entity/Entity/Access/EntityViewDisplayAccessControlHandler.php @@ -0,0 +1,24 @@ +<?php + +namespace Drupal\Core\Entity\Entity\Access; + +use Drupal\Core\Access\AccessResult; +use Drupal\Core\Entity\EntityAccessControlHandler; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Session\AccountInterface; + +/** + * Provides an entity access control handler for displays. + */ +class EntityViewDisplayAccessControlHandler extends EntityAccessControlHandler { + + /** + * {@inheritdoc} + */ + protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) { + /** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $entity */ + return parent::checkAccess($entity, $operation, $account) + ->orIf(AccessResult::allowedIfHasPermission($account, 'administer ' . $entity->getTargetEntityTypeId() . ' display')); + } + +} diff --git a/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php b/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php index faa6d0d..6c31df5 100644 --- a/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php +++ b/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php @@ -23,6 +23,9 @@ use Symfony\Component\Validator\ConstraintViolationListInterface; * "id" = "id", * "status" = "status" * }, + * handlers = { + * "access" = "\Drupal\Core\Entity\Entity\Access\EntityFormDisplayAccessControlHandler", + * }, * config_export = { * "id", * "targetEntityType", diff --git a/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php b/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php index 74b15b6..1604e31 100644 --- a/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php +++ b/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php @@ -21,6 +21,9 @@ use Drupal\Core\TypedData\TranslatableInterface; * "id" = "id", * "status" = "status" * }, + * handlers = { + * "access" = "\Drupal\Core\Entity\Entity\Access\EntityViewDisplayAccessControlHandler", + * }, * config_export = { * "id", * "targetEntityType", diff --git a/core/modules/field/tests/src/Unit/FieldConfigAccessControlHandlerTest.php b/core/modules/field/tests/src/Unit/FieldConfigAccessControlHandlerTest.php index 0ad8265..f7c3c0b 100644 --- a/core/modules/field/tests/src/Unit/FieldConfigAccessControlHandlerTest.php +++ b/core/modules/field/tests/src/Unit/FieldConfigAccessControlHandlerTest.php @@ -21,9 +21,9 @@ class FieldConfigAccessControlHandlerTest extends FieldStorageConfigAccessContro parent::setUp(); $this->entity = new FieldConfig([ - 'field_name' => $this->fieldStorage->getName(), + 'field_name' => $this->entity->getName(), 'entity_type' => 'node', - 'fieldStorage' => $this->fieldStorage, + 'fieldStorage' => $this->entity, 'bundle' => 'test_bundle', 'field_type' => 'test_field', ], 'node'); diff --git a/core/modules/field/tests/src/Unit/FieldStorageConfigAccessControlHandlerTest.php b/core/modules/field/tests/src/Unit/FieldStorageConfigAccessControlHandlerTest.php index 1d47017..83abd3c 100644 --- a/core/modules/field/tests/src/Unit/FieldStorageConfigAccessControlHandlerTest.php +++ b/core/modules/field/tests/src/Unit/FieldStorageConfigAccessControlHandlerTest.php @@ -53,17 +53,10 @@ class FieldStorageConfigAccessControlHandlerTest extends UnitTestCase { protected $member; /** - * The mocked test field storage config. + * The FieldStorageConfig entity used for testing. * * @var \Drupal\field\FieldStorageConfigInterface */ - protected $fieldStorage; - - /** - * The main entity used for testing. - * - * @var \Drupal\Core\Config\Entity\ConfigEntityInterface - */ protected $entity; /** @@ -160,7 +153,7 @@ class FieldStorageConfigAccessControlHandlerTest extends UnitTestCase { $entity_manager->setContainer($container); \Drupal::setContainer($container); - $this->fieldStorage = new FieldStorageConfig([ + $this->entity = new FieldStorageConfig([ 'field_name' => 'test_field', 'entity_type' => 'node', 'type' => 'boolean', @@ -168,7 +161,6 @@ class FieldStorageConfigAccessControlHandlerTest extends UnitTestCase { 'uuid' => '6f2f259a-f3c7-42ea-bdd5-111ad1f85ed1', ]); - $this->entity = $this->fieldStorage; $this->accessControlHandler = $storage_access_control_handler; } @@ -195,7 +187,7 @@ class FieldStorageConfigAccessControlHandlerTest extends UnitTestCase { $this->assertAllowOperations([], $this->anon); $this->assertAllowOperations(['view', 'update', 'delete'], $this->member); - $this->fieldStorage->setLocked(TRUE)->save(); + $this->entity->setLocked(TRUE)->save(); // Unfortunately, EntityAccessControlHandler has a static cache, which we // therefore must reset manually. $this->accessControlHandler->resetCache(); diff --git a/core/tests/Drupal/Tests/Core/Entity/Access/EntityFormDisplayAccessControlHandlerTest.php b/core/tests/Drupal/Tests/Core/Entity/Access/EntityFormDisplayAccessControlHandlerTest.php new file mode 100644 index 0000000..e4b8bf1 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Entity/Access/EntityFormDisplayAccessControlHandlerTest.php @@ -0,0 +1,244 @@ +<?php + +namespace Drupal\Tests\Core\Entity\Access; + +use Drupal\Component\Plugin\PluginManagerInterface; +use Drupal\Component\Uuid\UuidInterface; +use Drupal\Core\Cache\Context\CacheContextsManager; +use Drupal\Core\Config\Entity\ConfigEntityTypeInterface; +use Drupal\Core\DependencyInjection\Container; +use Drupal\Core\Entity\Entity\Access\EntityFormDisplayAccessControlHandler; +use Drupal\Core\Entity\Entity\EntityFormDisplay; +use Drupal\Core\Entity\EntityFieldManagerInterface; +use Drupal\Core\Entity\EntityManager; +use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Field\FieldTypePluginManagerInterface; +use Drupal\Core\Field\FormatterPluginManager; +use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\Core\Render\RendererInterface; +use Drupal\Core\Session\AccountInterface; +use Drupal\Tests\UnitTestCase; + +/** + * @coversDefaultClass \Drupal\Core\Entity\Entity\Access\EntityFormDisplayAccessControlHandler + * @group Entity + */ +class EntityFormDisplayAccessControlHandlerTest extends UnitTestCase { + + /** + * The field storage config access controller to test. + * + * @var \Drupal\field\FieldStorageConfigAccessControlHandler + */ + protected $accessControlHandler; + + /** + * The mock module handler. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + + /** + * The mock account without field storage config access. + * + * @var \Drupal\Core\Session\AccountInterface + */ + protected $anon; + + /** + * The mock account with EntityFormDisplay access. + * + * @var \Drupal\Core\Session\AccountInterface + */ + protected $member; + + /** + * The mock account with EntityFormDisplay access via parent access check. + * + * @var \Drupal\Core\Session\AccountInterface + */ + protected $parent_member; + + /** + * The EntityFormDisplay entity used for testing. + * + * @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface + */ + protected $entity; + + + /** + * Returns a mock Entity Type Manager. + * + * @return EntityTypeManagerInterface + * The mocked entity type manager. + */ + protected function getEntityTypeManager() { + $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class); + return $entity_type_manager->reveal(); + } + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->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 foobar form display', TRUE], + ])); + $this->member + ->expects($this->any()) + ->method('id') + ->will($this->returnValue(2)); + + $this->parent_member = $this->getMock(AccountInterface::class); + $this->parent_member + ->expects($this->any()) + ->method('hasPermission') + ->will($this->returnValueMap([ + ['Llama', TRUE], + ])); + $this->parent_member + ->expects($this->any()) + ->method('id') + ->will($this->returnValue(3)); + + $entity_form_display_entity_type = $this->getMock(ConfigEntityTypeInterface::class); + $entity_form_display_entity_type->expects($this->any()) + ->method('getAdminPermission') + ->will($this->returnValue('Llama')); + $entity_form_display_entity_type + ->expects($this->any()) + ->method('getKey') + ->will($this->returnValueMap([ + ['langcode', 'langcode'], + ])); + $entity_form_display_entity_type->expects($this->any()) + ->method('entityClassImplements') + ->will($this->returnValue(TRUE)); + $entity_form_display_entity_type->expects($this->any()) + ->method('getConfigPrefix') + ->willReturn(''); + + $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 EntityFormDisplayAccessControlHandler($entity_form_display_entity_type); + $storage_access_control_handler->setModuleHandler($this->moduleHandler); + + $entity_type_manager = $this->getMock(EntityTypeManagerInterface::class); + $entity_type_manager + ->expects($this->any()) + ->method('getStorage') + ->willReturnMap([ + ['entity_display', $this->getMock(EntityStorageInterface::class)], + ]); + $entity_type_manager + ->expects($this->any()) + ->method('getAccessControlHandler') + ->willReturnMap([ + ['entity_display', $storage_access_control_handler], + ]); + $entity_type_manager + ->expects($this->any()) + ->method('getFieldDefinitions') + ->willReturn([]); + $entity_type_manager + ->expects($this->any()) + ->method('getDefinition') + ->will($this->returnValue($entity_form_display_entity_type)); + + $entity_field_manager = $this->getMock(EntityFieldManagerInterface::class); + $entity_field_manager->expects($this->any()) + ->method('getFieldDefinitions') + ->will($this->returnValue([])); + + $entity_manager = new EntityManager(); + $container = new Container(); + $container->set('entity.manager', $entity_manager); + $container->set('entity_type.manager', $entity_type_manager); + $container->set('entity_field.manager', $entity_field_manager); + $container->set('language_manager', $this->getMock(LanguageManagerInterface::class)); + $container->set('plugin.manager.field.widget', $this->prophesize(PluginManagerInterface::class)); + $container->set('plugin.manager.field.field_type', $this->getMock(FieldTypePluginManagerInterface::class)); + $container->set('plugin.manager.field.formatter', $this->prophesize(FormatterPluginManager::class)); + $container->set('uuid', $this->getMock(UuidInterface::class)); + $container->set('renderer', $this->getMock(RendererInterface::class)); + $container->set('cache_contexts_manager', $this->prophesize(CacheContextsManager::class)); + // Inject the container into entity.manager so it can defer to + // entity_type.manager. + $entity_manager->setContainer($container); + \Drupal::setContainer($container); + + $this->entity = new EntityFormDisplay([ + 'targetEntityType' => 'foobar', + 'bundle' => 'bazqux', + 'mode' => 'default', + 'id' => 'foobar.bazqux.default', + 'uuid' => '6f2f259a-f3c7-42ea-bdd5-111ad1f85ed1', + ], 'entity_display'); + + $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."); + } + } + + /** + * @covers ::access + * @covers ::checkAccess + */ + public function testAccess() { + $this->assertAllowOperations([], $this->anon); + $this->assertAllowOperations(['view', 'update', 'delete'], $this->member); + $this->assertAllowOperations(['view', 'update', 'delete'], $this->parent_member); + + $this->entity->enforceIsNew(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); + $this->assertAllowOperations(['view', 'update'], $this->parent_member); + } + +} diff --git a/core/tests/Drupal/Tests/Core/Entity/Access/EntityViewDisplayAccessControlHandlerTest.php b/core/tests/Drupal/Tests/Core/Entity/Access/EntityViewDisplayAccessControlHandlerTest.php new file mode 100644 index 0000000..b798681 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Entity/Access/EntityViewDisplayAccessControlHandlerTest.php @@ -0,0 +1,44 @@ +<?php + +namespace Drupal\Tests\Core\Entity\Access; + +use Drupal\Core\Entity\Entity\Access\EntityViewDisplayAccessControlHandler; +use Drupal\Core\Entity\Entity\EntityViewDisplay; +use Drupal\Core\Session\AccountInterface; + +/** + * @coversDefaultClass \Drupal\Core\Entity\Entity\Access\EntityViewDisplayAccessControlHandler + * @group Entity + */ +class EntityViewDisplayAccessControlHandlerTest extends EntityFormDisplayAccessControlHandlerTest { + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->member = $this->getMock(AccountInterface::class); + $this->member + ->expects($this->any()) + ->method('hasPermission') + ->will($this->returnValueMap([ + ['administer foobar display', TRUE], + ])); + $this->member + ->expects($this->any()) + ->method('id') + ->will($this->returnValue(2)); + + $this->entity = new EntityViewDisplay([ + 'targetEntityType' => 'foobar', + 'bundle' => 'bazqux', + 'mode' => 'default', + 'id' => 'foobar.bazqux.default', + 'uuid' => '6f2f259a-f3c7-42ea-bdd5-111ad1f85ed1', + ], 'entity_display'); + $this->accessControlHandler = new EntityViewDisplayAccessControlHandler($this->entity->getEntityType()); + $this->accessControlHandler->setModuleHandler($this->moduleHandler); + } + +} |