diff --git a/core/includes/entity.inc b/core/includes/entity.inc index 48ee728..87d2ad4 100644 --- a/core/includes/entity.inc +++ b/core/includes/entity.inc @@ -50,6 +50,7 @@ function entity_get_info($entity_type = NULL) { 'controller class' => 'Drupal\Core\Entity\DatabaseStorageController', 'list controller class' => 'Drupal\Core\Entity\EntityListController', 'render controller class' => 'Drupal\Core\Entity\EntityRenderController', + 'access controller class' => 'Drupal\Core\Entity\EntityAccessController', 'form controller class' => array( 'default' => 'Drupal\Core\Entity\EntityFormController', ), @@ -340,6 +341,27 @@ function entity_page_label(EntityInterface $entity, $langcode = NULL) { } /** + * Returns the entity access controller for the given entity type. + * + * @see hook_entity_info() + * + * @param string $entity_type + * The type of the entity. + * + * @return Drupal\Core\Entity\EntityAccessControllerInterface + * An entity access controller instance. + */ +function entity_access_controller($entity_type) { + $controllers = &drupal_static(__FUNCTION__, array()); + if (!isset($controllers[$entity_type])) { + $type_info = entity_get_info($entity_type); + $class = $type_info['access controller class']; + $controllers[$entity_type] = new $class($entity_type); + } + return $controllers[$entity_type]; +} + +/** * Returns an entity form controller for the given operation. * * Since there might be different scenarios in which an entity is edited, diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php index 8f462de..ba35b4c 100644 --- a/core/lib/Drupal/Core/Entity/Entity.php +++ b/core/lib/Drupal/Core/Entity/Entity.php @@ -251,8 +251,12 @@ public function getIterator() { /** * Implements AccessibleInterface::access(). */ - public function access(\Drupal\user\User $account = NULL) { - // TODO: Implement access() method. + public function access($operation = 'view', \Drupal\user\User $user = NULL) { + if ($operation == 'edit') { + // Map the 'edit' operation to 'update'. + $operation = 'update'; + } + return entity_access_controller($this->entityType)->{"{$operation}Access"}($this, LANGUAGE_DEFAULT, $user); } /** diff --git a/core/lib/Drupal/Core/Entity/EntityAccessController.php b/core/lib/Drupal/Core/Entity/EntityAccessController.php new file mode 100644 index 0000000..2530091 --- /dev/null +++ b/core/lib/Drupal/Core/Entity/EntityAccessController.php @@ -0,0 +1,44 @@ +parent->entityType())->{"{$operation}Access"}($this->parent, $this->langcode, $user); } /** diff --git a/core/lib/Drupal/Core/Entity/Field/Type/Field.php b/core/lib/Drupal/Core/Entity/Field/Type/Field.php index f4b4e5c..fcf59dd 100644 --- a/core/lib/Drupal/Core/Entity/Field/Type/Field.php +++ b/core/lib/Drupal/Core/Entity/Field/Type/Field.php @@ -297,7 +297,7 @@ public function __clone() { /** * Implements AccessibleInterface::access(). */ - public function access(User $account = NULL) { + public function access($operation = 'view', User $user = NULL) { // TODO: Implement access() method. Use item access. } } diff --git a/core/lib/Drupal/Core/TypedData/AccessibleInterface.php b/core/lib/Drupal/Core/TypedData/AccessibleInterface.php index 669f1d7..94bb8e7 100644 --- a/core/lib/Drupal/Core/TypedData/AccessibleInterface.php +++ b/core/lib/Drupal/Core/TypedData/AccessibleInterface.php @@ -15,12 +15,15 @@ /** * Checks data value access. * + * @param string $operation + * (optional) The operation to be performed. Defaults to 'view'. * @param \Drupal\user\User $account * (optional) The user account to check access for. Defaults to the current * user. * * @return bool - * TRUE if the given user has access; otherwise FALSE. + * TRUE if the given user has access for the given operation, FALSE + * otherwise. */ - public function access(\Drupal\user\User $account = NULL); + public function access($operation = 'view', \Drupal\user\User $user = NULL); } diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityAccessTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityAccessTest.php new file mode 100644 index 0000000..317af1b --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityAccessTest.php @@ -0,0 +1,121 @@ + 'Entity access', + 'description' => 'Tests entity access.', + 'group' => 'Entity API', + ); + } + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = array('entity_test', 'entity_access_test'); + + /** + * Asserts entity access correctly grants or denies access. + */ + function assertEntityAccess($ops, AccessibleInterface $object, User $user = NULL) { + foreach ($ops as $op => $result) { + $msg = t("Entity access returns @result with operation '@op'.", array('@result' => isset($result) ? 'null' : ($result ? 'true' : 'false'), '@op' => $op)); + $this->assertEqual($result, $object->access($op, $user), $msg); + } + } + + /** + * Ensures entity access is properly working. + */ + function testEntityAccess() { + // Set up a non-admin user that is allowed to view test entities. + $user = $this->drupalCreateUser(array('view test entity')); + $this->drupalLogin($user); + + $entity = entity_create('entity_test', array( + 'name' => 'test', + )); + $entity->save(); + + // The current user is allowed to view, create, update and delete entities. + $this->assertEntityAccess(array( + 'create' => TRUE, + 'update' => TRUE, + 'delete' => TRUE, + 'view' => TRUE, + ), $entity); + + // The custom user is not allowed to view test entities. + $user = $this->drupalCreateUser(); + $this->assertEntityAccess(array( + 'create' => TRUE, + 'update' => TRUE, + 'delete' => TRUE, + 'view' => FALSE, + ), $entity, $user); + + // Check that the default access controller is used for entities that don't + // have a specific access controller defined. + $controller = entity_access_controller('entity_access_test'); + $this->assertTrue(is_a($controller, '\Drupal\Core\Entity\EntityAccessController'), 'The default entity controller is used for the entity_access_test entity type.'); + + $entity = entity_create('entity_access_test', array()); + $this->assertEntityAccess(array( + 'create' => FALSE, + 'update' => FALSE, + 'delete' => FALSE, + 'view' => FALSE, + ), $entity); + } + + /** + * Ensures entity access for entity translations is properly working. + */ + function testEntityTranslationAccess() { + // Enable translations for the test entity type. + variable_set('entity_test_translation', TRUE); + module_enable(array('locale')); + + // Set up a non-admin user that is allowed to view test entity translations. + $user = $this->drupalCreateUser(array('view test entity translations')); + $this->drupalLogin($user); + + // Create two test languages. + foreach (array('foo', 'bar') as $langcode) { + $language = new Language(array( + 'langcode' => $langcode, + 'name' => $this->randomString(), + )); + language_save($language); + } + + $entity = entity_create('entity_test', array( + 'name' => 'test', + 'langcode' => 'foo', + )); + $entity->save(); + + $translation = $entity->getTranslation('bar'); + $this->assertEntityAccess(array( + 'view' => TRUE, + ), $translation); + } +} diff --git a/core/modules/system/tests/modules/entity_access_test/entity_access_test.info b/core/modules/system/tests/modules/entity_access_test/entity_access_test.info new file mode 100644 index 0000000..d285089 --- /dev/null +++ b/core/modules/system/tests/modules/entity_access_test/entity_access_test.info @@ -0,0 +1,6 @@ +name = "Entity access test" +description = "Support module for testing entity access." +package = Testing +version = VERSION +core = 8.x +hidden = TRUE diff --git a/core/modules/system/tests/modules/entity_access_test/entity_access_test.module b/core/modules/system/tests/modules/entity_access_test/entity_access_test.module new file mode 100644 index 0000000..574efab --- /dev/null +++ b/core/modules/system/tests/modules/entity_access_test/entity_access_test.module @@ -0,0 +1,34 @@ + array( + 'title' => t('View test entities'), + ), + 'view test entity translations' => array( + 'title' => t('View translations of test entities'), + ), + ); +} + +/** + * Implements hook_entity_info(). + */ +function entity_access_test_entity_info() { + return array( + 'entity_access_test' => array( + 'label' => 'Entity without access controller', + 'entity keys' => array( + 'id' => 'id', + ), + ), + ); +} 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 46def82..ba87cd1 100644 --- a/core/modules/system/tests/modules/entity_test/entity_test.module +++ b/core/modules/system/tests/modules/entity_test/entity_test.module @@ -13,6 +13,7 @@ function entity_test_entity_info() { 'label' => t('Test entity'), 'entity class' => 'Drupal\entity_test\EntityTest', 'controller class' => 'Drupal\entity_test\EntityTestStorageController', + 'access controller class' => 'Drupal\entity_test\EntityTestAccessController', 'form controller class' => array( 'default' => 'Drupal\entity_test\EntityTestFormController', ), diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestAccessController.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestAccessController.php new file mode 100644 index 0000000..ced2a42 --- /dev/null +++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestAccessController.php @@ -0,0 +1,49 @@ +