diff --git a/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php b/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php index d94e2b5fb65b7283528adee8d87f902b74702682..fc06979220a430eaaa83db9e817a66b4225b0230 100644 --- a/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php +++ b/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php @@ -290,7 +290,7 @@ protected static function matchEntityByTitle(SelectionInterface $handler, $input /** * Converts an array of entity objects into a string of entity labels. * - * This method is also responsible for checking the 'view' access on the + * This method is also responsible for checking the 'view label' access on the * passed-in entities. * * @param \Drupal\Core\Entity\EntityInterface[] $entities @@ -302,7 +302,9 @@ protected static function matchEntityByTitle(SelectionInterface $handler, $input public static function getEntityLabels(array $entities) { $entity_labels = array(); foreach ($entities as $entity) { - $label = ($entity->access('view')) ? $entity->label() : t('- Restricted access -'); + // Use the special view label, since some entities allow the label to be + // viewed, even if the entity is not allowed to be viewed. + $label = ($entity->access('view label')) ? $entity->label() : t('- Restricted access -'); // Take into account "autocreated" entities. if (!$entity->isNew()) { diff --git a/core/lib/Drupal/Core/Entity/EntityAccessControlHandler.php b/core/lib/Drupal/Core/Entity/EntityAccessControlHandler.php index 27fda29eb1c06e7258e021a5ab6f156291b65d46..02b497db8d202dec85af5508c4e5b2ff72449d9d 100644 --- a/core/lib/Drupal/Core/Entity/EntityAccessControlHandler.php +++ b/core/lib/Drupal/Core/Entity/EntityAccessControlHandler.php @@ -39,6 +39,16 @@ class EntityAccessControlHandler extends EntityHandlerBase implements EntityAcce */ protected $entityType; + /** + * Allows to grant access to just the labels. + * + * By default, the "view label" operation falls back to "view". Set this to + * TRUE to allow returning different access when just listing entity labels. + * + * @var bool + */ + protected $viewLabelOperation = FALSE; + /** * Constructs an access control handler instance. * @@ -57,6 +67,10 @@ public function access(EntityInterface $entity, $operation, AccountInterface $ac $account = $this->prepareUser($account); $langcode = $entity->language()->getId(); + if ($operation === 'view label' && $this->viewLabelOperation == FALSE) { + $operation = 'view'; + } + if (($return = $this->getCache($entity->uuid(), $operation, $langcode, $account)) !== NULL) { // Cache hit, no work necessary. return $return_as_object ? $return : $return->isAllowed(); @@ -124,7 +138,8 @@ protected function processAccessHookResults(array $access) { * @param \Drupal\Core\Entity\EntityInterface $entity * The entity for which to check access. * @param string $operation - * The entity operation. Usually one of 'view', 'update' or 'delete'. + * The entity operation. Usually one of 'view', 'view label', 'update' or + * 'delete'. * @param \Drupal\Core\Session\AccountInterface $account * The user for which to check access. * diff --git a/core/lib/Drupal/Core/Entity/EntityAccessControlHandlerInterface.php b/core/lib/Drupal/Core/Entity/EntityAccessControlHandlerInterface.php index c1e7333f930748b29465cd694a456460d8ec985a..1d3c1edf2c7f98a56d3811a68b6f1082fe61e24b 100644 --- a/core/lib/Drupal/Core/Entity/EntityAccessControlHandlerInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityAccessControlHandlerInterface.php @@ -27,7 +27,7 @@ interface EntityAccessControlHandlerInterface { * The entity for which to check access. * @param string $operation * The operation access should be checked for. - * Usually one of "view", "update" or "delete". + * Usually one of "view", "view label", "update" or "delete". * @param \Drupal\Core\Session\AccountInterface $account * (optional) The user session for which to check access, or NULL to check * access for the current user. Defaults to NULL. diff --git a/core/modules/link/src/Tests/LinkFieldTest.php b/core/modules/link/src/Tests/LinkFieldTest.php index 7fa960c1f9e58bd2413bb17f2de3c322d9051017..2d00cfa36a0f28bea3ddff74f67fcf803711641b 100644 --- a/core/modules/link/src/Tests/LinkFieldTest.php +++ b/core/modules/link/src/Tests/LinkFieldTest.php @@ -10,6 +10,7 @@ use Drupal\Component\Utility\Html; use Drupal\Component\Utility\Unicode; use Drupal\Core\Url; +use Drupal\entity_test\Entity\EntityTest; use Drupal\field\Entity\FieldConfig; use Drupal\link\LinkItemInterface; use Drupal\simpletest\WebTestBase; @@ -99,6 +100,12 @@ function testURLValidation() { // Create a node to test the link widget. $node = $this->drupalCreateNode(); + // Create an entity with restricted view access. + $entity_test_no_label_access = EntityTest::create([ + 'name' => 'forbid_access', + ]); + $entity_test_no_label_access->save(); + // Define some valid URLs (keys are the entered values, values are the // strings displayed to the user). $valid_external_entries = array( @@ -132,7 +139,7 @@ function testURLValidation() { // Entity URI displayed as ER autocomplete value when displayed in a form. 'entity:node/1' => $node->label() . ' (1)', // URI for an entity that exists, but is not accessible by the user. - 'entity:user/1' => '- Restricted access - (1)', + 'entity:entity_test/' . $entity_test_no_label_access->id() => '- Restricted access - (' . $entity_test_no_label_access->id() . ')', // URI for an entity that doesn't exist, but with a valid ID. 'entity:user/999999' => 'entity:user/999999', ); diff --git a/core/modules/node/src/Tests/NodeEditFormTest.php b/core/modules/node/src/Tests/NodeEditFormTest.php index f2701a45b7c987d8c20e2bf021d0c32e5665ec1b..d6c40b665bc8a967422fcf3833d52418686c8b86 100644 --- a/core/modules/node/src/Tests/NodeEditFormTest.php +++ b/core/modules/node/src/Tests/NodeEditFormTest.php @@ -8,6 +8,7 @@ namespace Drupal\node\Tests; use Drupal\node\NodeInterface; +use Drupal\user\Entity\User; /** * Create a node and test node edit functionality. @@ -205,10 +206,17 @@ protected function checkVariousAuthoredByValues(NodeInterface $node, $form_eleme // won't do. $this->assertTrue($uid === 0 || $uid === '0', 'Node authored by anonymous user.'); + // Go back to the edit form and check that the correct value is displayed + // in the author widget. + $this->drupalGet('node/' . $node->id() . '/edit'); + $anonymous_user = User::getAnonymousUser(); + $expected = $anonymous_user->label() . ' (' . $anonymous_user->id() . ')'; + $this->assertFieldByName($form_element_name, $expected, 'Authored by field displays the correct value for the anonymous user.'); + // Change the authored by field to another user's name (that is not // logged in). $edit[$form_element_name] = $this->webUser->getUsername(); - $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save and keep published')); + $this->drupalPostForm(NULL, $edit, t('Save and keep published')); $this->nodeStorage->resetCache(array($node->id())); $node = $this->nodeStorage->load($node->id()); $this->assertIdentical($node->getOwnerId(), $this->webUser->id(), 'Node authored by normal user.'); diff --git a/core/modules/system/src/Tests/Entity/EntityAccessControlHandlerTest.php b/core/modules/system/src/Tests/Entity/EntityAccessControlHandlerTest.php index 222cd7a1ead4eda5a581df5dbe671ded10cc8f6b..f2f05b462aef9aeda941e95dd2b25826f999f3ec 100644 --- a/core/modules/system/src/Tests/Entity/EntityAccessControlHandlerTest.php +++ b/core/modules/system/src/Tests/Entity/EntityAccessControlHandlerTest.php @@ -11,9 +11,12 @@ use Drupal\Core\Session\AccountInterface; use Drupal\Core\Access\AccessibleInterface; use Drupal\Core\Entity\EntityAccessControlHandler; +use Drupal\Core\Session\AnonymousUserSession; use Drupal\entity_test\Entity\EntityTest; use Drupal\entity_test\Entity\EntityTestDefaultAccess; +use Drupal\entity_test\Entity\EntityTestLabel; use Drupal\language\Entity\ConfigurableLanguage; +use Drupal\user\Entity\User; /** * Tests the entity access control handler. @@ -36,13 +39,70 @@ function assertEntityAccess($ops, AccessibleInterface $object, AccountInterface } } + /** + * Ensures user labels are accessible for everyone. + */ + public function testUserLabelAccess() { + // Set up a non-admin user. + \Drupal::currentUser()->setAccount($this->createUser(['uid' => 2])); + + $anonymous_user = User::getAnonymousUser(); + $user = $this->createUser(); + + // The current user is allowed to view the anonymous user label. + $this->assertEntityAccess(array( + 'create' => FALSE, + 'update' => FALSE, + 'delete' => FALSE, + 'view' => FALSE, + 'view label' => TRUE, + ), $anonymous_user); + + // The current user is allowed to view user labels. + $this->assertEntityAccess(array( + 'create' => FALSE, + 'update' => FALSE, + 'delete' => FALSE, + 'view' => FALSE, + 'view label' => TRUE, + ), $user); + + // Switch to a anonymous user account. + $account_switcher = \Drupal::service('account_switcher'); + $account_switcher->switchTo(new AnonymousUserSession()); + + // The anonymous user is allowed to view the anonymous user label. + $this->assertEntityAccess(array( + 'create' => FALSE, + 'update' => FALSE, + 'delete' => FALSE, + 'view' => FALSE, + 'view label' => TRUE, + ), $anonymous_user); + + // The anonymous user is allowed to view user labels. + $this->assertEntityAccess(array( + 'create' => FALSE, + 'update' => FALSE, + 'delete' => FALSE, + 'view' => FALSE, + 'view label' => TRUE, + ), $user); + + // Restore user account. + $account_switcher->switchBack(); + } + /** * Ensures entity access is properly working. */ function testEntityAccess() { // 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 = EntityTest::create(array( + + // Use the 'entity_test_label' entity type in order to test the 'view label' + // access operation. + $entity = EntityTestLabel::create(array( 'name' => 'test', )); @@ -52,15 +112,18 @@ function testEntityAccess() { 'update' => FALSE, 'delete' => FALSE, 'view' => TRUE, + 'view label' => TRUE, ), $entity); - // The custom user is not allowed to perform any operation on test entities. + // The custom user is not allowed to perform any operation on test entities, + // except for viewing their label. $custom_user = $this->createUser(); $this->assertEntityAccess(array( 'create' => FALSE, 'update' => FALSE, 'delete' => FALSE, 'view' => FALSE, + 'view label' => TRUE, ), $entity, $custom_user); } 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 bdc3785ab7be12ec466520fdb7c34fc8cec5dffb..79880604504d6717f1d6934b32ee49f2319dbb4f 100644 --- a/core/modules/system/tests/modules/entity_test/src/EntityTestAccessControlHandler.php +++ b/core/modules/system/tests/modules/entity_test/src/EntityTestAccessControlHandler.php @@ -11,6 +11,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityAccessControlHandler; use Drupal\Core\Session\AccountInterface; +use Drupal\entity_test\Entity\EntityTestLabel; /** * Defines the access control handler for the test entity type. @@ -25,6 +26,13 @@ */ class EntityTestAccessControlHandler extends EntityAccessControlHandler { + /** + * Allows to grant access to just the labels. + * + * @var bool + */ + protected $viewLabelOperation = TRUE; + /** * {@inheritdoc} */ @@ -37,7 +45,11 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter return AccessResult::forbidden(); } - if ($operation === 'view') { + if ($operation === 'view label' && $entity instanceof EntityTestLabel) { + // Viewing the label of the 'entity_test_label' entity type is allowed. + return AccessResult::allowed(); + } + elseif (in_array($operation, array('view', 'view label'))) { if (!$entity->isDefaultTranslation()) { return AccessResult::allowedIfHasPermission($account, 'view test entity translations'); } diff --git a/core/modules/user/src/UserAccessControlHandler.php b/core/modules/user/src/UserAccessControlHandler.php index ae52b2bc5a82c497e9c912d1192ec4ebf1f9a09d..eb8958534274e5d820d41f5be8ff56c886911a5a 100644 --- a/core/modules/user/src/UserAccessControlHandler.php +++ b/core/modules/user/src/UserAccessControlHandler.php @@ -21,12 +21,26 @@ */ class UserAccessControlHandler extends EntityAccessControlHandler { + /** + * Allow access to user label. + * + * @var bool + */ + protected $viewLabelOperation = TRUE; + /** * {@inheritdoc} */ protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) { /** @var \Drupal\user\UserInterface $entity*/ + // We don't treat the user label as privileged information, so this check + // has to be the first one in order to allow labels for all users to be + // viewed, including the special anonymous user. + if ($operation === 'view label') { + return AccessResult::allowed(); + } + // The anonymous user's profile can neither be viewed, updated nor deleted. if ($entity->isAnonymous()) { return AccessResult::forbidden();