Verified Commit d3e51377 authored by catch's avatar catch Committed by Jess
Browse files

task: #3633160 Add test coverage for SA-CORE-2026-010

By: xjm
By: kim.pepper
By: mohit_aghera
By: benjifisher
By: larowlan
By: poker10
By: neclimdul
By: catch
By: smustgrave
(cherry picked from commit b0a3d2f5)
parent 0f4c82b7
Loading
Loading
Loading
Loading
+9 −0
Changes for core/modules/image/tests/modules/image_module_test/image_module_test.routing.yml: 9 added lines, 0 removed lines.
Original line number Diff line number Diff line
image_module_test.image_style_protected:
  path: '/image-test-private-files/styles/{image_style}/{scheme}'
  defaults:
    _controller: '\Drupal\image\Controller\ImageStyleDownloadController::deliver'
    required_derivative_scheme: 'image-test-dummy'
  requirements:
    _access: 'TRUE'
  options:
    no_cache: TRUE
+5 −0
Changes for core/modules/image/tests/modules/image_module_test/image_module_test.services.yml: 5 added lines, 0 removed lines.
Original line number Diff line number Diff line
services:
  stream_wrapper.image_test_private:
    class: Drupal\image_module_test\StreamWrapper\ImageTestDummyStreamWrapper
    tags:
      - { name: stream_wrapper, scheme: image-test-dummy }
+9 −1
Changes for core/modules/image/tests/modules/image_module_test/src/Hook/ImageModuleTestHooks.php: 9 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -52,11 +52,19 @@ public function imageStyleFlush($style, $path = NULL): void {
   * Implements hook_file_download().
   */
  #[Hook('file_download')]
  public function fileDownload($uri): array {
  public function fileDownload($uri): array|int {
    $default_uri = \Drupal::keyValue('image')->get('test_file_download', FALSE);
    if ($default_uri == $uri) {
      return ['X-Image-Owned-By' => 'image_module_test'];
    }
    // When we have custom stream wrapper is enabled, do the validation based
    // on state variable to allow it.
    if (str_starts_with($uri, 'image-test-dummy://')) {
      if (\Drupal::state()->get('image_module_test.allow_image_test_dummy', FALSE)) {
        return ['X-Allowed-By' => 'image_module_test'];
      }
      return -1;
    }
    return [];
  }

+46 −0
Changes for core/modules/image/tests/modules/image_module_test/src/StreamWrapper/ImageTestDummyStreamWrapper.php: 46 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\image_module_test\StreamWrapper;

use Drupal\Core\StreamWrapper\LocalStream;

/**
 * A non-public, non-private local stream wrapper for image style access tests.
 *
 * Mimics a contributed stream wrapper (e.g. s3fs) so tests can
 * cover the case where image style derivatives are served from a scheme
 * that is neither core 'public' nor core 'private'.
 */
class ImageTestDummyStreamWrapper extends LocalStream {

  /**
   * {@inheritdoc}
   */
  public function getName(): string {
    return 'Image test private files';
  }

  /**
   * {@inheritdoc}
   */
  public function getDescription(): string {
    return 'Image module test wrapper for non-public access checks.';
  }

  /**
   * {@inheritdoc}
   */
  public function getDirectoryPath(): string {
    return \Drupal::getContainer()->getParameter('site.path') . '/files';
  }

  /**
   * {@inheritdoc}
   */
  public function getExternalUrl(): string {
    return '';
  }

}
+31 −1
Changes for core/modules/image/tests/src/Functional/ImageStyleDownloadAccessControlTest.php: 31 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@
use Drupal\Core\Url;
use Drupal\file\Entity\File;
use Drupal\image\Entity\ImageStyle;
use Drupal\image\ImageStyleInterface;
use Drupal\Tests\BrowserTestBase;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
@@ -22,7 +23,7 @@ class ImageStyleDownloadAccessControlTest extends BrowserTestBase {
  /**
   * {@inheritdoc}
   */
  protected static $modules = ['image'];
  protected static $modules = ['image', 'image_module_test'];

  /**
   * {@inheritdoc}
@@ -111,6 +112,35 @@ public function testPrivateThroughPublicRoute(): void {
    $this->assertSession()->statusCodeEquals(403);
  }

  /**
   * Tests that hook_file_download runs for non-public scheme derivatives.
   */
  public function testNonPublicSchemeInvokesFileDownloadHook(): void {
    $this->fileSystem->copy(\Drupal::root() . '/core/tests/fixtures/files/image-1.png', 'image-test-dummy://image.png');

    $token = $this->style->getPathToken('image-test-dummy://image.png');
    $url = Url::fromRoute('image_module_test.image_style_protected', [
      'image_style' => $this->style->id(),
      'scheme' => 'image-test-dummy',
    ], [
      'query' => [
        'file' => 'image.png',
        ImageStyleInterface::TOKEN => $token,
      ],
      'absolute' => TRUE,
    ])->toString();

    // The hook denies by default, so the derivative must not be served.
    $this->drupalGet($url);
    $this->assertSession()->statusCodeEquals(403);

    // Set the state variable so that file download hook in image_module_test
    // allows the access of the file.
    \Drupal::state()->set('image_module_test.allow_image_test_dummy', TRUE);
    $this->drupalGet($url);
    $this->assertSession()->statusCodeEquals(200);
  }

  /**
   * Ensures that public:// access is forbidden through image.style.private.
   */