Problem/Motivation

⚠️ Discovered while reviewing #3312442: [PP-1] Make ready for CKEditor 5, which is updating the ckeditor_bidi module to support CKEditor 5 and provide an automatic upgrade path.

<p lang> <* lang>

should get simplified to

<p> <* lang>

Steps to reproduce

See unit test, or alternatively create a dummy CKEditor 5 plugin with

    elements:
      - <p dir="ltr rtl">

You'll notice that filter_html gets configured to not allow just <p>, but <p dir="ltr rtl">, even though that's already implied by that filter (see \Drupal\filter\Plugin\Filter\FilterHtml::getHTMLRestrictions()).

Proposed resolution

Automatically resolve global attributes. This is something we didn't notice in #3231334: Global attributes (<* lang> and <* dir="ltr rtl">): validation + support (fix data loss).

Remaining tasks

  • Test coverage.
  • Reviews

User interface changes

No more nonsensical additions to filter_html.

API changes

None.

Data model changes

None.

Release notes snippet

N/A

CommentFileSizeAuthor
#36 3314478-36.patch20.36 KBWim Leers
#36 interdiff.txt8.44 KBWim Leers
#33 reroll_diff_28-33.txt6.82 KBpooja saraah
#33 3314478-33.patch19.82 KBpooja saraah
#28 3314478-28.patch20.17 KBWim Leers
#28 interdiff.txt628 bytesWim Leers
#27 3314478-27.patch20.71 KBWim Leers
#27 interdiff.txt1.54 KBWim Leers
#27 Screen Shot 2022-10-13 at 1.33.02 PM.png370.37 KBWim Leers
#25 3314478-25.patch19.17 KBWim Leers
#25 interdiff.txt1000 bytesWim Leers
#24 3314478-24.patch19.17 KBWim Leers
#24 interdiff.txt4.92 KBWim Leers
#23 3314478-23.patch17.13 KBWim Leers
#23 interdiff.txt946 bytesWim Leers
#20 3314478-20.patch16.21 KBWim Leers
#20 interdiff.txt5.87 KBWim Leers
#18 3314478-18.patch10.98 KBWim Leers
#18 interdiff.txt2.75 KBWim Leers
#13 3314478-13.patch8.54 KBWim Leers
#13 interdiff.txt628 bytesWim Leers
#12 3314478-12.patch9.07 KBWim Leers
#12 interdiff.txt2.54 KBWim Leers
#11 3314478-11.patch8.96 KBWim Leers
#11 interdiff.txt1.75 KBWim Leers
#8 3314478-8.patch8.63 KBWim Leers
#8 interdiff.txt2.99 KBWim Leers
#7 3314478-7.patch7.86 KBWim Leers
#7 interdiff.txt5.21 KBWim Leers
#6 3314478-6.patch3.6 KBWim Leers
#6 interdiff.txt1.01 KBWim Leers
#4 3314478-4.patch3.6 KBWim Leers
#4 interdiff.txt1.48 KBWim Leers
#2 3314478-2.patch2.42 KBWim Leers
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Wim Leers created an issue. See original summary.

Wim Leers’s picture

Status: Active » Needs review
FileSize
2.42 KB
Wim Leers’s picture

Wim Leers’s picture

The last submitted patch, 2: 3314478-2.patch, failed testing. View results

Wim Leers’s picture

Wim Leers’s picture

