diff --git a/core/lib/Drupal/Core/Access/AccessCheckInterface.php b/core/lib/Drupal/Core/Access/AccessCheckInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..0280330c19627751d3596d1bfc904bfc829fd929 --- /dev/null +++ b/core/lib/Drupal/Core/Access/AccessCheckInterface.php @@ -0,0 +1,43 @@ +<?php + +/** + * @file + * Contains Drupal\Core\Access\AccessCheckInterface. + */ + +namespace Drupal\Core\Access; + +use Symfony\Component\Routing\Route; +use Symfony\Component\HttpFoundation\Request; + +/** + * An access check service determines access rules for particular routes. + */ +interface AccessCheckInterface { + + /** + * Declares whether the access check applies to a specific route or not. + * + * @param \Symfony\Component\Routing\Route $route + * The route to consider attaching to. + * + * @return bool + * TRUE if the check applies to the passed route, FALSE otherwise. + */ + public function applies(Route $route); + + /** + * Checks for access to route. + * + * @param \Symfony\Component\Routing\Route $route + * The route to check against. + * @param \Symfony\Component\HttpFoundation\Request $request + * The request object. + * + * @return mixed + * TRUE if access is allowed. + * FALSE if not. + * NULL if no opinion. + */ + public function access(Route $route, Request $request); +} diff --git a/core/lib/Drupal/Core/Access/AccessManager.php b/core/lib/Drupal/Core/Access/AccessManager.php new file mode 100644 index 0000000000000000000000000000000000000000..33d3e9dcd12cdd86b2de828f95045ed7285c5b9f --- /dev/null +++ b/core/lib/Drupal/Core/Access/AccessManager.php @@ -0,0 +1,150 @@ +<?php +/** + * @file + * Contains Drupal\Core\Access\AccessManager. + */ + +namespace Drupal\Core\Access; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Route; +use Symfony\Component\DependencyInjection\ContainerAware; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; + +/** + * Attaches access check services to routes and runs them on request. + */ +class AccessManager extends ContainerAware { + + /** + * Array of registered access check service ids. + * + * @var array + */ + protected $checkIds; + + /** + * Array of access check objects keyed by service id. + * + * @var array + */ + protected $checks; + + /** + * The request object. + * + * @var \Symfony\Component\HttpFoundation\Request + */ + protected $request; + + /** + * Constructs a new AccessManager. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The request object. + */ + public function __construct(Request $request) { + $this->request = $request; + } + + /** + * Registers a new AccessCheck by service ID. + * + * @param string $service_id + * The ID of the service in the Container that provides a check. + */ + public function addCheckService($service_id) { + $this->checkIds[] = $service_id; + } + + /** + * For each route, saves a list of applicable access checks to the route. + * + * @param \Symfony\Component\Routing\RouteCollection $routes + * A collection of routes to apply checks to. + */ + public function setChecks(RouteCollection $routes) { + foreach ($routes as $route) { + $checks = $this->applies($route); + if (!empty($checks)) { + $route->setOption('_access_checks', $checks); + } + } + } + + /** + * Determine which registered access checks apply to a route. + * + * @param \Symfony\Component\Routing\Route $route + * The route to get list of access checks for. + * + * @return array + * An array of service ids for the access checks that apply to passed + * route. + */ + protected function applies(Route $route) { + $checks = array(); + + foreach ($this->checkIds as $service_id) { + if (empty($this->checks[$service_id])) { + $this->loadCheck($service_id); + } + + if ($this->checks[$service_id]->applies($route)) { + $checks[] = $service_id; + } + } + + return $checks; + } + + /** + * Checks a route against applicable access check services. + * + * Determines whether the route is accessible or not. + * + * @param \Symfony\Component\Routing\Route $route + * The route to check access to. + * + * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + * If any access check denies access or none explicitly approve. + */ + public function check(Route $route) { + $access = FALSE; + $checks = $route->getOption('_access_checks') ?: array(); + + // No checks == deny by default. + foreach ($checks as $service_id) { + if (empty($this->checks[$service_id])) { + $this->loadCheck($service_id); + } + + $access = $this->checks[$service_id]->access($route, $this->request); + if ($access === FALSE) { + // A check has denied access, no need to continue checking. + break; + } + } + + // Access has been denied or not explicily approved. + if (!$access) { + throw new AccessDeniedHttpException(); + } + } + + /** + * Lazy-loads access check services. + * + * @param string $service_id + * The service id of the access check service to load. + */ + protected function loadCheck($service_id) { + if (!in_array($service_id, $this->checkIds)) { + throw new \InvalidArgumentException(sprintf('No check has been registered for %s', $service_id)); + } + + $this->checks[$service_id] = $this->container->get($service_id); + } + +} diff --git a/core/lib/Drupal/Core/Access/DefaultAccessCheck.php b/core/lib/Drupal/Core/Access/DefaultAccessCheck.php new file mode 100644 index 0000000000000000000000000000000000000000..80afdef2ce4c7c25f00b7221eec26da5d7f978c4 --- /dev/null +++ b/core/lib/Drupal/Core/Access/DefaultAccessCheck.php @@ -0,0 +1,31 @@ +<?php + +/** + * @file + * Contains Drupal\Core\Access\DefaultAccessCheck. + */ + +namespace Drupal\Core\Access; + +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 AccessCheckInterface { + + /** + * Implements AccessCheckInterface::applies(). + */ + public function applies(Route $route) { + return array_key_exists('_access', $route->getRequirements()); + } + + /** + * Implements AccessCheckInterface::access(). + */ + public function access(Route $route, Request $request) { + return $route->getRequirement('_access'); + } +} diff --git a/core/lib/Drupal/Core/Access/PermissionAccessCheck.php b/core/lib/Drupal/Core/Access/PermissionAccessCheck.php new file mode 100644 index 0000000000000000000000000000000000000000..ee3768b157314cffa677709fd4b7eb6f2c8b0618 --- /dev/null +++ b/core/lib/Drupal/Core/Access/PermissionAccessCheck.php @@ -0,0 +1,35 @@ +<?php + +/** + * @file + * Contains Drupal\Core\Access\PermissionAccessCheck. + */ + +namespace Drupal\Core\Access; + +use Symfony\Component\Routing\Route; +use Symfony\Component\HttpFoundation\Request; + +/** + * Determines access to routes based on permissions defined via hook_permission(). + */ +class PermissionAccessCheck implements AccessCheckInterface { + + /** + * Implements AccessCheckInterface::applies(). + */ + public function applies(Route $route) { + return array_key_exists('_permission', $route->getRequirements()); + } + + /** + * Implements AccessCheckInterface::access(). + */ + public function access(Route $route, Request $request) { + $permission = $route->getRequirement('_permission'); + // @todo Replace user_access() with a correctly injected and session-using + // alternative. + // If user_access() fails, return NULL to give other checks a chance. + return user_access($permission) ? TRUE : NULL; + } +} diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php index 71ef37b454e0a929d394dfd7fd0956e928870b7f..8b4eaaa70b92c0352aaed156fc78a4af73d26f33 100644 --- a/core/lib/Drupal/Core/CoreBundle.php +++ b/core/lib/Drupal/Core/CoreBundle.php @@ -8,6 +8,7 @@ namespace Drupal\Core; use Drupal\Core\DependencyInjection\Compiler\RegisterKernelListenersPass; +use Drupal\Core\DependencyInjection\Compiler\RegisterAccessChecksPass; use Drupal\Core\DependencyInjection\Compiler\RegisterMatchersPass; use Drupal\Core\DependencyInjection\Compiler\RegisterNestedMatchersPass; use Drupal\Core\DependencyInjection\Compiler\RegisterSerializationClassesPass; @@ -179,8 +180,18 @@ public function build(ContainerBuilder $container) { $container->register('view_subscriber', 'Drupal\Core\EventSubscriber\ViewSubscriber') ->addArgument(new Reference('content_negotiation')) ->addTag('event_subscriber'); + $container->register('legacy_access_subscriber', 'Drupal\Core\EventSubscriber\LegacyAccessSubscriber') + ->addTag('event_subscriber'); + $container->register('access_manager', 'Drupal\Core\Access\AccessManager') + ->addArgument(new Reference('request')) + ->addMethodCall('setContainer', array(new Reference('service_container'))); $container->register('access_subscriber', 'Drupal\Core\EventSubscriber\AccessSubscriber') + ->addArgument(new Reference('access_manager')) ->addTag('event_subscriber'); + $container->register('access_check.default', 'Drupal\Core\Access\DefaultAccessCheck') + ->addTag('access_check'); + $container->register('access_check.permission', 'Drupal\Core\Access\PermissionAccessCheck') + ->addTag('access_check'); $container->register('maintenance_mode_subscriber', 'Drupal\Core\EventSubscriber\MaintenanceModeSubscriber') ->addTag('event_subscriber'); $container->register('path_subscriber', 'Drupal\Core\EventSubscriber\PathSubscriber') @@ -221,6 +232,9 @@ public function build(ContainerBuilder $container) { $container->addCompilerPass(new RegisterKernelListenersPass(), PassConfig::TYPE_AFTER_REMOVING); // Add a compiler pass for adding Normalizers and Encoders to Serializer. $container->addCompilerPass(new RegisterSerializationClassesPass()); + // Add a compiler pass for registering event subscribers. + $container->addCompilerPass(new RegisterKernelListenersPass(), PassConfig::TYPE_AFTER_REMOVING); + $container->addCompilerPass(new RegisterAccessChecksPass()); } } diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterAccessChecksPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterAccessChecksPass.php new file mode 100644 index 0000000000000000000000000000000000000000..9e6506d3f285d6abd3a3b968b947ceb28a47cb07 --- /dev/null +++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterAccessChecksPass.php @@ -0,0 +1,32 @@ +<?php + +/** + * @file + * Contains Drupal\Core\DependencyInjection\Compiler\RegisterAccessChecksPass. + */ + +namespace Drupal\Core\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; + +/** + * Adds services tagged 'access_check' to the access_manager service. + */ +class RegisterAccessChecksPass implements CompilerPassInterface { + + /** + * Implements CompilerPassInterface::process(). + * + * Adds services tagged 'access_check' to the access_manager service. + */ + public function process(ContainerBuilder $container) { + if (!$container->hasDefinition('access_manager')) { + return; + } + $access_manager = $container->getDefinition('access_manager'); + foreach ($container->findTaggedServiceIds('access_check') as $id => $attributes) { + $access_manager->addMethodCall('addCheckService', array($id)); + } + } +} diff --git a/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php index 4f1dc7545dff73f9754150e5b3a4239d7c5f1c02..4cfd0b6763ff6697cd458cbcafecdb504a65a978 100644 --- a/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php @@ -2,15 +2,17 @@ /** * @file - * Definition of Drupal\Core\EventSubscriber\AccessSubscriber. + * Contains Drupal\Core\EventSubscriber\AccessSubscriber. */ namespace Drupal\Core\EventSubscriber; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\Event\GetResponseEvent; -use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Drupal\Core\Routing\RoutingEvents; +use Drupal\Core\Access\AccessManager; +use Drupal\Core\Routing\RouteBuildEvent; /** * Access subscriber for controller requests. @@ -18,21 +20,41 @@ class AccessSubscriber implements EventSubscriberInterface { /** - * Verifies that the current user can access the requested path. + * Constructs a new AccessSubscriber. * - * @todo This is a total hack to keep our current access system working. It - * should be replaced with something robust and injected at some point. + * @param \Drupal\Core\Access\AccessManager $access_manager + * The access check manager that will be responsible for applying + * AccessCheckers against routes. + */ + public function __construct(AccessManager $access_manager) { + $this->accessManager = $access_manager; + } + + /** + * Verifies that the current user can access the requested path. * - * @param Symfony\Component\HttpKernel\Event\GetResponseEvent $event + * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event * The Event to process. */ public function onKernelRequestAccessCheck(GetResponseEvent $event) { + $request = $event->getRequest(); + if (!$request->attributes->has('_route')) { + // If no Route is available it is likely a static resource and access is + // handled elsewhere. + return; + } - $router_item = $event->getRequest()->attributes->get('drupal_menu_item'); + $this->accessManager->check($request->attributes->get('_route')); + } - if (isset($router_item['access']) && !$router_item['access']) { - throw new AccessDeniedHttpException(); - } + /** + * Apply access checks to routes. + * + * @param \Drupal\Core\Routing\RouteBuildEvent $event + * The event to process. + */ + public function onRoutingRouteAlterSetAccessCheck(RouteBuildEvent $event) { + $this->accessManager->setChecks($event->getRouteCollection()); } /** @@ -43,6 +65,8 @@ public function onKernelRequestAccessCheck(GetResponseEvent $event) { */ static function getSubscribedEvents() { $events[KernelEvents::REQUEST][] = array('onKernelRequestAccessCheck', 30); + // Setting very low priority to ensure access checks are run after alters. + $events[RoutingEvents::ALTER][] = array('onRoutingRouteAlterSetAccessCheck', 0); return $events; } diff --git a/core/lib/Drupal/Core/EventSubscriber/LegacyAccessSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/LegacyAccessSubscriber.php new file mode 100644 index 0000000000000000000000000000000000000000..566e82ce23702078d3a1c641cb1bc50a9829b6af --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/LegacyAccessSubscriber.php @@ -0,0 +1,49 @@ +<?php + +/** + * @file + * Contains Drupal\Core\EventSubscriber\LegacyAccessSubscriber. + */ + +namespace Drupal\Core\EventSubscriber; + +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Access subscriber for legacy controller requests. + */ +class LegacyAccessSubscriber implements EventSubscriberInterface { + + /** + * Verifies that the current user can access the requested path. + * + * @todo This is a total hack to keep our current access system working. It + * should be replaced with something robust and injected at some point. + * + * @param Symfony\Component\HttpKernel\Event\GetResponseEvent $event + * The Event to process. + */ + public function onKernelRequestAccessCheck(GetResponseEvent $event) { + + $router_item = $event->getRequest()->attributes->get('drupal_menu_item'); + + if (isset($router_item['access']) && !$router_item['access']) { + throw new AccessDeniedHttpException(); + } + } + + /** + * Registers the methods in this class that should be listeners. + * + * @return array + * An array of event listener definitions. + */ + static function getSubscribedEvents() { + $events[KernelEvents::REQUEST][] = array('onKernelRequestAccessCheck', 30); + + return $events; + } +} diff --git a/core/lib/Drupal/Core/Routing/FirstEntryFinalMatcher.php b/core/lib/Drupal/Core/Routing/FirstEntryFinalMatcher.php index 45d0888c51744baa0534c57879c965d05794f77c..cc1adde62a9274998e7bcc4891f8a1980a6f3f72 100644 --- a/core/lib/Drupal/Core/Routing/FirstEntryFinalMatcher.php +++ b/core/lib/Drupal/Core/Routing/FirstEntryFinalMatcher.php @@ -53,7 +53,8 @@ public function matchRequest(Request $request) { preg_match($compiled->getRegex(), $path, $matches); - return array_merge($this->mergeDefaults($matches, $route->getDefaults()), array('_route' => $name)); + $route->setOption('_name', $name); + return array_merge($this->mergeDefaults($matches, $route->getDefaults()), array('_route' => $route)); } } diff --git a/core/modules/rest/lib/Drupal/rest/EventSubscriber/RouteSubscriber.php b/core/modules/rest/lib/Drupal/rest/EventSubscriber/RouteSubscriber.php index f76d535489c89997da4b9600879f98007aa13feb..83fdd63b0393f53d8b2fc4e93a46ee23fe587710 100644 --- a/core/modules/rest/lib/Drupal/rest/EventSubscriber/RouteSubscriber.php +++ b/core/modules/rest/lib/Drupal/rest/EventSubscriber/RouteSubscriber.php @@ -62,6 +62,7 @@ public function dynamicRoutes(RouteBuildEvent $event) { // @todo Switch to ->addCollection() once http://drupal.org/node/1819018 is resolved. foreach ($plugin->routes() as $name => $route) { + $route->setRequirement('_access', 'TRUE'); $collection->add("rest.$name", $route); } } diff --git a/core/modules/system/lib/Drupal/system/Access/CronAccessCheck.php b/core/modules/system/lib/Drupal/system/Access/CronAccessCheck.php new file mode 100644 index 0000000000000000000000000000000000000000..117dc1a07f5a8918d94ae0808d18aa3cd0e42fac --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Access/CronAccessCheck.php @@ -0,0 +1,41 @@ +<?php + +/** + * @file + * Contains Drupal\system\Access\CronAccessCheck. + */ + +namespace Drupal\system\Access; + +use Drupal\Core\Access\AccessCheckInterface; +use Symfony\Component\Routing\Route; +use Symfony\Component\HttpFoundation\Request; + +/** + * Access check for cron routes. + */ +class CronAccessCheck implements AccessCheckInterface { + + /** + * Implements AccessCheckInterface::applies(). + */ + public function applies(Route $route) { + return array_key_exists('_access_system_cron', $route->getRequirements()); + } + + /** + * Implements AccessCheckInterface::access(). + */ + public function access(Route $route, Request $request) { + $key = $request->attributes->get('key'); + if ($key != state()->get('system.cron_key')) { + watchdog('cron', 'Cron could not run because an invalid key was used.', array(), WATCHDOG_NOTICE); + return FALSE; + } + elseif (config('system.maintenance')->get('enabled')) { + watchdog('cron', 'Cron could not run because the site is in maintenance mode.', array(), WATCHDOG_NOTICE); + return FALSE; + } + return TRUE; + } +} diff --git a/core/modules/system/lib/Drupal/system/CronController.php b/core/modules/system/lib/Drupal/system/CronController.php index 292d242f4036827ee9a74e4f03a7b573e2b56c6d..1a7e50e9384b38554ec50c083fee0e8f141efdb5 100644 --- a/core/modules/system/lib/Drupal/system/CronController.php +++ b/core/modules/system/lib/Drupal/system/CronController.php @@ -8,10 +8,9 @@ namespace Drupal\system; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; /** - * Controllers for Cron handling. + * Controller for Cron handling. */ class CronController { @@ -21,35 +20,11 @@ class CronController { * @return Symfony\Component\HttpFoundation\Response * A Symfony response object. */ - public function run($key) { - if (!$this->access($key)) { - throw new AccessDeniedHttpException(); - } - + public function run() { // @todo Make this an injected object. drupal_cron_run(); // HTTP 204 is "No content", meaning "I did what you asked and we're done." return new Response('', 204); } - - /** - * Determines if the user has access to run cron. - * - * @todo Eliminate this method in favor of a new-style access checker once - * http://drupal.org/node/1793520 gets in. - */ - function access($key) { - if ($key != state()->get('system.cron_key')) { - watchdog('cron', 'Cron could not run because an invalid key was used.', array(), WATCHDOG_NOTICE); - return FALSE; - } - elseif (config('system.maintenance')->get('enabled')) { - watchdog('cron', 'Cron could not run because the site is in maintenance mode.', array(), WATCHDOG_NOTICE); - return FALSE; - } - - return TRUE; - } - } diff --git a/core/modules/system/lib/Drupal/system/SystemBundle.php b/core/modules/system/lib/Drupal/system/SystemBundle.php new file mode 100644 index 0000000000000000000000000000000000000000..4ea60179f9db7165e682d9f5ca5ebb5a4a7fb2ff --- /dev/null +++ b/core/modules/system/lib/Drupal/system/SystemBundle.php @@ -0,0 +1,25 @@ +<?php + +/** + * @file + * Contains Drupal\system\SystemBundle. + */ + +namespace Drupal\system; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\Bundle\Bundle; + +/** + * System dependency injection container. + */ +class SystemBundle extends Bundle { + + /** + * Overrides Bundle::build(). + */ + public function build(ContainerBuilder $container) { + $container->register('access_check.cron', 'Drupal\system\Access\CronAccessCheck') + ->addTag('access_check'); + } +} diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/FirstEntryFinalMatcherTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/FirstEntryFinalMatcherTest.php index a288b9eb16e0af913162f62d7ecd470ab03bd453..c44a49247040fac60a7d43ed9dcc9f034b43041a 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Routing/FirstEntryFinalMatcherTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Routing/FirstEntryFinalMatcherTest.php @@ -61,7 +61,7 @@ public function testFinalMatcherStatic() { $matcher->setCollection($collection); $attributes = $matcher->matchRequest($request); - $this->assertEqual($attributes['_route'], 'route_a', 'The correct matching route was found.'); + $this->assertEqual($attributes['_route']->getOption('_name'), 'route_a', 'The correct matching route was found.'); $this->assertEqual($attributes['_controller'], 'foo', 'The correct controller was found.'); } @@ -82,7 +82,7 @@ public function testFinalMatcherPattern() { $matcher->setCollection($collection); $attributes = $matcher->matchRequest($request); - $this->assertEqual($attributes['_route'], 'route_a', 'The correct matching route was found.'); + $this->assertEqual($attributes['_route']->getOption('_name'), 'route_a', 'The correct matching route was found.'); $this->assertEqual($attributes['_controller'], 'foo', 'The correct controller was found.'); $this->assertEqual($attributes['value'], 'narf', 'Required placeholder value found.'); } @@ -105,7 +105,7 @@ public function testFinalMatcherPatternDefalts() { $matcher->setCollection($collection); $attributes = $matcher->matchRequest($request); - $this->assertEqual($attributes['_route'], 'route_a', 'The correct matching route was found.'); + $this->assertEqual($attributes['_route']->getOption('_name'), 'route_a', 'The correct matching route was found.'); $this->assertEqual($attributes['_controller'], 'foo', 'The correct controller was found.'); $this->assertEqual($attributes['value'], 'poink', 'Optional placeholder value used default.'); } diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/HttpMethodMatcherTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/HttpMethodMatcherTest.php index c98da2eef301bd00b0782e07d1d18ee7e85042a8..8055743ee0940b1d90ee1848ba760a029fd763c6 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Routing/HttpMethodMatcherTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Routing/HttpMethodMatcherTest.php @@ -44,7 +44,7 @@ function __construct($test_id = NULL) { $this->fixtures = new RoutingFixtures(); } - + /** * Confirms that the HttpMethod matcher matches properly. */ @@ -78,7 +78,7 @@ public function testNestedMatcher() { $attributes = $matcher->matchRequest($request); - $this->assertEqual($attributes['_route'], 'route_a', 'The correct matching route was found.'); + $this->assertEqual($attributes['_route']->getOption('_name'), 'route_a', 'The correct matching route was found.'); } /** diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/MimeTypeMatcherTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/MimeTypeMatcherTest.php index 850cc7e96ecd105cf15b0d2753e4a9d4228d00b0..8c9a1d8f1dca67a5aa3a3b731fc25553e3470cf7 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Routing/MimeTypeMatcherTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Routing/MimeTypeMatcherTest.php @@ -93,7 +93,7 @@ public function testNestedMatcher() { $request->headers->set('Accept', 'text/html, text/xml;q=0.9'); $attributes = $matcher->matchRequest($request); - $this->assertEqual($attributes['_route'], 'route_e', 'The correct matching route was found.'); + $this->assertEqual($attributes['_route']->getOption('_name'), 'route_e', 'The correct matching route was found.'); } /** diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/NestedMatcherTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/NestedMatcherTest.php index 444785c49f07b98c99905ab8dde4198b6aca1896..de295389f519ba605fcafb44dcd788cc55fd6ab0 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Routing/NestedMatcherTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Routing/NestedMatcherTest.php @@ -60,6 +60,6 @@ public function testNestedMatcher() { $attributes = $matcher->matchRequest($request); - $this->assertEqual($attributes['_route'], 'route_a', 'The correct matching route was found.'); + $this->assertEqual($attributes['_route']->getOption('_name'), 'route_a', 'The correct matching route was found.'); } } diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/RouterPermissionTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/RouterPermissionTest.php new file mode 100644 index 0000000000000000000000000000000000000000..6980886ed16967d19767d350d0bb11d5f3d4a1fa --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Routing/RouterPermissionTest.php @@ -0,0 +1,50 @@ +<?php + +/** + * @file + * Contains Drupal\system\Tests\Routing\RouterPermissionTest. + */ + +namespace Drupal\system\Tests\Routing; + +use Drupal\simpletest\WebTestBase; + +/** + * Basic tests for access permissions in routes. + */ +class RouterPermissionTest extends WebTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = array('router_test'); + + public static function getInfo() { + return array( + 'name' => 'Router Permission tests', + 'description' => 'Function Tests for the routing permission system.', + 'group' => 'Routing', + ); + } + + /** + * Tests permission requirements on routes. + */ + public function testPermissionAccess() { + + $this->drupalGet('router_test/test7'); + $this->assertResponse(403, "Access denied for a route where we don't have a permission"); + + $this->drupalGet('router_test/test8'); + $this->assertResponse(403, 'Access denied by default if no access specified'); + + $user = $this->drupalCreateUser(array('access test7')); + $this->drupalLogin($user); + $this->drupalGet('router_test/test7'); + $this->assertResponse(200); + $this->assertNoRaw('Access denied'); + $this->assertRaw('test7text', 'The correct string was returned because the route was successful.'); + } +} diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml index 85e051763cce737a30df2e3003652f29d1706cd4..085895e2420821dee022bfb4c516ca5a957fd77f 100644 --- a/core/modules/system/system.routing.yml +++ b/core/modules/system/system.routing.yml @@ -1,4 +1,6 @@ -cron: +system.cron: pattern: '/cron/{key}' defaults: _controller: '\Drupal\system\CronController::run' + requirements: + _access_system_cron: 'TRUE' diff --git a/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/RouteTestSubscriber.php b/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/RouteTestSubscriber.php index a67e83f2702d43b9cdf3ef3e9808adac4419ed9e..4243f81beafa0c89488874e493f633bdb1672d5c 100644 --- a/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/RouteTestSubscriber.php +++ b/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/RouteTestSubscriber.php @@ -35,6 +35,8 @@ public function dynamicRoutes(RouteBuildEvent $event) { $collection = $event->getRouteCollection(); $route = new Route('/router_test/test5', array( '_content' => '\Drupal\router_test\TestControllers::test5' + ), array( + '_access' => 'TRUE' )); $collection->add('router_test_5', $route); } diff --git a/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/TestControllers.php b/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/TestControllers.php index bcf18b7b0fdd18985562abc444c1c772eff69fe4..adb5c345e0f184e715484e47d8156184a231c3c0 100644 --- a/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/TestControllers.php +++ b/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/TestControllers.php @@ -34,4 +34,16 @@ public function test5() { return "test5"; } + public function test6() { + return new Response('test6'); + } + + public function test7() { + return new Response('test7text'); + } + + public function test8() { + return new Response('test8'); + } + } diff --git a/core/modules/system/tests/modules/router_test/router_test.module b/core/modules/system/tests/modules/router_test/router_test.module index b3d9bbc7f3711e882119cd6b3af051245d859d04..ffbe070b8ea78bd52fd4ebae0df024c1479447dd 100644 --- a/core/modules/system/tests/modules/router_test/router_test.module +++ b/core/modules/system/tests/modules/router_test/router_test.module @@ -1 +1,13 @@ <?php + +/** + * Implements hook_permission(). + */ +function router_test_permission() { + return array( + 'access test7' => array( + 'title' => t('Access test7 route'), + 'description' => t('Test permission only.'), + ), + ); +} diff --git a/core/modules/system/tests/modules/router_test/router_test.routing.yml b/core/modules/system/tests/modules/router_test/router_test.routing.yml index cc177d38b37b2681068ffa59407a45de124c0c19..2c94ff245cf9ec74b1ce408dbb8df2d4af9e1a65 100644 --- a/core/modules/system/tests/modules/router_test/router_test.routing.yml +++ b/core/modules/system/tests/modules/router_test/router_test.routing.yml @@ -2,24 +2,46 @@ router_test_1: pattern: '/router_test/test1' defaults: _controller: '\Drupal\router_test\TestControllers::test1' + requirements: + _access: 'TRUE' router_test_2: pattern: '/router_test/test2' defaults: _content: '\Drupal\router_test\TestControllers::test2' + requirements: + _access: 'TRUE' router_test_3: pattern: '/router_test/test3/{value}' defaults: _content: '\Drupal\router_test\TestControllers::test3' + requirements: + _access: 'TRUE' router_test_4: pattern: '/router_test/test4/{value}' defaults: _content: '\Drupal\router_test\TestControllers::test4' value: 'narf' + requirements: + _access: 'TRUE' router_test_6: pattern: '/router_test/test6' defaults: _controller: '\Drupal\router_test\TestControllers::test1' + requirements: + _access: 'TRUE' + +router_test_7: + pattern: '/router_test/test7' + defaults: + _controller: '\Drupal\router_test\TestControllers::test7' + requirements: + _permission: 'access test7' + +router_test_8: + pattern: '/router_test/test8' + defaults: + _controller: '\Drupal\router_test\TestControllers::test8' diff --git a/core/modules/user/lib/Drupal/user/Access/RegisterAccessCheck.php b/core/modules/user/lib/Drupal/user/Access/RegisterAccessCheck.php new file mode 100644 index 0000000000000000000000000000000000000000..f8adae4e7fa8082e7ce2d468358b57ce1e464928 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Access/RegisterAccessCheck.php @@ -0,0 +1,32 @@ +<?php + +/** + * @file + * Contains Drupal\user\Access\RegisterAccessCheck. + */ + +namespace Drupal\user\Access; + +use Drupal\Core\Access\AccessCheckInterface; +use Symfony\Component\Routing\Route; +use Symfony\Component\HttpFoundation\Request; + +/** + * Access check for user registration routes. + */ +class RegisterAccessCheck implements AccessCheckInterface { + + /** + * Implements AccessCheckInterface::applies(). + */ + public function applies(Route $route) { + return array_key_exists('_access_user_register', $route->getRequirements()); + } + + /** + * Implements AccessCheckInterface::access(). + */ + public function access(Route $route, Request $request) { + return user_is_anonymous() && (config('user.settings')->get('register') != USER_REGISTER_ADMINISTRATORS_ONLY); + } +} diff --git a/core/modules/user/lib/Drupal/user/UserBundle.php b/core/modules/user/lib/Drupal/user/UserBundle.php new file mode 100644 index 0000000000000000000000000000000000000000..9ae8936a1dd71b324c2ee5c838698dfb5e3860b2 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/UserBundle.php @@ -0,0 +1,25 @@ +<?php + +/** + * @file + * Contains Drupal\system\UserBundle. + */ + +namespace Drupal\user; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\Bundle\Bundle; + +/** + * User dependency injection container. + */ +class UserBundle extends Bundle { + + /** + * Overrides Bundle::build(). + */ + public function build(ContainerBuilder $container) { + $container->register('access_check.user.register', 'Drupal\user\Access\RegisterAccessCheck') + ->addTag('access_check'); + } +} diff --git a/core/modules/user/lib/Drupal/user/UserRouteController.php b/core/modules/user/lib/Drupal/user/UserRouteController.php index 0cb5a1507cb04f57ced284c80cce9d54b7ba05fa..cf3b50ea2e784c1ccaec717d7ff9281811568d9b 100644 --- a/core/modules/user/lib/Drupal/user/UserRouteController.php +++ b/core/modules/user/lib/Drupal/user/UserRouteController.php @@ -8,7 +8,6 @@ namespace Drupal\user; use Symfony\Component\DependencyInjection\ContainerAware; -use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; /** * Returns responses for User module routes. @@ -22,12 +21,6 @@ class UserRouteController extends ContainerAware { * A renderable array containing the user registration form. */ public function register() { - // @todo Remove once access control is integrated with new routing system: - // http://drupal.org/node/1793520. - if (!user_register_access()) { - throw new AccessDeniedHttpException(); - } - $account = entity_create('user', array()); return entity_get_form($account, 'register'); } diff --git a/core/modules/user/user.routing.yml b/core/modules/user/user.routing.yml index 5e11fab24ef0f0492258ed06ce4d4711c0673a9f..c3a05d74f2805ebc297980a425373ab2c365e3f1 100644 --- a/core/modules/user/user.routing.yml +++ b/core/modules/user/user.routing.yml @@ -2,3 +2,5 @@ user_register: pattern: '/user/register' defaults: _content: '\Drupal\user\UserRouteController::register' + requirements: + _access_user_register: 'TRUE'