Commit 47308d21 authored by alexpott's avatar alexpott

Issue #1947892 by Berdir, theduke: Improve DX with EntityAccessControllerInterface.

parent d77275ff
......@@ -258,10 +258,9 @@ public function getIterator() {
* Implements \Drupal\Core\TypedData\AccessibleInterface::access().
*/
public function access($operation = 'view', \Drupal\user\Plugin\Core\Entity\User $account = NULL) {
$method = $operation . 'Access';
return drupal_container()->get('plugin.manager.entity')
->getAccessController($this->entityType)
->$method($this, LANGUAGE_DEFAULT, $account);
->access($this, $operation, LANGUAGE_DEFAULT, $account);
}
/**
......
......@@ -22,55 +22,48 @@ class EntityAccessController implements EntityAccessControllerInterface {
protected $accessCache = array();
/**
* Implements \Drupal\Core\Entity\EntityAccessControllerInterface::viewAccess().
* {@inheritdoc}
*/
public function viewAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
if (($access = $this->getCache($entity, 'view', $langcode, $account)) !== NULL) {
return $access;
}
$access = (bool) $this->access($entity, 'view', $langcode, $account);
return $this->setCache($access, $entity, 'view', $langcode, $account);
}
public function access(EntityInterface $entity, $operation, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
/**
* Implements \Drupal\Core\Entity\EntityAccessControllerInterface::createAccess().
*/
public function createAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
if (($access = $this->getCache($entity, 'create', $langcode, $account)) !== NULL) {
return $access;
// @todo Remove this once we can rely on $account.
if (!$account) {
$account = user_load($GLOBALS['user']->uid);
}
$access = (bool) $this->access($entity, 'create', $langcode, $account);
return $this->setCache($access, $entity, 'create', $langcode, $account);
}
/**
* Implements \Drupal\Core\Entity\EntityAccessControllerInterface::updateAccess().
*/
public function updateAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
if (($access = $this->getCache($entity, 'update', $langcode, $account)) !== NULL) {
if (($access = $this->getCache($entity, $operation, $langcode, $account)) !== NULL) {
// Cache hit, no work necessary.
return $access;
}
$access = (bool) $this->access($entity, 'update', $langcode, $account);
return $this->setCache($access, $entity, 'update', $langcode, $account);
}
// Invoke hook_entity_access(), hook results take precedence over overridden
// implementations of EntityAccessController::checkAccess(). Entities
// that have checks that need to be done before the hook is invoked should
// do so by overridding this method.
/**
* Implements \Drupal\Core\Entity\EntityAccessControllerInterface::deleteAccess().
*/
public function deleteAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
if (($access = $this->getCache($entity, 'delete', $langcode, $account)) !== NULL) {
return $access;
}
// We grant access to the entity if both of these conditions are met:
// - No modules say to deny access.
// - At least one module says to grant access.
$access = module_invoke_all($entity->entityType() . '_access', $entity, $operation, $account, $langcode);
$access = (bool) $this->access($entity, 'delete', $langcode, $account);
return $this->setCache($access, $entity, 'delete', $langcode, $account);
if (in_array(FALSE, $access, TRUE)) {
$return = FALSE;
}
elseif (in_array(TRUE, $access, TRUE)) {
$return = TRUE;
}
else {
// No result from hook, so entity checks are done.
$return = (bool) $this->checkAccess($entity, $operation, $langcode, $account);
}
return $this->setCache($return, $entity, $operation, $langcode, $account);
}
/**
* Performs default, shared access checks.
* Performs access checks.
*
* This method is supposed to be overwritten by extending classes that
* do their own custom access checking.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity for which to check 'create' access.
......@@ -78,32 +71,16 @@ public function deleteAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAU
* The entity operation. Usually one of 'view', 'edit', 'create' or
* 'delete'.
* @param string $langcode
* (optional) The language code for which to check access. Defaults to
* LANGUAGE_DEFAULT.
* The language code for which to check access.
* @param \Drupal\user\Plugin\Core\Entity\User $account
* (optional) The user for which to check access, or NULL to check access
* for the current user. Defaults to NULL.
* The user for which to check access.
*
* @return bool|null
* TRUE if access was granted, FALSE if access was denied and NULL if access
* could not be determined.
*/
protected function access(EntityInterface $entity, $operation, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
// @todo Remove this once we can rely on $account.
if (!$account) {
$account = user_load($GLOBALS['user']->uid);
}
// We grant access to the entity if both of these conditions are met:
// - No modules say to deny access.
// - At least one module says to grant access.
$access = module_invoke_all($entity->entityType() . '_access', $entity, $operation, $account, $langcode);
if (in_array(FALSE, $access, TRUE)) {
return FALSE;
}
elseif (in_array(TRUE, $access, TRUE)) {
return TRUE;
}
protected function checkAccess(EntityInterface $entity, $operation, $langcode, User $account) {
return NULL;
}
/**
......@@ -115,23 +92,16 @@ protected function access(EntityInterface $entity, $operation, $langcode = LANGU
* The entity operation. Usually one of 'view', 'edit', 'create' or
* 'delete'.
* @param string $langcode
* (optional) The language code for which to check access. Defaults to
* LANGUAGE_DEFAULT.
* The language code for which to check access.
* @param \Drupal\user\Plugin\Core\Entity\User $account
* (optional) The user for which to check access, or NULL to check access
* for the current user. Defaults to NULL.
* The user for which to check access.
*
* @return bool|null
* TRUE if access was granted, FALSE if access was denied and NULL if there
* is no record for the given user, operation, langcode and entity in the
* cache.
*/
protected function getCache(EntityInterface $entity, $operation, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
// @todo Remove this once we can rely on $account.
if (!$account) {
$account = user_load($GLOBALS['user']->uid);
}
protected function getCache(EntityInterface $entity, $operation, $langcode, User $account) {
$uid = $account ? $account->id() : 0;
$uuid = $entity->uuid();
......@@ -150,21 +120,14 @@ protected function getCache(EntityInterface $entity, $operation, $langcode = LAN
* The entity operation. Usually one of 'view', 'edit', 'create' or
* 'delete'.
* @param string $langcode
* (optional) The language code for which to check access. Defaults to
* LANGUAGE_DEFAULT.
* The language code for which to check access.
* @param \Drupal\user\Plugin\Core\Entity\User $account
* (optional) The user for which to check access, or NULL to check access
* for the current user. Defaults to NULL.
* The user for which to check access.
*
* @return bool
* TRUE if access was granted, FALSE otherwise.
*/
protected function setCache($access, EntityInterface $entity, $operation, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
// @todo Remove this once we can rely on $account.
if (!$account) {
$account = user_load($GLOBALS['user']->uid);
}
protected function setCache($access, EntityInterface $entity, $operation, $langcode, User $account) {
$uid = $account ? $account->id() : 0;
$uuid = $entity->uuid();
......@@ -173,7 +136,7 @@ protected function setCache($access, EntityInterface $entity, $operation, $langc
}
/**
* Implements \Drupal\Core\Entity\EntityAccessControllerInterface::clearCache().
* {@inheritdoc}
*/
public function resetCache() {
$this->accessCache = array();
......
......@@ -16,10 +16,13 @@
interface EntityAccessControllerInterface {
/**
* Checks 'view' access for a given entity or entity translation.
* Checks access to an operation on a given entity or entity translation.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity for which to check 'view' access.
* The entity for which to check access.
* @param string $operation
* The operation access should be checked for.
* Usually one of "view", "create", "update" or "delete".
* @param string $langcode
* (optional) The language code for which to check access. Defaults to
* LANGUAGE_DEFAULT.
......@@ -30,61 +33,11 @@ interface EntityAccessControllerInterface {
* @return bool
* TRUE if access was granted, FALSE otherwise.
*/
public function viewAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL);
/**
* Checks 'create' access for a given entity or entity translation.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity for which to check 'create' access.
* @param string $langcode
* (optional) The language code for which to check access. Defaults to
* LANGUAGE_DEFAULT.
* @param \Drupal\user\Plugin\Core\Entity\User $account
* (optional) The user for which to check access, or NULL to check access
* for the current user. Defaults to NULL.
*
* @return bool
* TRUE if access was granted, FALSE otherwise.
*/
public function createAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL);
/**
* Checks 'update' access for a given entity or entity translation.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to check 'update' access.
* @param string $langcode
* (optional) The language code for which to check access. Defaults to
* LANGUAGE_DEFAULT.
* @param \Drupal\user\Plugin\Core\Entity\User $account
* (optional) The user for which to check access, or NULL to check access
* for the current user. Defaults to NULL.
*
* @return bool
* TRUE if access was granted, FALSE otherwise.
*/
public function updateAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL);
/**
* Checks 'delete' access for a given entity or entity translation.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity for which to check 'delete' access.
* @param string $langcode
* (optional) The language code for which to check access. Defaults to
* LANGUAGE_DEFAULT.
* @param \Drupal\user\Plugin\Core\Entity\User $account
* (optional) The user for which to check access, or NULL to check access
* for the current user. Defaults to NULL.
*
* @return bool
* TRUE if access was granted, FALSE otherwise.
*/
public function deleteAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL);
public function access(EntityInterface $entity, $operation, $langcode = LANGUAGE_DEFAULT, User $account = NULL);
/**
* Clears all cached access checks.
*/
public function resetCache();
}
......@@ -211,7 +211,6 @@ public function onChange($property_name) {
* Implements \Drupal\Core\TypedData\AccessibleInterface::access().
*/
public function access($operation = 'view', \Drupal\user\Plugin\Core\Entity\User $account = NULL) {
$method = $operation . 'Access';
// Determine the language code of this translation by cutting of the
// leading "@" from the property name to get the langcode.
// @todo Add a way to set and get the langcode so that's more obvious what
......@@ -219,6 +218,6 @@ public function access($operation = 'view', \Drupal\user\Plugin\Core\Entity\User
$langcode = substr($this->getName(), 1);
return drupal_container()->get('plugin.manager.entity')
->getAccessController($this->parent->entityType())
->$method($this->parent, $langcode, $account);
->access($this->parent, $operation, $langcode, $account);
}
}
......@@ -17,31 +17,15 @@
class CustomBlockAccessController extends EntityAccessController {
/**
* Implements EntityAccessControllerInterface::viewAccess().
* {@inheritdoc}
*/
public function viewAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
return TRUE;
}
/**
* Implements EntityAccessControllerInterface::createAccess().
*/
public function createAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
return user_access('administer blocks', $account);
}
/**
* Implements EntityAccessControllerInterface::updateAccess().
*/
public function updateAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
return user_access('administer blocks', $account);
}
/**
* Implements EntityAccessControllerInterface::deleteAccess().
*/
public function deleteAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
return user_access('administer blocks', $account);
protected function checkAccess(EntityInterface $entity, $operation, $langcode, User $account) {
if ($operation === 'view') {
return TRUE;
}
elseif (in_array($operation, array('create', 'update', 'delete'))) {
return user_access('administer blocks', $account);
}
}
}
......@@ -17,10 +17,12 @@
class BlockAccessController extends EntityAccessController {
/**
* Overrides \Drupal\Core\Entity\EntityAccessController::viewAccess().
* {@inheritdoc}
*/
public function viewAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
return $entity->getPlugin()->access();
protected function checkAccess(EntityInterface $entity, $operation, $langcode, User $account) {
if ($operation === 'view') {
return $entity->getPlugin()->access();
}
}
}
......@@ -16,45 +16,33 @@
*
* @see \Drupal\comment\Plugin\Core\Entity\Comment.
*/
class CommentAccessController extends EntityAccessController implements CommentAccessControllerInterface {
class CommentAccessController extends EntityAccessController {
/**
* Overrides \Drupal\Core\Entity\EntityAccessController::viewAccess().
* {@inheritdoc}
*/
public function viewAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
return user_access('access comments', $account);
}
/**
* Overrides \Drupal\Core\Entity\EntityAccessController::createAccess().
*/
public function createAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
return user_access('post comments', $account);
}
/**
* Overrides \Drupal\Core\Entity\EntityAccessController::updateAccess().
*/
public function updateAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
// If no user is specified fill in the current one.
if (!isset($account)) {
$account = $GLOBALS['user'];
protected function checkAccess(EntityInterface $entity, $operation, $langcode, User $account) {
switch ($operation) {
case 'view':
return user_access('access comments', $account);
break;
case 'create':
return user_access('post comments', $account);
break;
case 'update':
return ($account->uid && $account->uid == $entity->uid->value && $entity->status->value == COMMENT_PUBLISHED && user_access('edit own comments', $account)) || user_access('administer comments', $account);
break;
case 'delete':
return user_access('administer comments', $account);
break;
case 'approve':
return user_access('administer comments', $account);
break;
}
return ($account->uid && $account->uid == $entity->uid->value && $entity->status->value == COMMENT_PUBLISHED && user_access('edit own comments', $account)) || user_access('administer comments', $account);
}
/**
* Overrides \Drupal\Core\Entity\EntityAccessController::deleteAccess().
*/
public function deleteAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
return user_access('administer comments', $account);
}
/**
* Implements \Drupal\comment\CommentAccessControllerInterface::approveAccess().
*/
public function approveAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
return user_access('administer comments', $account);
}
}
<?php
/**
* @file
* Contains \Drupal\comment\CommentAccessControllerInterface.
*/
namespace Drupal\comment;
use Drupal\Core\Entity\EntityAccessControllerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\user\Plugin\Core\Entity\User;
/**
* Defines an interface for comment access controller.
*
* Additional to EntityAccessControllerInterface this adds an access method for
* approving a comment.
*/
interface CommentAccessControllerInterface extends EntityAccessControllerInterface {
/**
* Checks 'approve' access for a given entity or entity translation.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity for which to check 'delete' access.
* @param string $langcode
* (optional) The language code for which to check access. Defaults to
* LANGUAGE_DEFAULT.
* @param \Drupal\user\Plugin\Core\Entity\User $account
* (optional) The user for which to check access, or NULL to check access
* for the current user. Defaults to NULL.
*
* @return bool
* TRUE if access was granted, FALSE otherwise.
*/
public function approveAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL);
}
......@@ -18,39 +18,22 @@
class NodeAccessController extends EntityAccessController {
/**
* Overrides \Drupal\Core\Entity\EntityAccessController::viewAccess().
* {@inheritdoc}
*/
public function viewAccess(EntityInterface $node, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
if (($cached = $this->getCache($node, 'view', $langcode, $account)) !== NULL ) {
return $cached;
}
if (($access = $this->access($node, 'view', $langcode, $account)) !== NULL) {
return $this->setCache((bool) $access, $node, 'view', $langcode, $account);
};
// If no modules implement hook_node_grants(), the default behavior is to
// allow all users to view published nodes, so reflect that here.
$status = $node instanceof EntityNG ? $node->getTranslation($langcode, FALSE)->status->value : $node->status;
return $this->setCache($status, $node, 'view', $langcode, $account);
}
/**
* Overrides \Drupal\Core\Entity\EntityAccessController::access().
*/
protected function access(EntityInterface $node, $operation, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
public function access(EntityInterface $entity, $operation, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
if (user_access('bypass node access', $account)) {
return TRUE;
}
if (!user_access('access content', $account)) {
return FALSE;
}
return parent::access($entity, $operation, $langcode, $account);
}
if (($access = parent::access($node, $operation, $langcode, $account)) !== NULL) {
return (bool) $access;
};
/**
* {@inheritdoc}
*/
protected function checkAccess(EntityInterface $node, $operation, $langcode, User $account) {
// Fetch information from the node object if possible.
$status = isset($node->status) ? $node->status : NULL;
$uid = isset($node->uid) ? $node->uid : NULL;
......@@ -61,11 +44,7 @@ protected function access(EntityInterface $node, $operation, $langcode = LANGUAG
}
// Check if authors can view their own unpublished nodes.
if ($operation == 'view' && !$status && user_access('view own unpublished content', $account)) {
// @todo Remove this once we can rely on $account.
if (!$account) {
$account = user_load($GLOBALS['user']->uid);
}
if ($operation === 'view' && !$status && user_access('view own unpublished content', $account)) {
if ($account->id() != 0 && $account->id() == $uid) {
return TRUE;
......@@ -75,31 +54,35 @@ protected function access(EntityInterface $node, $operation, $langcode = LANGUAG
// If no module specified either allow or deny, we fall back to the
// node_access table.
if (($grants = $this->accessGrants($node, $operation, $langcode, $account)) !== NULL) {
return (bool) $grants;
return $grants;
}
// If no modules implement hook_node_grants(), the default behavior is to
// allow all users to view published nodes, so reflect that here.
if ($operation === 'view') {
return $status;
}
}
/**
* Determines access to nodes based on node grants.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* @param \Drupal\Core\Entity\EntityInterface $node
* The entity for which to check 'create' access.
* @param string $operation
* The entity operation. Usually one of 'view', 'edit', 'create' or
* 'delete'.
* @param string $langcode
* (optional) The language code for which to check access. Defaults to
* LANGUAGE_DEFAULT.
* The language code for which to check access.
* @param \Drupal\user\Plugin\Core\Entity\User $account
* (optional) The user for which to check access, or NULL to check access
* for the current user. Defaults to NULL.
* The user for which to check access.
*
* @return bool|null
* TRUE if access was granted, FALSE if access was denied or NULL if no
* module implements hook_node_grants(), the node does not (yet) have an id
* or none of the implementing modules explicitly granted or denied access.
*/
protected function accessGrants(EntityInterface $node, $operation, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
protected function accessGrants(EntityInterface $node, $operation, $langcode, User $account) {
// If no module implements the hook or the node does not have an id there is
// no point in querying the database for access grants.
if (!module_implements('node_grants') || !$node->id()) {
......
......@@ -2546,8 +2546,7 @@ function node_access($op, $node, $account = NULL, $langcode = NULL) {
$account = user_load($account->uid);
}
$method = $op . 'Access';
return entity_access_controller('node')->$method($node, $langcode, $account);
return entity_access_controller('node')->access($node, $op, $langcode, $account);
}
/**
......
......@@ -19,12 +19,13 @@ class ShortcutAccessController extends EntityAccessController {
/**
* {@inheritdoc}
*/
public function deleteAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
if (!user_access('administer shortcuts')) {
return FALSE;
protected function checkAccess(EntityInterface $entity, $operation, $langcode, User $account) {
if ($operation == 'delete') {
if (!user_access('administer shortcuts', $account)) {
return FALSE;
}
return $entity->id() != 'default';
}
return $entity->id() != 'default';
}
}
......@@ -89,6 +89,10 @@ function testEntityAccess() {
* Ensures that the default controller is used as a fallback.
*/
function testEntityAccessDefaultController() {
// The implementation requires that the global user id can be loaded.
global $user;
$user = $this->createUser(array('uid' => 2));
// Check that the default access controller is used for entities that don't
// have a specific access controller defined.
$controller = $this->container->get('plugin.manager.entity')->getAccessController('entity_test_default_access');
......
......@@ -17,34 +17,18 @@
class EntityTestAccessController extends EntityAccessController {
/**
* Implements \Drupal\Core\Entity\EntityAccessControllerInterface::viewAccess().
* {@inheritdoc}
*/
public function viewAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
if ($langcode != LANGUAGE_DEFAULT) {
return user_access('view test entity translations', $account);
protected function checkAccess(EntityInterface $entity, $operation, $langcode, User $account) {
if ($operation === 'view') {
if ($langcode != LANGUAGE_DEFAULT) {
return user_access('view test entity translations', $account);
}
return user_access('view test entity', $account);
}
elseif (in_array($operation, array('create', 'update', 'delete'))) {
return user_access('administer entity_test content', $account);
}
return user_access('view test entity', $account);
}
/**
* Implements \Drupal\Core\Entity\EntityAccessControllerInterface::createAccess().
*/
public function createAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
return user_access('administer entity_test content', $account);
}
/**
* Implements \Drupal\Core\Entity\EntityAccessControllerInterface::updateAccess().
*/
public function updateAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
return user_access('administer entity_test content', $account);
}
/**
* Implements \Drupal\Core\Entity\EntityAccessControllerInterface::deleteAccess().
*/
public function deleteAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
return user_access('administer entity_test content', $account);
}
}
......@@ -19,31 +19,26 @@
class TermAccessController extends EntityAccessController {
/**
* Implements \Drupal\Core\Entity\EntityAccessControllerInterface::viewAccess().
* {@inheritdoc}
*/
public function viewAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
return user_access('access content', $account);
}
/**
* Implements \Drupal\Core\Entity\EntityAccessControllerInterface::createAccess().
*/
public function createAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
return user_access('administer taxonomy', $account);
}
/**
* Implements \Drupal\Core\Entity\EntityAccessControllerInterface::updateAccess().
*/
public function updateAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
return user_access("edit terms in {$entity->bundle()}", $account) || user_access('administer taxonomy', $account);
}
/**
* Implements \Drupal\Core\Entity\EntityAccessControllerInterface::deleteAccess().
*/