You are here

public function EditorSecurityTest::testSwitchingSecurity in Drupal 9

Same name and namespace in other branches
  1. 8 core/modules/editor/tests/src/Functional/EditorSecurityTest.php \Drupal\Tests\editor\Functional\EditorSecurityTest::testSwitchingSecurity()

Tests administrator security: is the user safe when switching text formats?

Tests 24 scenarios. Tests only with a text editor that is not XSS-safe.

When changing from a more restrictive text format with a text editor (or a text format without a text editor) to a less restrictive text format, it is possible that a malicious user could trigger an XSS.

E.g. when switching a piece of text that uses the Restricted HTML text format and contains a <script> tag to the Full HTML text format, the <script> tag would be executed. Unless we apply appropriate filtering.

File

core/modules/editor/tests/src/Functional/EditorSecurityTest.php, line 305

Class

EditorSecurityTest
Tests XSS protection for content creators when using text editors.

Namespace

Drupal\Tests\editor\Functional

Code

public function testSwitchingSecurity() {
  $expected = [
    [
      'node_id' => 1,
      // No text editor => no XSS filtering.
      'value' => self::$sampleContent,
      'format' => 'restricted_without_editor',
      'switch_to' => [
        'restricted_with_editor' => self::$sampleContentSecured,
        // Intersection of restrictions => most strict XSS filtering.
        'restricted_plus_dangerous_tag_with_editor' => self::$sampleContentSecured,
        // No text editor => no XSS filtering.
        'unrestricted_without_editor' => FALSE,
        'unrestricted_with_editor' => self::$sampleContentSecured,
      ],
    ],
    [
      'node_id' => 2,
      // Text editor => XSS filtering.
      'value' => self::$sampleContentSecured,
      'format' => 'restricted_with_editor',
      'switch_to' => [
        // No text editor => no XSS filtering.
        'restricted_without_editor' => FALSE,
        // Intersection of restrictions => most strict XSS filtering.
        'restricted_plus_dangerous_tag_with_editor' => self::$sampleContentSecured,
        // No text editor => no XSS filtering.
        'unrestricted_without_editor' => FALSE,
        'unrestricted_with_editor' => self::$sampleContentSecured,
      ],
    ],
    [
      'node_id' => 3,
      // Text editor => XSS filtering.
      'value' => self::$sampleContentSecuredEmbedAllowed,
      'format' => 'restricted_plus_dangerous_tag_with_editor',
      'switch_to' => [
        // No text editor => no XSS filtering.
        'restricted_without_editor' => FALSE,
        // Intersection of restrictions => most strict XSS filtering.
        'restricted_with_editor' => self::$sampleContentSecured,
        // No text editor => no XSS filtering.
        'unrestricted_without_editor' => FALSE,
        // Intersection of restrictions => most strict XSS filtering.
        'unrestricted_with_editor' => self::$sampleContentSecured,
      ],
    ],
    [
      'node_id' => 4,
      // No text editor => no XSS filtering.
      'value' => self::$sampleContent,
      'format' => 'unrestricted_without_editor',
      'switch_to' => [
        // No text editor => no XSS filtering.
        'restricted_without_editor' => FALSE,
        'restricted_with_editor' => self::$sampleContentSecured,
        // Intersection of restrictions => most strict XSS filtering.
        'restricted_plus_dangerous_tag_with_editor' => self::$sampleContentSecured,
        // From no editor, no security filters, to editor, still no security
        // filters: resulting content when viewed was already vulnerable, so
        // it must be intentional.
        'unrestricted_with_editor' => FALSE,
      ],
    ],
    [
      'node_id' => 5,
      // Text editor => XSS filtering.
      'value' => self::$sampleContentSecured,
      'format' => 'unrestricted_with_editor',
      'switch_to' => [
        // From editor, no security filters to security filters, no editor: no
        // risk.
        'restricted_without_editor' => FALSE,
        'restricted_with_editor' => self::$sampleContentSecured,
        // Intersection of restrictions => most strict XSS filtering.
        'restricted_plus_dangerous_tag_with_editor' => self::$sampleContentSecured,
        // From no editor, no security filters, to editor, still no security
        // filters: resulting content when viewed was already vulnerable, so
        // it must be intentional.
        'unrestricted_without_editor' => FALSE,
      ],
    ],
  ];

  // Log in as the privileged user, and for every sample, do the following:
  // - switch to every other text format/editor
  // - assert the XSS-filtered values that we get from the server
  $this
    ->drupalLogin($this->privilegedUser);
  $cookies = $this
    ->getSessionCookies();
  foreach ($expected as $case) {
    $this
      ->drupalGet('node/' . $case['node_id'] . '/edit');

    // Verify data- attributes.
    $body = $this
      ->assertSession()
      ->fieldExists('edit-body-0-value');
    $this
      ->assertSame(self::$sampleContent, $body
      ->getAttribute('data-editor-value-original'), 'The data-editor-value-original attribute is correctly set.');
    $this
      ->assertSame('false', (string) $body
      ->getAttribute('data-editor-value-is-changed'), 'The data-editor-value-is-changed attribute is correctly set.');

    // Switch to every other text format/editor and verify the results.
    foreach ($case['switch_to'] as $format => $expected_filtered_value) {
      $post = [
        'value' => self::$sampleContent,
        'original_format_id' => $case['format'],
      ];
      $client = $this
        ->getHttpClient();
      $response = $client
        ->post($this
        ->buildUrl('/editor/filter_xss/' . $format), [
        'body' => http_build_query($post),
        'cookies' => $cookies,
        'headers' => [
          'Accept' => 'application/json',
          'Content-Type' => 'application/x-www-form-urlencoded',
        ],
        'http_errors' => FALSE,
      ]);
      $this
        ->assertEquals(200, $response
        ->getStatusCode());
      $json = Json::decode($response
        ->getBody());
      $this
        ->assertSame($expected_filtered_value, $json, 'The value was correctly filtered for XSS attack vectors.');
    }
  }
}