Commit d9f2ba2c authored by webchick's avatar webchick

Issue #2109035 by tim.plunkett, damiankloip, dawehner, chx: Make access...

Issue #2109035 by tim.plunkett, damiankloip, dawehner, chx: Make access checkers (much) easier to find.
parent a06d7830
......@@ -447,29 +447,29 @@ services:
access_check.default:
class: Drupal\Core\Access\DefaultAccessCheck
tags:
- { name: access_check }
- { name: access_check, applies_to: _access }
access_check.entity:
class: Drupal\Core\Entity\EntityAccessCheck
tags:
- { name: access_check }
- { name: access_check, applies_to: _entity_access }
access_check.entity_create:
class: Drupal\Core\Entity\EntityCreateAccessCheck
arguments: ['@entity.manager']
tags:
- { name: access_check }
- { name: access_check, applies_to: _entity_create_access }
access_check.theme:
class: Drupal\Core\Theme\ThemeAccessCheck
tags:
- { name: access_check }
- { name: access_check, applies_to: _access_theme }
access_check.custom:
class: Drupal\Core\Access\CustomAccessCheck
arguments: ['@controller_resolver']
tags:
- { name: access_check }
- { name: access_check, applies_to: _custom_access }
access_check.csrf:
class: Drupal\Core\Access\CsrfAccessCheck
tags:
- { name: access_check }
- { name: access_check, applies_to: _csrf_token }
arguments: ['@csrf_token']
maintenance_mode_subscriber:
class: Drupal\Core\EventSubscriber\MaintenanceModeSubscriber
......
......@@ -8,7 +8,10 @@
namespace Drupal\Core\Access;
/**
* An exception thrown for invalid access callback return values.
* An exception thrown for access errors.
*
* Examples could be invalid access callback return values, or invalid access
* objects being used.
*/
class AccessException extends \RuntimeException {
}
......@@ -8,6 +8,7 @@
namespace Drupal\Core\Access;
use Drupal\Core\ParamConverter\ParamConverterManager;
use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface;
use Drupal\Core\Routing\RequestHelper;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\Session\AccountInterface;
......@@ -16,7 +17,6 @@
use Symfony\Component\Routing\Route;
use Symfony\Component\DependencyInjection\ContainerAware;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
......@@ -118,9 +118,15 @@ public function setRequest(Request $request) {
*
* @param string $service_id
* The ID of the service in the Container that provides a check.
* @param array $applies_checks
* (optional) An array of route requirement keys the checker service applies
* to.
*/
public function addCheckService($service_id) {
public function addCheckService($service_id, array $applies_checks = array()) {
$this->checkIds[] = $service_id;
foreach ($applies_checks as $applies_check) {
$this->staticRequirementMap[$applies_check][] = $service_id;
}
}
/**
......@@ -130,7 +136,7 @@ public function addCheckService($service_id) {
* A collection of routes to apply checks to.
*/
public function setChecks(RouteCollection $routes) {
$this->loadAccessRequirementMap();
$this->loadDynamicRequirementMap();
foreach ($routes as $route) {
if ($checks = $this->applies($route)) {
$route->setOption('_access_checks', $checks);
......@@ -329,19 +335,24 @@ protected function loadCheck($service_id) {
throw new \InvalidArgumentException(sprintf('No check has been registered for %s', $service_id));
}
$this->checks[$service_id] = $this->container->get($service_id);
$check = $this->container->get($service_id);
if (!($check instanceof RoutingAccessInterface)) {
throw new AccessException('All access checks must implement AccessInterface.');
}
$this->checks[$service_id] = $check;
}
/**
* Compiles a mapping of requirement keys to access checker service IDs.
*/
public function loadAccessRequirementMap() {
if (isset($this->staticRequirementMap, $this->dynamicRequirementMap)) {
public function loadDynamicRequirementMap() {
if (isset($this->dynamicRequirementMap)) {
return;
}
// Set them here, so we can use the isset() check above.
$this->staticRequirementMap = array();
$this->dynamicRequirementMap = array();
foreach ($this->checkIds as $service_id) {
......@@ -349,14 +360,8 @@ public function loadAccessRequirementMap() {
$this->loadCheck($service_id);
}
// Empty arrays will not register anything.
if (is_subclass_of($this->checks[$service_id], 'Drupal\Core\Access\StaticAccessCheckInterface')) {
foreach ((array) $this->checks[$service_id]->appliesTo() as $key) {
$this->staticRequirementMap[$key][] = $service_id;
}
}
// Add the service ID to a the regular that will be iterated over.
else {
// Add the service ID to an array that will be iterated over.
if ($this->checks[$service_id] instanceof AccessCheckInterface) {
$this->dynamicRequirementMap[] = $service_id;
}
}
......
......@@ -7,8 +7,8 @@
namespace Drupal\Core\Access;
use Drupal\Core\Access\CsrfTokenGenerator;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\HttpFoundation\Request;
......@@ -19,7 +19,7 @@
* a token generated by \Drupal::csrfToken()->get() using the same value as the
* "_csrf_token" parameter in the route.
*/
class CsrfAccessCheck implements StaticAccessCheckInterface {
class CsrfAccessCheck implements RoutingAccessInterface {
/**
* The CSRF token generator.
......@@ -38,13 +38,6 @@ function __construct(CsrfTokenGenerator $csrf_token) {
$this->csrfToken = $csrf_token;
}
/**
* {@inheritdoc}
*/
public function appliesTo() {
return array('_csrf_token');
}
/**
* {@inheritdoc}
*/
......
......@@ -8,6 +8,7 @@
namespace Drupal\Core\Access;
use Drupal\Core\Controller\ControllerResolverInterface;
use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
......@@ -22,7 +23,7 @@
* cannot reuse any stored property of your actual controller instance used
* to generate the output.
*/
class CustomAccessCheck implements StaticAccessCheckInterface {
class CustomAccessCheck implements RoutingAccessInterface {
/**
* The controller resolver.
......@@ -41,13 +42,6 @@ public function __construct(ControllerResolverInterface $controller_resolver) {
$this->controllerResolver = $controller_resolver;
}
/**
* {@inheritdoc}
*/
public function appliesTo() {
return array('_custom_access');
}
/**
* {@inheritdoc}
*/
......
......@@ -8,20 +8,14 @@
namespace Drupal\Core\Access;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\HttpFoundation\Request;
/**
* Allows access to routes to be controlled by an '_access' boolean parameter.
*/
class DefaultAccessCheck implements StaticAccessCheckInterface {
/**
* {@inheritdoc}
*/
public function appliesTo() {
return array('_access');
}
class DefaultAccessCheck implements RoutingAccessInterface {
/**
* {@inheritdoc}
......
<?php
/**
* @file
* Contains \Drupal\Core\Access\StaticAccessCheckInterface.
*/
namespace Drupal\Core\Access;
use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface;
/**
* An access check service determines access rules for particular routes.
*
* This interface is specifically for routes that know exactly which requirement
* keys they should react to for a route.
*/
interface StaticAccessCheckInterface extends RoutingAccessInterface {
/**
* Declares the route requirement keys this access checker applies to.
*
* This should be used when the requirement matching for a route is static,
* and does not require any further information. For example, '_access' will
* provide TRUE, or FALSE. We do not need any more information other than the
* route provides this requirement key.
*
* @return array
* An array of route requirement keys this access checker applies to. An
* empty array will check all routes using the apply method.
*/
public function appliesTo();
}
......@@ -26,7 +26,13 @@ public function process(ContainerBuilder $container) {
}
$access_manager = $container->getDefinition('access_manager');
foreach ($container->findTaggedServiceIds('access_check') as $id => $attributes) {
$access_manager->addMethodCall('addCheckService', array($id));
$applies = array();
foreach ($attributes as $attribute) {
if (isset($attribute['applies_to'])) {
$applies[] = $attribute['applies_to'];
}
}
$access_manager->addMethodCall('addCheckService', array($id, $applies));
}
}
}
......@@ -7,23 +7,15 @@
namespace Drupal\Core\Entity;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Core\Access\StaticAccessCheckInterface;
/**
* Provides a generic access checker for entities.
*/
class EntityAccessCheck implements StaticAccessCheckInterface {
/**
* {@inheritdoc}
*/
public function appliesTo() {
return array('_entity_access');
}
class EntityAccessCheck implements AccessInterface {
/**
* Implements \Drupal\Core\Access\AccessCheckInterface::access().
......
......@@ -7,7 +7,7 @@
namespace Drupal\Core\Entity;
use Drupal\Core\Access\StaticAccessCheckInterface;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
......@@ -15,7 +15,7 @@
/**
* Defines an access checker for entity creation.
*/
class EntityCreateAccessCheck implements StaticAccessCheckInterface {
class EntityCreateAccessCheck implements AccessInterface {
/**
* The entity manager.
......@@ -41,13 +41,6 @@ public function __construct(EntityManagerInterface $entity_manager) {
$this->entityManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
public function appliesTo() {
return array($this->requirementsKey);
}
/**
* {@inheritdoc}
*/
......
......@@ -7,7 +7,7 @@
namespace Drupal\Core\Theme;
use Drupal\Core\Access\StaticAccessCheckInterface;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
......@@ -15,14 +15,7 @@
/**
* Access check for a theme.
*/
class ThemeAccessCheck implements StaticAccessCheckInterface {
/**
* {@inheritdoc}
*/
public function appliesTo() {
return array('_access_theme');
}
class ThemeAccessCheck implements AccessInterface {
/**
* {@inheritdoc}
......
......@@ -8,3 +8,11 @@ services:
plugin.manager.aggregator.processor:
class: Drupal\aggregator\Plugin\AggregatorPluginManager
arguments: [processor, '@container.namespaces', '@cache.cache', '@language_manager']
access_check.aggregator.categories:
class: Drupal\aggregator\Access\CategoriesAccessCheck
arguments: ['@database']
tags:
- { name: access_check, applies_to: _access_aggregator_categories }
aggregator.category.storage:
class: Drupal\aggregator\CategoryStorageController
arguments: ['@database']
<?php
/**
* @file
* Contains \Drupal\aggregator\Access\CategoriesAccess.
*/
namespace Drupal\aggregator\Access;
use Drupal\Core\Database\Connection;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
/**
* Provides an access check for aggregator categories routes.
*/
class CategoriesAccessCheck implements AccessInterface {
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* Constructs a CategoriesAccessCheck object.
*
* @param \Drupal\Core\Database\Connection
* The database connection.
*/
public function __construct(Connection $database) {
$this->database = $database;
}
/**
* {@inheritdoc}
*/
public function access(Route $route, Request $request, AccountInterface $account) {
return $account->hasPermission('access news feeds') && (bool) $this->database->queryRange('SELECT 1 FROM {aggregator_category}', 0, 1)->fetchField() ? static::ALLOW : static::DENY;
}
}
......@@ -15,4 +15,4 @@ services:
class: Drupal\book\Access\BookNodeIsRemovableAccessCheck
arguments: ['@book.manager']
tags:
- { name: access_check }
- { name: access_check, applies_to: _access_book_removable }
......@@ -8,7 +8,7 @@
namespace Drupal\book\Access;
use Drupal\book\BookManager;
use Drupal\Core\Access\StaticAccessCheckInterface;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\HttpFoundation\Request;
......@@ -16,7 +16,7 @@
/**
* Determines whether the requested node can be removed from its book.
*/
class BookNodeIsRemovableAccessCheck implements StaticAccessCheckInterface {
class BookNodeIsRemovableAccessCheck implements AccessInterface {
/**
* Book Manager Service.
......@@ -35,13 +35,6 @@ public function __construct(BookManager $book_manager) {
$this->bookManager = $book_manager;
}
/**
* {@inheritdoc}
*/
public function appliesTo() {
return array('_access_book_removable');
}
/**
* {@inheritdoc}
*/
......
......@@ -9,13 +9,13 @@ services:
class: Drupal\config_translation\Access\ConfigTranslationOverviewAccess
arguments: ['@plugin.manager.config_translation.mapper']
tags:
- { name: access_check }
- { name: access_check, applies_to: _config_translation_overview_access }
config_translation.access.form:
class: Drupal\config_translation\Access\ConfigTranslationFormAccess
arguments: ['@plugin.manager.config_translation.mapper']
tags:
- { name: access_check }
- { name: access_check, applies_to: _config_translation_form_access }
plugin.manager.config_translation.mapper:
class: Drupal\config_translation\ConfigMapperManager
......
......@@ -16,13 +16,6 @@
*/
class ConfigTranslationFormAccess extends ConfigTranslationOverviewAccess {
/**
* {@inheritdoc}
*/
public function appliesTo() {
return array('_config_translation_form_access');
}
/**
* {@inheritdoc}
*/
......
......@@ -8,7 +8,7 @@
namespace Drupal\config_translation\Access;
use Drupal\config_translation\ConfigMapperManagerInterface;
use Drupal\Core\Access\StaticAccessCheckInterface;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
......@@ -16,7 +16,7 @@
/**
* Checks access for displaying the configuration translation overview.
*/
class ConfigTranslationOverviewAccess implements StaticAccessCheckInterface {
class ConfigTranslationOverviewAccess implements AccessInterface {
/**
* The mapper plugin discovery service.
......@@ -42,13 +42,6 @@ public function __construct(ConfigMapperManagerInterface $config_mapper_manager)
$this->configMapperManager = $config_mapper_manager;
}
/**
* {@inheritdoc}
*/
public function appliesTo() {
return array('_config_translation_overview_access');
}
/**
* {@inheritdoc}
*/
......
......@@ -2,5 +2,5 @@ services:
access_check.contact_personal:
class: Drupal\contact\Access\ContactPageAccess
tags:
- { name: access_check }
- { name: access_check, applies_to: _access_contact_personal_tab }
arguments: ['@config.factory', '@user.data']
......@@ -7,8 +7,8 @@
namespace Drupal\contact\Access;
use Drupal\Core\Access\StaticAccessCheckInterface;
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\user\UserDataInterface;
use Symfony\Component\Routing\Route;
......@@ -17,7 +17,7 @@
/**
* Access check for contact_personal_page route.
*/
class ContactPageAccess implements StaticAccessCheckInterface {
class ContactPageAccess implements AccessInterface {
/**
* The contact settings config object.
......@@ -46,13 +46,6 @@ public function __construct(ConfigFactory $config_factory, UserDataInterface $us
$this->userData = $user_data;
}
/**
* {@inheritdoc}
*/
public function appliesTo() {
return array('_access_contact_personal_tab');
}
/**
* {@inheritdoc}
*/
......
......@@ -13,13 +13,13 @@ services:
class: Drupal\content_translation\Access\ContentTranslationOverviewAccess
arguments: ['@entity.manager']
tags:
- { name: access_check }
- { name: access_check, applies_to: _access_content_translation_overview }
content_translation.manage_access:
class: Drupal\content_translation\Access\ContentTranslationManageAccessCheck
arguments: ['@entity.manager']
tags:
- { name: access_check }
- { name: access_check, applies_to: _access_content_translation_manage }
content_translation.manager:
class: Drupal\content_translation\ContentTranslationManager
......
......@@ -7,9 +7,9 @@
namespace Drupal\content_translation\Access;
use Drupal\Core\Access\StaticAccessCheckInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Language\Language;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\HttpFoundation\Request;
......@@ -17,7 +17,7 @@
/**
* Access check for entity translation CRUD operation.
*/
class ContentTranslationManageAccessCheck implements StaticAccessCheckInterface {
class ContentTranslationManageAccessCheck implements AccessInterface {
/**
* The entity type manager.
......@@ -36,13 +36,6 @@ public function __construct(EntityManagerInterface $manager) {
$this->entityManager = $manager;
}
/**
* {@inheritdoc}
*/
public function appliesTo() {
return array('_access_content_translation_manage');
}
/**
* {@inheritdoc}
*/
......
......@@ -7,8 +7,8 @@
namespace Drupal\content_translation\Access;
use Drupal\Core\Access\StaticAccessCheckInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\HttpFoundation\Request;
......@@ -16,7 +16,7 @@
/**
* Access check for entity translation overview.
*/
class ContentTranslationOverviewAccess implements StaticAccessCheckInterface {
class ContentTranslationOverviewAccess implements AccessInterface {
/**
* The entity type manager.
......@@ -35,13 +35,6 @@ public function __construct(EntityManagerInterface $manager) {
$this->entityManager = $manager;
}
/**
* {@inheritdoc}
*/
public function appliesTo() {
return array('_access_content_translation_overview');
}
/**
* {@inheritdoc}
*/
......
......@@ -6,12 +6,12 @@ services:
class: Drupal\edit\Access\EditEntityFieldAccessCheck
arguments: ['@entity.manager']
tags:
- { name: access_check }
- { name: access_check, applies_to: _access_edit_entity_field }
access_check.edit.entity:
class: Drupal\edit\Access\EditEntityAccessCheck
arguments: ['@entity.manager']
tags:
- { name: access_check }
- { name: access_check, applies_to: _access_edit_entity }
edit.editor.selector:
class: Drupal\edit\EditorSelector
arguments: ['@plugin.manager.edit.editor', '@plugin.manager.field.formatter']
......
......@@ -7,8 +7,8 @@
namespace Drupal\edit\Access;
use Drupal\Core\Access\StaticAccessCheckInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\HttpFoundation\Request;
......@@ -18,7 +18,7 @@
/**
* Access check for editing entities.
*/
class EditEntityAccessCheck implements StaticAccessCheckInterface {
class EditEntityAccessCheck implements AccessInterface {
/**
* The entity manager.
......@@ -37,14 +37,6 @@ public function __construct(EntityManagerInterface $entity_manager) {
$this->entityManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
public function appliesTo() {
// @see edit.routing.yml
return array('_access_edit_entity');
}
/**
* {@inheritdoc}
*/
......
......@@ -7,9 +7,8 @@
namespace Drupal\edit\Access;
use Drupal\Core\Access\StaticAccessCheckInterface;