diff --git a/core/core.services.yml b/core/core.services.yml index 12f3a3c..beef3f9 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -261,7 +261,7 @@ services: - [setFinalMatcher, ['@router.matcher.final_matcher']] url_generator: class: Drupal\Core\Routing\UrlGenerator - arguments: ['@router.route_provider', '@path_processor_manager', '@config.factory', '@settings'] + arguments: ['@router.route_provider', '@path_processor_manager', '@route_processor_manager', '@config.factory', '@settings'] calls: - [setRequest, ['@?request']] - [setContext, ['@?router.request_context']] @@ -446,6 +446,11 @@ services: arguments: ['@controller_resolver'] tags: - { name: access_check } + access_check.csrf: + class: Drupal\Core\Access\CsrfAccessCheck + tags: + - { name: access_check } + arguments: ['@csrf_token'] maintenance_mode_subscriber: class: Drupal\Core\EventSubscriber\MaintenanceModeSubscriber tags: @@ -504,6 +509,8 @@ services: tags: - { name: event_subscriber } arguments: [['@exception_controller', execute]] + route_processor_manager: + class: Drupal\Core\RouteProcessor\RouteProcessorManager path_processor_manager: class: Drupal\Core\PathProcessor\PathProcessorManager path_processor_decode: @@ -522,6 +529,11 @@ services: - { name: path_processor_inbound, priority: 100 } - { name: path_processor_outbound, priority: 300 } arguments: ['@path.alias_manager'] + route_processer_csrf: + class: Drupal\Core\Access\RouteProcessorCsrf + tags: + - { name: route_processor_outbound } + arguments: ['@csrf_token'] transliteration: class: Drupal\Core\Transliteration\PHPTransliteration flood: diff --git a/core/lib/Drupal/Core/Access/CsrfAccessCheck.php b/core/lib/Drupal/Core/Access/CsrfAccessCheck.php new file mode 100644 index 0000000..1f329ed --- /dev/null +++ b/core/lib/Drupal/Core/Access/CsrfAccessCheck.php @@ -0,0 +1,71 @@ +get() using the same value as the + * "_csrf" parameter in the route. + */ +class CsrfAccessCheck implements StaticAccessCheckInterface { + + /** + * The CSRF token generator. + * + * @var \Drupal\Core\Access\CsrfTokenGenerator + */ + protected $csrfToken; + + /** + * Constructs a CsrfAccessCheck object. + * + * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token + * The CSRF token generator. + */ + function __construct(CsrfTokenGenerator $csrf_token) { + $this->csrfToken = $csrf_token; + } + + /** + * {@inheritdoc} + */ + public function appliesTo() { + return array('_csrf'); + } + + /** + * {@inheritdoc} + */ + public function access(Route $route, Request $request, AccountInterface $account) { + // If this is the controller request, check CSRF access as normal. + if ($request->attributes->get('_controller_request')) { + return $this->csrfToken->validate($request->query->get('token'), $route->getRequirement('_csrf')) ? static::ALLOW : static::KILL; + } + + // Otherwise, this could be another requested access check that we don't + // want to check CSRF tokens on. + $conjunction = $route->getOption('_access_mode') ?: 'ANY'; + // Return ALLOW if all access checks are needed. + if ($conjunction == 'ALL') { + return static::ALLOW; + } + // Return DENY otherwise, as another access checker should grant access + // for the route. + else { + return static::DENY; + } + } + +} diff --git a/core/lib/Drupal/Core/Access/RouteProcessorCsrf.php b/core/lib/Drupal/Core/Access/RouteProcessorCsrf.php new file mode 100644 index 0000000..e9633a7 --- /dev/null +++ b/core/lib/Drupal/Core/Access/RouteProcessorCsrf.php @@ -0,0 +1,49 @@ +csrfToken = $csrf_token; + } + + /** + * {@inheritdoc} + */ + public function processOutbound(Route $route, array &$parameters) { + if ($route->hasRequirement('_csrf')) { + // Adding this to the parameters means it will get merged into the query + // string when the route is compiled. + $parameters['token'] = $this->csrfToken->get($route->getRequirement('_csrf')); + } + } + +} + diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php index 9cd8f5a..8a525c9 100644 --- a/core/lib/Drupal/Core/CoreServiceProvider.php +++ b/core/lib/Drupal/Core/CoreServiceProvider.php @@ -14,6 +14,7 @@ use Drupal\Core\DependencyInjection\Compiler\RegisterKernelListenersPass; use Drupal\Core\DependencyInjection\Compiler\RegisterAccessChecksPass; use Drupal\Core\DependencyInjection\Compiler\RegisterPathProcessorsPass; +use Drupal\Core\DependencyInjection\Compiler\RegisterRouteProcessorsPass; use Drupal\Core\DependencyInjection\Compiler\RegisterRouteFiltersPass; use Drupal\Core\DependencyInjection\Compiler\RegisterRouteEnhancersPass; use Drupal\Core\DependencyInjection\Compiler\RegisterParamConvertersPass; @@ -63,6 +64,7 @@ public function register(ContainerBuilder $container) { $container->addCompilerPass(new RegisterServicesForDestructionPass()); // Add the compiler pass that will process the tagged services. $container->addCompilerPass(new RegisterPathProcessorsPass()); + $container->addCompilerPass(new RegisterRouteProcessorsPass()); $container->addCompilerPass(new ListCacheBinsPass()); // Add the compiler pass for appending string translators. $container->addCompilerPass(new RegisterStringTranslatorsPass()); diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterRouteProcessorsPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterRouteProcessorsPass.php new file mode 100644 index 0000000..dd95869 --- /dev/null +++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterRouteProcessorsPass.php @@ -0,0 +1,34 @@ +hasDefinition('route_processor_manager')) { + return; + } + $manager = $container->getDefinition('route_processor_manager'); + // Add outbound route processors. + foreach ($container->findTaggedServiceIds('route_processor_outbound') as $id => $attributes) { + $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0; + $manager->addMethodCall('addOutbound', array(new Reference($id), $priority)); + } + } + +} diff --git a/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php index 09261a6..0c31999 100644 --- a/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php @@ -11,6 +11,7 @@ use Drupal\Core\Session\AccountInterface; use Symfony\Cmf\Component\Routing\RouteObjectInterface; use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; @@ -59,13 +60,29 @@ public function __construct(AccessManager $access_manager, AccountInterface $cur */ public function onKernelRequestAccessCheck(GetResponseEvent $event) { $request = $event->getRequest(); + + // The controller is being handled by the HTTP kernel, so add an attribute + // to tell us this is the controller request. + $request->attributes->set('_controller_request', TRUE); + if (!$request->attributes->has(RouteObjectInterface::ROUTE_OBJECT)) { // If no Route is available it is likely a static resource and access is // handled elsewhere. return; } - $access = $this->accessManager->check($request->attributes->get(RouteObjectInterface::ROUTE_OBJECT), $request, $this->currentUser); + // Wrap this in a try/catch to ensure the '_controller_request' attribute + // can always be removed. + try { + $access = $this->accessManager->check($request->attributes->get(RouteObjectInterface::ROUTE_OBJECT), $request, $this->currentUser); + } + catch (\Exception $e) { + $request->attributes->remove('_controller_request'); + throw $e; + } + + $request->attributes->remove('_controller_request'); + if (!$access) { throw new AccessDeniedHttpException(); } diff --git a/core/lib/Drupal/Core/PathProcessor/OutboundPathProcessorInterface.php b/core/lib/Drupal/Core/PathProcessor/OutboundPathProcessorInterface.php index 347a877..9e69001 100644 --- a/core/lib/Drupal/Core/PathProcessor/OutboundPathProcessorInterface.php +++ b/core/lib/Drupal/Core/PathProcessor/OutboundPathProcessorInterface.php @@ -8,6 +8,7 @@ namespace Drupal\Core\PathProcessor; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Route; /** * Defines an interface for classes that process the outbound path. diff --git a/core/lib/Drupal/Core/RouteProcessor/OutboundRouteProcessorInterface.php b/core/lib/Drupal/Core/RouteProcessor/OutboundRouteProcessorInterface.php new file mode 100644 index 0000000..c9bda24 --- /dev/null +++ b/core/lib/Drupal/Core/RouteProcessor/OutboundRouteProcessorInterface.php @@ -0,0 +1,32 @@ +outboundProcessors[$priority][] = $processor; + $this->sortedOutbound = array(); + } + + /** + * {@inheritdoc} + */ + public function processOutbound(Route $route, array &$parameters) { + $processors = $this->getOutbound(); + foreach ($processors as $processor) { + $processor->processOutbound($route, $parameters); + } + } + + /** + * Returns the sorted array of outbound processors. + * + * @return array + * An array of processor objects. + */ + protected function getOutbound() { + if (empty($this->sortedOutbound)) { + $this->sortedOutbound = $this->sortProcessors(); + } + + return $this->sortedOutbound; + } + + /** + * Sorts the processors according to priority. + */ + protected function sortProcessors() { + $sorted = array(); + krsort($this->outboundProcessors); + + foreach ($this->outboundProcessors as $processors) { + $sorted = array_merge($sorted, $processors); + } + return $sorted; + } + +} diff --git a/core/lib/Drupal/Core/Routing/NullGenerator.php b/core/lib/Drupal/Core/Routing/NullGenerator.php index b6e2609..1430f1f 100644 --- a/core/lib/Drupal/Core/Routing/NullGenerator.php +++ b/core/lib/Drupal/Core/Routing/NullGenerator.php @@ -9,6 +9,7 @@ use Symfony\Component\Routing\RequestContext; use Symfony\Component\Routing\Exception\RouteNotFoundException; +use Symfony\Component\Routing\Route; /** * No-op implementation of a Url Generator, needed for backward compatibility. diff --git a/core/lib/Drupal/Core/Routing/UrlGenerator.php b/core/lib/Drupal/Core/Routing/UrlGenerator.php index bf6956f..1bb4b53 100644 --- a/core/lib/Drupal/Core/Routing/UrlGenerator.php +++ b/core/lib/Drupal/Core/Routing/UrlGenerator.php @@ -19,6 +19,7 @@ use Drupal\Component\Utility\Url; use Drupal\Core\Config\ConfigFactory; use Drupal\Core\PathProcessor\OutboundPathProcessorInterface; +use Drupal\Core\RouteProcessor\OutboundRouteProcessorInterface; /** * Generates URLs from route names and parameters. @@ -40,6 +41,13 @@ class UrlGenerator extends ProviderBasedGenerator implements UrlGeneratorInterfa protected $pathProcessor; /** + * The route processor. + * + * @var \Drupal\Tests\Core\RouteProcessor\OutboundRouteProcessorInterface + */ + protected $routeProcessor; + + /** * The base path to use for urls. * * @var string @@ -77,10 +85,11 @@ class UrlGenerator extends ProviderBasedGenerator implements UrlGeneratorInterfa * @param \Symfony\Component\HttpKernel\Log\LoggerInterface $logger * An optional logger for recording errors. */ - public function __construct(RouteProviderInterface $provider, OutboundPathProcessorInterface $path_processor, ConfigFactory $config, Settings $settings, LoggerInterface $logger = NULL) { + public function __construct(RouteProviderInterface $provider, OutboundPathProcessorInterface $path_processor, OutboundRouteProcessorInterface $route_processor, ConfigFactory $config, Settings $settings, LoggerInterface $logger = NULL) { parent::__construct($provider, $logger); $this->pathProcessor = $path_processor; + $this->routeProcessor = $route_processor; $this->mixedModeSessions = $settings->get('mixed_mode_sessions', FALSE); $allowed_protocols = $config->get('system.filter')->get('protocols') ?: array('http', 'https'); Url::setAllowedProtocols($allowed_protocols); @@ -167,10 +176,13 @@ public function generate($name, $parameters = array(), $absolute = FALSE) { public function generateFromRoute($name, $parameters = array(), $options = array()) { $absolute = !empty($options['absolute']); $route = $this->getRoute($name); + $this->processRoute($route, $parameters); + // Symfony adds any parameters that are not path slugs as query strings. if (isset($options['query']) && is_array($options['query'])) { $parameters = (array) $parameters + $options['query']; } + $path = $this->getInternalPathFromRoute($route, $parameters); $path = $this->processPath($path, $options); $fragment = ''; @@ -179,6 +191,7 @@ public function generateFromRoute($name, $parameters = array(), $options = array $fragment = '#' . $fragment; } } + $base_url = $this->context->getBaseUrl(); if (!$absolute || !$host = $this->context->getHost()) { return $base_url . $path . $fragment; @@ -336,6 +349,19 @@ protected function processPath($path, &$options = array()) { } /** + * Passes the route to the processor manager for altering before complation. + * + * @param \Symfony\Component\Routing\Route $route + * The route object to process. + * + * @param array $parameters + * An array of parameters to be passed to the route compiler. + */ + protected function processRoute(SymfonyRoute $route, array &$parameters) { + $this->routeProcessor->processOutbound($route, $parameters); + } + + /** * Returns whether or not the url generator has been initialized. * * @return bool diff --git a/core/modules/shortcut/lib/Drupal/shortcut/Controller/ShortcutSetController.php b/core/modules/shortcut/lib/Drupal/shortcut/Controller/ShortcutSetController.php index 7123ae5..d2f38eb 100644 --- a/core/modules/shortcut/lib/Drupal/shortcut/Controller/ShortcutSetController.php +++ b/core/modules/shortcut/lib/Drupal/shortcut/Controller/ShortcutSetController.php @@ -33,9 +33,8 @@ class ShortcutSetController extends ControllerBase { * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException */ public function addShortcutLinkInline(ShortcutSetInterface $shortcut_set, Request $request) { - $token = $request->query->get('token'); $link = $request->query->get('link'); - if (isset($token) && drupal_valid_token($token, 'shortcut-add-link') && shortcut_valid_link($link)) { + if (shortcut_valid_link($link)) { $item = menu_get_item($link); $title = ($item && $item['title']) ? $item['title'] : $link; $link = array( diff --git a/core/modules/shortcut/shortcut.module b/core/modules/shortcut/shortcut.module index dc9bd86..4f1245c 100644 --- a/core/modules/shortcut/shortcut.module +++ b/core/modules/shortcut/shortcut.module @@ -456,14 +456,15 @@ function shortcut_preprocess_page(&$variables) { $link_mode = isset($mlid) ? "remove" : "add"; if ($link_mode == "add") { - $query['token'] = drupal_get_token('shortcut-add-link'); $link_text = shortcut_set_switch_access() ? t('Add to %shortcut_set shortcuts', array('%shortcut_set' => $shortcut_set->label())) : t('Add to shortcuts'); - $link_path = 'admin/config/user-interface/shortcut/manage/' . $shortcut_set->id() . '/add-link-inline'; + $route_name = 'shortcut.link_add_inline'; + $route_parameters = array('shortcut_set' => $shortcut_set->id()); } else { $query['mlid'] = $mlid; $link_text = shortcut_set_switch_access() ? t('Remove from %shortcut_set shortcuts', array('%shortcut_set' => $shortcut_set->label())) : t('Remove from shortcuts'); - $link_path = 'admin/config/user-interface/shortcut/link/' . $mlid . '/delete'; + $route_name = 'shortcut.link_delete'; + $route_parameters = array('menu_link' => $mlid); } if (theme_get_setting('shortcut_module_link')) { @@ -476,7 +477,8 @@ function shortcut_preprocess_page(&$variables) { '#prefix' => '