Expand test coverage more: let's make sure that we really did not miss any edge case this time around! 🤓

  1. +++ b/core/modules/ckeditor5/tests/src/Unit/HTMLRestrictionsTest.php
    @@ -538,6 +538,74 @@ public function providerConvenienceConstructors(): \Generator {
    +    yield '<p foo="a b" bar> + <* foo="b a"> results in <p> getting simplified' => [
    +      '<p foo="a b" bar> <* foo="b a">',
    +      ['p' => ['bar' => TRUE], '*' => ['foo' => ['b' => TRUE, 'a' => TRUE]]],
    +    ];
    +    yield '<* foo="b a"> <p foo="a b" bar> results in <p> getting simplified' => [
    +      '<* foo="b a"> <p foo="a b" bar>',
    +      ['p' => ['bar' => TRUE], '*' => ['foo' => ['b' => TRUE, 'a' => TRUE]]],
    +    ];
    +    yield '<p foo="a b c"> + <* foo="b a"> results in <p> getting simplified' => [
    +      '<p foo="a b c"> <* foo="b a">',
    +      ['p' => ['foo' => ['c' => TRUE]], '*' => ['foo' => ['b' => TRUE, 'a' => TRUE]]],
    +    ];
    +    yield '<* foo="b a"> <p foo="a b c"> results in <p> getting simplified' => [
    +      '<* foo="b a"> <p foo="a b c">',
    +      ['p' => ['foo' => ['c' => TRUE]], '*' => ['foo' => ['b' => TRUE, 'a' => TRUE]]],
    +    ];
    

    This is adding test coverage for attribute restrictions on a tag being a superset of the restrictions on the global attributes <*> tag.

  2. +++ b/core/modules/ckeditor5/tests/src/Unit/HTMLRestrictionsTest.php
    @@ -538,6 +538,74 @@ public function providerConvenienceConstructors(): \Generator {
    +    // Attribute restrictions that match the global attribute restrictions
    +    // should be omitted from wildcard tags.
    +    yield '<p> <$text-container foo> <* foo> results in <$text-container> getting simplified' => [
    

    This (and the dozens of subsequent test cases) is adding test coverage for wildcard tags — the test coverage up to this point only dealt with concrete tags.

Wim Leers’s picture

The last submitted patch, 7: 3314478-7.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 8: 3314478-8.patch, failed testing. View results

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
1.75 KB
8.96 KB

That had some unintended consequences. Fixing…

Wim Leers’s picture

Wim Leers’s picture

smustgrave’s picture

I tried testing this with the ckeditor_bidi module

      - <$text-container dir="ltr rtl">

This is what I allow. With the patch I noticed that no element is getting the "dir" attribute now. Is that expected?

Wim Leers’s picture

This is what I allow. With the patch I noticed that no element is getting the "dir" attribute now. Is that expected?

Yes, it is! And for clarity: by "no element is getting […]", you mean filter_html's allowed_html setting.

Because the <dir> attribute gets special treatment from filter_html:

'#description' => $this->t('A list of HTML tags that can be used. By default only the <em>lang</em> and <em>dir</em> attributes are allowed for all HTML tags. Each HTML tag may have attributes which are treated as allowed attribute names for that HTML tag. Each attribute may allow all values, or only allow specific values. Attribute names or values may be written as a prefix and wildcard like <em>jump-*</em>. JavaScript event attributes, JavaScript URLs, and CSS are always stripped.'),

and

    // The 'style' and 'on*' ('onClick' etc.) attributes are always forbidden,
    // and are removed by Xss::filter().
    // The 'lang', and 'dir' attributes apply to all elements and are always
    // allowed. The list of allowed values for the 'dir' attribute is enforced
    // by self::filterAttributes(). Note that those two attributes are in the
    // short list of globally usable attributes in HTML5. They are always
    // allowed since the correct values of lang and dir may only be known to
    // the content author. Of the other global attributes, they are not usually
    // added by hand to content, and especially the class attribute can have
    // undesired visual effects by allowing content authors to apply any
    // available style, so specific values should be explicitly allowed.
    // @see http://www.w3.org/TR/html5/dom.html#global-attributes
    $restrictions['allowed']['*'] = [
      'style' => FALSE,
      'on*' => FALSE,
      'lang' => TRUE,
      'dir' => ['ltr' => TRUE, 'rtl' => TRUE],
    ];
smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

In that case testing patch #13 can confirm ckeditor_bidi still works.

alexpott’s picture

Status: Reviewed & tested by the community » Needs review
+++ b/core/modules/ckeditor5/src/HTMLRestrictions.php
@@ -85,6 +85,52 @@ public function __construct(array $elements) {
+    // Note: `<*>` also allows specifying disallowed attributes, but no other
+    // tags are allowed to do this. Consequently, simplification is only needed
+    // if >=1 allowed attribute present on `<*>`.

Do we have test coverage of the impact of allowing something and disallowing something else.

Like what happen if I allow * foo but disallow * bar but also configure to allow p foo bar?

I looked for test coverage of this situation. But could not find it. Note that that does not mean it does not exist :).

Wim Leers’s picture

Assigned: Unassigned » Wim Leers
FileSize
2.75 KB
10.98 KB

Very interesting question! 🤓

filter_html does not allow configuring <* bar>. One can configure filter_html to allow <p style>, but <* style> will still be applied anyway.

It's only for security reasons that HTML restrictions are able to specify globally disallowed attributes. It does not make sense to override it, at all, ever, because filter_html will simply ignore it. That's why it never even crossed my mind to test this! 🤓

But you're of course right that if we're explicitly testing the behavior for the "collision with globally allowed attributes" case, that we should also do the same for the "collision with globally disallowed attributes" case.

ℹ️ In fact, this is very closely related to #3312807: Harden CKEditor 5 against self-XSS through source editing, where we will be improving the UX to explicitly fail validation when that occurs. Documented this explicitly at #3312807-4: Harden CKEditor 5 against self-XSS through source editing.

Conclusion: it's important to provide explicit DX when a user/developer tries to override globally disallowed attributes to become allowed. We need test coverage for this. I'm happy to split this off into a separate issue if you prefer, but I'm equally fine with doing it here. For now, getting it going here, because it is the other side of the same aspect. Attached patch adds unit test coverage with expected failures.

Tricky: Unfortunately, like many other aspects of the Filter module, there is no validation logic in \Drupal\filter\Plugin\Filter\FilterHtml at all. This means that it's totally possible there are sites out there that have <p style> configured as their allowed_html … and that configuration is treated as valid, but 100% certainly ignored. So we need to handle that gracefully. I'll do that in a next patch iteration.

Wim Leers’s picture

Wim Leers’s picture

The last submitted patch, 18: 3314478-18.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 20: 3314478-20.patch, failed testing. View results

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
946 bytes
17.13 KB

Conclusion: it's important to provide explicit DX when a user/developer tries to override globally disallowed attributes to become allowed. We need test coverage for this. I'm happy to split this off into a separate issue if you prefer, but I'm equally fine with doing it here. For now, getting it going here, because it is the other side of the same aspect. Attached patch adds unit test coverage with expected failures.

This was addressed by #18 (tests) and #20 (fix).

Next up:

Tricky: Unfortunately, like many other aspects of the Filter module, there is no validation logic in \Drupal\filter\Plugin\Filter\FilterHtml at all. This means that it's totally possible there are sites out there that have <p style> configured as their allowed_html … and that configuration is treated as valid, but 100% certainly ignored. So we need to handle that gracefully. I'll do that in a next patch iteration.

Here's the test coverage. And … the new test failure in #20 actually reveals that this would've been caught anyway! 🤓 So this is just adding explicit test coverage.

Wim Leers’s picture

This should fix all tests … except one.

Wim Leers’s picture

😶

Status: Needs review » Needs work

The last submitted patch, 25: 3314478-25.patch, failed testing. View results

Wim Leers’s picture

Hah, that last failure is actually correct. It is not something you would ever see in the UI, because as described at #3312807: Harden CKEditor 5 against self-XSS through source editing, n thanks to #3228691: Restrict allowed additional attributes to prevent self XSS the UI will warn you:

The additional expectation added in this interdiff is for the validation error thrown by FundamentalCompatibilityConstraintValidator. And it is accurate, because it validates an existing text editor config entity that DOES have the SourceEditing plugin configured to allow e.g. <p onhover>, which filter_html indeed does not allow, so it is indeed a fundamental compatibility problem. You'll just never be able to create such an invalid text editor config through the UI, only by manipulating config by hand and forcefully importing it.

Hence this interdiff is actually just confirming that the changes in #24 are correct! 🥳

Wim Leers’s picture

smustgrave’s picture

Queued #28 regardless. I think the core branch is still having javascript errors. Lets see what happens.

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Think it's another random failure.

catch’s picture

Status: Reviewed & tested by the community » Needs review
+++ b/core/modules/ckeditor5/src/HTMLRestrictions.php
@@ -246,6 +296,99 @@ private static function validateAllowedRestrictionsPhase4(array $elements): void
+    $conflict = static::findElementsOverridingGloballyDisallowedAttributes($elements);
+    if ($conflict) {
+      [$globally_disallowed_attribute_restrictions, $elements_overriding_globally_disallowed_attributes] = $conflict;
+      throw new \InvalidArgumentException(sprintf(
+        'The attribute restrictions in "%s" are allowing attributes that are disallowed by the special "*" global attribute restrictions (it disallows the attributes "%s"). This is not allowed.',
+        implode(' ', (new HTMLRestrictions($elements_overriding_globally_disallowed_attributes))->toCKEditor5ElementsArray()),

Code looks pretty good to me but I find the error message hard to read - here's an attempt at a reword, may or may not actually be better:

'The global ("*") attribute restrictions are disallowing the following attributes "%s" which are allowed by the attribute restrictions in "%s". This must be resolved so that the same attribute is not both allowed and disallowed'.

Could this be 'The attribute restrictions in "%s" are allowing attributes "%s" that are disallowed by the special "*" global attribute restrictions.

lauriii’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/ckeditor5/src/HTMLRestrictions.php
    @@ -84,7 +84,54 @@ public function __construct(array $elements) {
    +    // Automatically simplify based on the global attributes:
    +    // - `<p dir> <* dir>` must become `<p> <* dir>`
    +    // - `<p foo="b a"> <* foo="a b">` must become `<p> <* foo="a b">`
    +    // - `<p foo="a b c"> <* foo="a b">` must become `<p foo="c"> <* foo="a b">`
    

    This makes the goals of this issue crystal clear 💯

  2. +++ b/core/modules/ckeditor5/src/HTMLRestrictions.php
    @@ -84,7 +84,54 @@ public function __construct(array $elements) {
    +      $net_global_attribute_restrictions = (new static($original))
    +        ->doDiff(new static($global))
    
    @@ -246,6 +296,99 @@ private static function validateAllowedRestrictionsPhase4(array $elements): void
    +    $conflict = static::findElementsOverridingGloballyDisallowedAttributes($elements);
    ...
    +    $conflicting_restrictions = new static(array_fill_keys(
    
    @@ -350,6 +493,25 @@ private static function fromObjectWithHtmlRestrictions(object $object): HTMLRest
    +    $conflict = static::findElementsOverridingGloballyDisallowedAttributes($allowed);
    

    For consistency, should we reference these using HTMLRestrictions?

  3. +++ b/core/modules/ckeditor5/src/HTMLRestrictions.php
    @@ -246,6 +296,99 @@ private static function validateAllowedRestrictionsPhase4(array $elements): void
    +    $conflicting_restrictions = new static(array_fill_keys(
    +      array_keys($elements_with_attribute_level_restrictions),
    ...
    +      array_fill_keys(array_keys($globally_disallowed_attribute_restrictions), TRUE)
    +    ));
    

    Woah, this is smart 🤯

  4. +++ b/core/modules/ckeditor5/src/HTMLRestrictions.php
    @@ -350,6 +493,25 @@ private static function fromObjectWithHtmlRestrictions(object $object): HTMLRest
    +    // so clean up $allowed prior to constructing a HTMLRestrictions object.
    

    I feel like "clean up" could potentially could be replaced with something more elaborate. 

  5. +++ b/core/modules/ckeditor5/src/HTMLRestrictions.php
    @@ -350,6 +493,25 @@ private static function fromObjectWithHtmlRestrictions(object $object): HTMLRest
    +        if ($allowed[$element] === []) {
    +          $allowed[$element] = FALSE;
    +        }
    

    This first caught my attention since it is not common to disallow attributes for a tag. After reviewing this, it totally makes sense. This explicitly disallows any attributes if all of the attributes were removed as part of the clean up. 👍

  6. +++ b/core/modules/ckeditor5/src/HTMLRestrictions.php
    @@ -84,7 +84,54 @@ public function __construct(array $elements) {
    +    // Automatically simplify based on the global attributes:
    

    The word "automatically" feels redundant in here 😅

  7. +++ b/core/modules/ckeditor5/src/HTMLRestrictions.php
    @@ -84,7 +84,54 @@ public function __construct(array $elements) {
    +    // if >=1 allowed attribute present on `<*>`.
    

    Should there be "is" between attribute and present?

pooja saraah’s picture

Addressed the comment #31 #32 point-7
need to be addressed the comment #32 point-2,4,5,6
Attached patch against Drupal 10.1.x

pooja saraah’s picture

Status: Needs work » Needs review
Wim Leers’s picture

Assigned: Unassigned » Wim Leers
Wim Leers’s picture

#31: Thank you, I was struggling with the wording 😅

#32:

  1. Discussed with @lauriii — we actually are mostly using self elsewhere, I just keep forgetting about it 🙈 — now consistently using self 👍
  1. 👍 Done!
  1. 👍 Done!
  2. Yes! 😅 Good catch :)

Status: Needs review » Needs work

The last submitted patch, 36: 3314478-36.patch, failed testing. View results

Wim Leers’s picture

Status: Needs work » Needs review

Unrelated random fail; re-testing.

Status: Needs review » Needs work

The last submitted patch, 36: 3314478-36.patch, failed testing. View results

Wim Leers’s picture

Status: Needs work » Needs review

lauriii’s picture

Status: Needs review » Reviewed & tested by the community

#36 addresses all of my feedback. I think this is ready to go 👍

catch’s picture

Version: 10.1.x-dev » 9.4.x-dev
Status: Reviewed & tested by the community » Fixed

Committed/pushed to 10.1.x, cherry-picked back through to 9.4.x thanks!

  • catch committed f63bba4 on 10.0.x
    Issue #3314478 by Wim Leers, pooja saraah, smustgrave, lauriii, alexpott...
  • catch committed e794353 on 10.1.x
    Issue #3314478 by Wim Leers, pooja saraah, smustgrave, lauriii, alexpott...
  • catch committed 7489e2b on 9.4.x
    Issue #3314478 by Wim Leers, pooja saraah, smustgrave, lauriii, alexpott...
  • catch committed 80e40d7 on 9.5.x
    Issue #3314478 by Wim Leers, pooja saraah, smustgrave, lauriii, alexpott...
catch’s picture

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.