diff --git a/core/includes/entity.inc b/core/includes/entity.inc index 48ee728..0cbf147 100644 --- a/core/includes/entity.inc +++ b/core/includes/entity.inc @@ -340,6 +340,32 @@ 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|bool + * An entity access controller instance or FALSE if the given entity type does + * not have an access controller. + */ +function entity_access_controller($entity_type) { + $controllers = &drupal_static(__FUNCTION__, array()); + if (!array_key_exists($entity_type, $controllers)) { + $type_info = entity_get_info($entity_type); + if (isset($type_info['access controller class']) && $class = $type_info['access controller class']) { + $controllers[$entity_type] = new $class($entity_type); + } + else { + $controllers[$entity_type] = NULL; + } + } + 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..b41d5fb 100644 --- a/core/lib/Drupal/Core/Entity/Entity.php +++ b/core/lib/Drupal/Core/Entity/Entity.php @@ -251,8 +251,17 @@ 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 (!$controller = entity_access_controller($this->entityType)) { + // If no access controller is defined the entity is fully accessible by + // every user. + return TRUE; + } + if ($operation == 'edit') { + // Map the 'edit' operation to 'update'. + $operation = 'update'; + } + return $controller->{"{$operation}Access"}($this, LANGUAGE_DEFAULT, $user); } /** diff --git a/core/lib/Drupal/Core/Entity/EntityAccessControllerInterface.php b/core/lib/Drupal/Core/Entity/EntityAccessControllerInterface.php new file mode 100644 index 0000000..c068aa0 --- /dev/null +++ b/core/lib/Drupal/Core/Entity/EntityAccessControllerInterface.php @@ -0,0 +1,90 @@ +parent->entityType())) { + // If no access controller is defined the entity translation is fully + // accessible by every user. + return TRUE; + } + if ($operation == 'edit') { + // Map the 'edit' operation to 'update'. + $operation = 'update'; + } + return $controller->{"{$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..67205b9 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityAccessTest.php @@ -0,0 +1,118 @@ + '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' => $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 entity types without any access controller are completely + // unprotected. + $entity = entity_create('entity_access_test', array()); + $this->assertEntityAccess(array( + 'create' => TRUE, + 'update' => TRUE, + 'delete' => TRUE, + 'view' => TRUE, + ), $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..caec7a0 --- /dev/null +++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestAccessController.php @@ -0,0 +1,70 @@ +entityType = $entity_type; + } + + /** + * Implements EntityAccessInterface::view(). + */ + public function viewAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $user = NULL) { + if ($langcode != LANGUAGE_DEFAULT) { + return user_access('view test entity translations', $user); + } + return user_access('view test entity', $user); + } + + /** + * Implements EntityAccessInterface::create(). + */ + public function createAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $user = NULL) { + return TRUE; + } + + /** + * Implements EntityAccessInterface::update(). + */ + public function updateAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $user = NULL) { + return TRUE; + } + + /** + * Implements EntityAccessInterface::delete(). + */ + public function deleteAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $user = NULL) { + return TRUE; + } +}