Problem/Motivation

#2846554: Make the PathItem field type actually computed and auto-load stored aliases made it possible to read PathItem fields in GET responses for REST API requests for entities. Hurray!

What we did not explicitly test, is that it also works for PATCH, and that it then respects create url alias

This was discovered after #2824851 was committed, see #2824851-117: EntityResource::patch() makes an incorrect assumption about entity keys, hence results in incorrect behavior: a security issue against Drupal core was created, because since that patch was committed, the following security vulnerability was introduced:

Steps to reproduce

  1. Install a new 8.4.x site (standard profile).
  2. Enable path, node, hal, basic_auth.
  3. Create a user with create/edit node permissions, but no path alias permissions.
  4. Create a node the user can edit.
  5. As that user, submit a GET request to the node to acquire its full JSON.
  6. As that user, submit a PATCH request with the same JSON, but changing the path alias to any valid value.

Expected result: 403
Actual result: 200, path alias gets changed successfully.

(Due to little validation at this level, it is also possible to overwrite any other alias knowing its pid value, as well as creating path aliases that cover system routes.)

Proposed resolution

Add explicit test coverage, which would have surfaced the regression that #2824851 introduced during the patch review process. If we didn't have the vigilant @cburschka, then Drupal 8.4.0 would have shipped with this security vulnerability

Remaining tasks

User interface changes

None.

API changes

None.

Data model changes

None.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Wim Leers created an issue. See original summary.

Wim Leers’s picture

Version: 8.5.x-dev » 8.4.x-dev
Status: Active » Needs review
FileSize
2.8 KB

Let's start by adding a test to prove that it is secure in HEAD: PATCHing a node's path (URL alias) should result in a 403.

Patch lifted from #2824851-123: EntityResource::patch() makes an incorrect assumption about entity keys, hence results in incorrect behavior.

Wim Leers’s picture

#2 added sensible security coverage. But to really know for certain that things are working as expected, we should expand the test coverage to verify that granting the create url aliases permission then results in a 200 response: granting the necessary permission does allow the path field to be changed.

Note that there are three entity types that have a path field: node + term + media. The latter doesn't have any REST test coverage yet, so we leave that one out of consideration. Back when we worked on fixing path fields handling in #2846554: Make the PathItem field type actually computed and auto-load stored aliases, we chose to use NodeResourceTestBase to NOT GRANT the create url aliases permission by default, and to use TermResourceTestBase to GRANT the create url aliases permission by default, i.e. they'd be each other opposites.
For that reason, we should also add similar test coverage to TermResourceTestBase as #2 already did for NodeResourceTestBase. But rather than expecting a 403 response, we should expect a 200 response.

Now we have the test coverage we need!

Except this is going to fail… changing path aliases doesn't work at all in HEAD :( But at least the permission is respected as it should!

Patch lifted from #2824851-124: EntityResource::patch() makes an incorrect assumption about entity keys, hence results in incorrect behavior.

Wim Leers’s picture

This is the failure:

1) Drupal\Tests\hal\Functional\EntityResource\Term\TermHalJsonAnonTest::testPatchPath
Failed asserting that Array &0 (
    0 => Array &1 (
        'alias' => '/llama'
        'pid' => 1
        'langcode' => 'en'
        'lang' => 'en'
    )
) is identical to Array &0 (
    0 => Array &1 (
        'alias' => '/llamas-rule-the-world'
        'pid' => 1
        'langcode' => 'en'
        'lang' => 'en'
    )
).

(The latter is what is expected. The former shows what is actually stored — in other words: nothing was changed.)

This is failing because PathItem::setValue() is not calling PathItem::ensureLoaded(). But doing that triggers an infinite loop… 😵 Fortunately that's easy to work around! 🙏

Patch lifted from #2824851-126: EntityResource::patch() makes an incorrect assumption about entity keys, hence results in incorrect behavior.

Wim Leers’s picture

For *HalJson*Test tests, I had to use removeFieldsFromNormalization(), not unset(), so that fields moved to _links also are removed for the HAL+JSON normalization.

Patch lifted from #2824851-134: EntityResource::patch() makes an incorrect assumption about entity keys, hence results in incorrect behavior.

The last submitted patch, 3: 2909183-3.patch, failed testing. View results

The last submitted patch, 4: 2909183-4.patch, failed testing. View results

Wim Leers’s picture

Assigned: Wim Leers » Unassigned

Alright, this patch is green, and ready for final review :)

Wim Leers’s picture

@dawehner made this remark over at #2824851-133: EntityResource::patch() makes an incorrect assumption about entity keys, hence results in incorrect behavior:

+++ b/core/modules/path/src/Plugin/Field/FieldType/PathItem.php
@@ -162,7 +173,9 @@ public static function mainPropertyName() {
+    static $is_loading = FALSE;

❓ Couldn't you still have a class based variable? Otherwise for whatever reason two instances could maybe interfere ...

AFAICT you can't, because of \Drupal\path\Plugin\Field\FieldType\PathItem::__get().

tedbow’s picture

Status: Needs review » Reviewed & tested by the community

Looks good, great work!

catch’s picture

Status: Reviewed & tested by the community » Needs work

The explanation in #9 is useful, but it should be an inline comment in the patch.

tedbow’s picture

Status: Needs work » Needs review
FileSize
1.47 KB
7.86 KB

I was going to add an inline comment per #11 but I didn't really understand the explanation in #9 when I looked back at it. Maybe I forgot something because I did RTBC this issue

Anyways here is a patch that does change to class based variable. I can't see what it will break.

Wim Leers’s picture

Status: Needs review » Reviewed & tested by the community

Huh. I'd swear the explanation in #9 was accurate.

But I just stepped through it with a debugger and indeed was not a problem. I could not get the problem I described in #9 to get triggered. So I must've been wrong. I probably mixed two things up.

I of course like this better!

catch’s picture

Status: Reviewed & tested by the community » Fixed

Glad I asked for the comment :)

Committed/pushed to 8.5.x and cherry-picked to 8.4.x. Thanks!

  • catch committed 831450a on 8.5.x
    Issue #2909183 by Wim Leers, tedbow: Add path alias (PathItem) field...

  • catch committed 32ce326 on 8.4.x
    Issue #2909183 by Wim Leers, tedbow: Add path alias (PathItem) field...
Wim Leers’s picture

Wim Leers’s picture

Oh, can you also give issue credit to cburschka? Thanks!

amateescu’s picture

Note that I'm basically reverting the changes that were made here to PathItem in #2916300-9: Use ComputedFieldItemListTrait for the path field type and it seems the new test coverage still passes.

Wim Leers’s picture

Yep, makes sense — you're making the code introduced in #2846554: Make the PathItem field type actually computed and auto-load stored aliases obsolete and are replacing it with something more thorough. Hence the additional work-arounds added by this issue are no longer necessary. I'm very glad we introduced extra test coverage here that A) help ensure we don't need to fix this again, B) help validate your patch!

Status: Fixed » Closed (fixed)

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