Unverified Commit c2809119 authored by Alex Pott's avatar Alex Pott
Browse files

fix: #3587298 RouteProcessorCsrf::processOutbound() should bubble 'session'...

fix: #3587298 RouteProcessorCsrf::processOutbound() should bubble 'session' cache context for non-HTML requests

By: petar_basic
parent 5253354e
Loading
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -44,6 +44,9 @@ public function processOutbound($route_name, Route $route, array &$parameters, ?
      // string when the route is compiled.
      if (!$bubbleable_metadata || $this->requestStack->getCurrentRequest()->getRequestFormat() !== 'html') {
        $parameters['token'] = $this->csrfToken->get($path);
        // Tokens are per session; the response carrying the URL must vary by
        // session so it isn't cached across users sharing other contexts.
        $bubbleable_metadata?->addCacheContexts(['session']);
      }
      else {
        // Generate a placeholder and a render array to replace it.
+3 −1
Original line number Diff line number Diff line
@@ -122,7 +122,9 @@ protected function getNormalizedPostEntity() {
   */
  protected function getExpectedCacheContexts() {
    // @see ::createEntity()
    return ['url.site'];
    // 'session' is bubbled by URL generation for CSRF-protected routes
    // referenced in the response normalization.
    return ['session', 'url.site'];
  }

  /**
+21 −0
Original line number Diff line number Diff line
@@ -61,4 +61,25 @@ protected function assertResponseWhenMissingAuthentication($method, ResponseInte
  protected function assertAuthenticationEdgeCases($method, Url $url, array $request_options) {
  }

  /**
   * {@inheritdoc}
   *
   * Stateless authentication via basic_auth does not persist a session between
   * requests, so the CSRF token seed in the session metadata bag is regenerated
   * on every request. Any URL carrying a `?token=` (e.g. admin operation links
   * surfaced as Link headers) therefore legitimately differs between the HEAD
   * and GET request the test base issues for the same resource. Replace the
   * token value with a placeholder so the comparison still asserts URL
   * structure and link relations exactly.
   */
  protected function normalizeHeadersForGetHeadComparison(array $headers): array {
    if (isset($headers['Link'])) {
      $headers['Link'] = array_map(
        fn ($value) => preg_replace('/(\?|&)token=[^&>]+/', '$1token=NORMALIZED', $value),
        $headers['Link']
      );
    }
    return $headers;
  }

}
+22 −2
Original line number Diff line number Diff line
@@ -411,6 +411,26 @@ protected function getExpectedCacheContexts() {
    ];
  }

  /**
   * Normalizes response headers before HEAD vs GET equality assertion.
   *
   * Authentication providers that don't persist a session between requests
   * (e.g. basic_auth) cause a fresh CSRF token seed to be generated for each
   * request, so HEAD and GET produce different token values in any URL that
   * carries `?token=`. The URL structure and link relations are still the
   * same. Override this hook to neutralize such legitimately-varying values
   * for the comparison.
   *
   * @param array $headers
   *   Response headers as returned by Guzzle.
   *
   * @return array
   *   The headers, with provider-specific normalization applied.
   */
  protected function normalizeHeadersForGetHeadComparison(array $headers): array {
    return $headers;
  }

  /**
   * Tests all CRUD operations in a single test method.
   */
@@ -686,8 +706,8 @@ protected function doTestGet(): void {
      }
      return $headers;
    };
    $get_headers = $header_cleaner($get_headers);
    $head_headers = $header_cleaner($head_headers);
    $get_headers = $this->normalizeHeadersForGetHeadComparison($header_cleaner($get_headers));
    $head_headers = $this->normalizeHeadersForGetHeadComparison($header_cleaner($head_headers));
    $this->assertSame($get_headers, $head_headers);

    $this->resourceConfigStorage->load(static::$resourceConfigId)->disable()->save();
+9 −0
Original line number Diff line number Diff line
@@ -90,6 +90,15 @@ protected function getNormalizedPostEntity() {
    return [];
  }

  /**
   * {@inheritdoc}
   */
  protected function getExpectedCacheContexts() {
    // 'session' is bubbled by URL generation for CSRF-protected routes
    // referenced in the response normalization.
    return array_merge(['session'], parent::getExpectedCacheContexts());
  }

  /**
   * {@inheritdoc}
   */
Loading