Skip to content
Snippets Groups Projects
Unverified Commit ec91b0d0 authored by Alex Pott's avatar Alex Pott
Browse files

Issue #3485174 by fago, arthur_lorenz, alexpott, smustgrave: Menu APIs provide invalid CSRF tokens

parent 2f09ecf6
No related branches found
No related tags found
16 merge requests!12227Issue #3181946 by jonmcl, mglaman,!12079Issue #3523476 by matthiasm11: Add empty check on operator,!12024Fix: DocBlock comment for return value of Drupal\Core\Database\Connection::transactionManager(),!11974Draft: Issue #3495165 by catch, joeyroth, berdir, texas-bronius: Better warning...,!11934Issue #3520997: DefaultLazyPluginCollection unnecessarily instantiates plugins when sorting collection,!11887Issue #3520065: The migrate Row class API is incomplete,!11636Draft: Issue #3515643 by macsim: fieldNameExists method is inconsistent,!11515Issue #3480419 by mondrake, smustgrave, catch: Method...,!11380Issue #3490698 by catch, spokje: Bump MINIMUM_STABILITY back to 'stable' when...,!11281Use Drupal Core Leadership terminology in MAINTAINERS.txt,!11239Issue #3507548: Allow workspace changes listing to show all items, without a pager,!11238Fix issue #3051797,!11213Issue #3506743 by tomislav.matokovic: Increasing the color contrast for the navigation block title against the background of the navigation sidebar to at least 4.5:1,!11147Draft: Try to avoid manually setting required cache contexts,!11108Issue #3490298 by nicxvan: Profiles can be missed in OOP hooks,!11093Drupal on MongoDB 11.1.x
Pipeline #408162 passed
Pipeline: drupal

