.../rest/src/Plugin/rest/resource/EntityResource.php | 18 ++++++++++++------ .../EntityResource/EntityResourceTestBase.php | 5 ++++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php index da2cefb..5fa7aae 100644 --- a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php +++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php @@ -241,15 +241,21 @@ public function patch(EntityInterface $original_entity, EntityInterface $entity continue; } - // If the user has access to view the field, we need to check update - // access regardless of the field value to avoid information disclosure. - // (Otherwise the user may try PATCHing with value after value, until they - // send the current value for the field, and then they won't get a 403 - // response anymore, which indicates that the value they sent in the PATCH - // request body matches the current value.) + // If the user is allowed to edit the field, it is always safe to set the + // received value. We may be setting an unchanged value, but that is ok. if ($original_field->access('edit')) { $original_entity->set($field_name, $field->getValue()); } + // If the user is not allowed to edit the field, throw an exception (that + // will be transformed into a helpful 403 response). But only when: + // - the user IS NOT allowed to view the field, regardless of whether the + // value matches or not, to avoid information disclosure. + // (Otherwise the user may try PATCHing with value after value, until + // they send the current value for the field, and then they won't get a + // 403 response anymore, which indicates that the value they sent in the + // PATCH request body matches the current value.) + // - the user IS allowed to view the field, but the value does not match; + // because they are not allowed to edit the field. elseif (!$original_field->access('view') || !$original_field->equals($field)) { throw new AccessDeniedHttpException("Access denied on updating field '$field_name'."); } diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php index 2cdb62a..c32b2cf 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php @@ -942,7 +942,10 @@ public function testPatch() { $parseable_valid_request_body_2 = $this->serializer->encode($this->getNormalizedPatchEntity(), static::$format); $parseable_invalid_request_body = $this->serializer->encode($this->makeNormalizationInvalid($this->getNormalizedPatchEntity()), static::$format); $parseable_invalid_request_body_2 = $this->serializer->encode($this->getNormalizedPatchEntity() + ['field_rest_test' => [['value' => $this->randomString()]]], static::$format); - $parseable_invalid_request_body_3 = $this->serializer->encode($this->getNormalizedPatchEntity() + ['field_rest_test' => [['value' => 'All the faith he had had had had no effect on the outcome of his life.', 'format' => NULL]]], static::$format); + // The 'field_rest_test' field does not allow 'view' access, so does not end + // up in the normalization. Even when we explicitly add it the normalization + // that we send in the body of a PATCH request, it is considered invalid. + $parseable_invalid_request_body_3 = $this->serializer->encode($this->getNormalizedPatchEntity() + ['field_rest_test' => $this->entity->get('field_rest_test')->getValue()], static::$format); // The URL and Guzzle request options that will be used in this test. The // request options will be modified/expanded throughout this test: