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

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

(cherry picked from commit ec91b0d0)
parent 3fe75188
No related branches found
No related tags found
5 merge requests!12745Fixed: Path alias language doesn't changes on changing of node language,!12537Add ViewsConfigUpdater deprecation support for default_argument_skip_url,!11958Issue #3490507 by alexpott, smustgrave: Fix bogus mocking in...,!11769Issue #3517987: Add option to contextual filters to encode slashes in query parameter.,!11185Issue #3477324 by andypost, alexpott: Fix usage of str_getcsv() and fgetcsv() for PHP 8.4
Pipeline #413099 passed with warnings
Pipeline: drupal

#413134

    Pipeline: drupal

    #413130

      Pipeline: drupal

      #413126

        +7
        ......@@ -1481,7 +1481,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