#408164

    ......@@ -1457,7 +1457,7 @@ services:
    class: Drupal\Core\Access\RouteProcessorCsrf
    tags:
    - { name: route_processor_outbound }
    arguments: ['@csrf_token']
    arguments: ['@csrf_token', '@request_stack']
    transliteration:
    class: Drupal\Core\Transliteration\PhpTransliteration
    arguments: [null, '@module_handler']
    ......
    ......@@ -6,6 +6,7 @@
    use Drupal\Core\Render\BubbleableMetadata;
    use Drupal\Core\Security\TrustedCallbackInterface;
    use Drupal\Core\RouteProcessor\OutboundRouteProcessorInterface;
    use Symfony\Component\HttpFoundation\RequestStack;
    use Symfony\Component\Routing\Route;
    /**
    ......@@ -13,21 +14,21 @@
    */
    class RouteProcessorCsrf implements OutboundRouteProcessorInterface, TrustedCallbackInterface {
    /**
    * The CSRF token generator.
    *
    * @var \Drupal\Core\Access\CsrfTokenGenerator
    */
    protected $csrfToken;
    /**
    * Constructs a RouteProcessorCsrf object.
    *
    * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
    * @param \Drupal\Core\Access\CsrfTokenGenerator $csrfToken
    * The CSRF token generator.
    * @param \Symfony\Component\HttpFoundation\RequestStack|null $requestStack
    * The request stack.
    */
    public function __construct(CsrfTokenGenerator $csrf_token) {
    $this->csrfToken = $csrf_token;
    public function __construct(
    protected CsrfTokenGenerator $csrfToken,
    protected ?RequestStack $requestStack = NULL,
    ) {
    if ($requestStack === NULL) {
    $this->requestStack = \Drupal::service('request_stack');
    }
    }
    /**
    ......@@ -42,7 +43,7 @@ public function processOutbound($route_name, Route $route, array &$parameters, ?
    }
    // Adding this to the parameters means it will get merged into the query
    // string when the route is compiled.
    if (!$bubbleable_metadata) {
    if (!$bubbleable_metadata || $this->requestStack->getCurrentRequest()->getRequestFormat() !== 'html') {
    $parameters['token'] = $this->csrfToken->get($path);
    }
    else {
    ......@@ -51,7 +52,6 @@ public function processOutbound($route_name, Route $route, array &$parameters, ?
    $placeholder_render_array = [
    '#lazy_builder' => ['route_processor_csrf:renderPlaceholderCsrfToken', [$path]],
    ];
    // Instead of setting an actual CSRF token as the query string, we set
    // the placeholder, which will be replaced at the very last moment. This
    // ensures links with CSRF tokens don't break cacheability.
    ......
    ......@@ -103,6 +103,7 @@ public function routes() {
    ],
    [
    '_access' => 'TRUE',
    '_format' => 'json',
    ],
    [
    'parameters' => [
    ......
    ......@@ -5,9 +5,12 @@
    namespace Drupal\Tests\Core\Access;
    use Drupal\Component\Utility\Crypt;
    use Drupal\Core\Access\CsrfTokenGenerator;
    use Drupal\Core\Render\BubbleableMetadata;
    use Drupal\Tests\UnitTestCase;
    use Drupal\Core\Access\RouteProcessorCsrf;
    use PHPUnit\Framework\MockObject\MockObject;
    use Symfony\Component\HttpFoundation\RequestStack;
    use Symfony\Component\Routing\Route;
    /**
    ......@@ -18,17 +21,18 @@ class RouteProcessorCsrfTest extends UnitTestCase {
    /**
    * The mock CSRF token generator.
    *
    * @var \Drupal\Core\Access\CsrfTokenGenerator|\PHPUnit\Framework\MockObject\MockObject
    */
    protected $csrfToken;
    protected CsrfTokenGenerator&MockObject $csrfToken;
    /**
    * The mock request stack.
    */
    protected RequestStack&MockObject $requestStack;
    /**
    * The route processor.
    *
    * @var \Drupal\Core\Access\RouteProcessorCsrf
    */
    protected $processor;
    protected RouteProcessorCsrf $processor;
    /**
    * {@inheritdoc}
    ......@@ -40,7 +44,20 @@ protected function setUp(): void {
    ->disableOriginalConstructor()
    ->getMock();
    $this->processor = new RouteProcessorCsrf($this->csrfToken);
    $this->requestStack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')
    ->disableOriginalConstructor()
    ->getMock();
    $request = $this->createMock('Symfony\Component\HttpFoundation\Request');
    $request->expects($this->any())
    ->method('getRequestFormat')
    ->willReturn('html');
    $this->requestStack->expects($this->any())
    ->method('getCurrentRequest')
    ->willReturn($request);
    $this->processor = new RouteProcessorCsrf($this->csrfToken, $this->requestStack);
    }
    /**
    ......@@ -122,4 +139,37 @@ public function testProcessOutboundDynamicTwo(): void {
    $this->assertEquals((new BubbleableMetadata())->setAttachments(['placeholders' => [$placeholder => $placeholder_render_array]]), $bubbleable_metadata);
    }
    /**
    * Tests JSON requests to get no placeholders, but real tokens.
    */
    public function testProcessOutboundJsonFormat(): void {
    // Create a new request mock that returns 'json' format.
    $request = $this->createMock('Symfony\Component\HttpFoundation\Request');
    $request->expects($this->once())
    ->method('getRequestFormat')
    ->willReturn('json');
    $this->requestStack = $this->createMock('Symfony\Component\HttpFoundation\RequestStack');
    $this->requestStack->expects($this->once())
    ->method('getCurrentRequest')
    ->willReturn($request);
    // Mock that the CSRF token service should be called once with 'test-path'
    // and return a test token.
    $this->csrfToken->expects($this->any())
    ->method('get')
    ->with('test-path')
    ->willReturn('real_token_value');
    $this->processor = new RouteProcessorCsrf($this->csrfToken, $this->requestStack);
    $route = new Route('/test-path', [], ['_csrf_token' => 'TRUE']);
    $parameters = [];
    // For JSON requests, the actual CSRF token should be in parameters,
    // regardless of whether cache metadata is present.
    $this->processor->processOutbound('test', $route, $parameters);
    $this->assertEquals('real_token_value', $parameters['token']);
    $this->processor->processOutbound('test', $route, $parameters, new BubbleableMetadata());
    $this->assertEquals('real_token_value', $parameters['token']);
    }
    }
    0% Loading or .
    You are about to add 0 people to the discussion. Proceed with caution.
    Please register or to comment