.../Comment/CommentHalJsonAnonTest.php | 118 +++--------------- .../Comment/CommentHalJsonBasicAuthTest.php | 2 +- .../Comment/CommentHalJsonCookieTest.php | 2 +- .../Comment/CommentHalJsonTestBase.php | 133 +++++++++++++++++++++ .../EntityResource/HalEntityNormalizationTrait.php | 19 +++ .../EntityResource/Node/NodeHalJsonAnonTest.php | 22 +++- .../EntityResource/Comment/CommentJsonAnonTest.php | 21 ++++ .../Comment/CommentResourceTestBase.php | 35 +++++- .../EntityResource/EntityResourceTestBase.php | 45 +++++++ .../EntityTest/EntityTestResourceTestBase.php | 5 + .../EntityResource/Node/NodeResourceTestBase.php | 24 +++- .../EntityResource/Term/TermResourceTestBase.php | 7 ++ .../EntityResource/User/UserResourceTestBase.php | 7 ++ 13 files changed, 324 insertions(+), 116 deletions(-) diff --git a/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonAnonTest.php b/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonAnonTest.php index ab86f7a..56d94e2 100644 --- a/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonAnonTest.php +++ b/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonAnonTest.php @@ -2,117 +2,33 @@ namespace Drupal\Tests\hal\Functional\EntityResource\Comment; -use Drupal\entity_test\Entity\EntityTest; -use Drupal\Tests\hal\Functional\EntityResource\HalEntityNormalizationTrait; use Drupal\Tests\rest\Functional\AnonResourceTestTrait; -use Drupal\Tests\rest\Functional\EntityResource\Comment\CommentResourceTestBase; -use Drupal\user\Entity\User; /** * @group hal */ -class CommentHalJsonAnonTest extends CommentResourceTestBase { +class CommentHalJsonAnonTest extends CommentHalJsonTestBase { - use HalEntityNormalizationTrait; use AnonResourceTestTrait; /** * {@inheritdoc} + * + * Anononymous users cannot edit their own comments. + * + * @see \Drupal\comment\CommentAccessControlHandler::checkAccess + * + * Therefore we grant them the 'administer comments' permission for the + * purpose of this test. + * + * @see ::setUpAuthorization */ - public static $modules = ['hal']; - - /** - * {@inheritdoc} - */ - protected static $format = 'hal_json'; - - /** - * {@inheritdoc} - */ - protected static $mimeType = 'application/hal+json'; - - /** - * {@inheritdoc} - */ - protected static $expectedErrorMimeType = 'application/json'; - - /** - * {@inheritdoc} - */ - protected function getExpectedNormalizedEntity() { - $default_normalization = parent::getExpectedNormalizedEntity(); - - $normalization = $this->applyHalFieldNormalization($default_normalization); - - $author = User::load(0); - $commented_entity = EntityTest::load(1); - return $normalization + [ - '_links' => [ - 'self' => [ - 'href' => $this->baseUrl . '/comment/1?_format=hal_json', - ], - 'type' => [ - 'href' => $this->baseUrl . '/rest/type/comment/comment', - ], - $this->baseUrl . '/rest/relation/comment/comment/entity_id' => [ - [ - 'href' => $this->baseUrl . '/entity_test/1?_format=hal_json', - ], - ], - $this->baseUrl . '/rest/relation/comment/comment/uid' => [ - [ - 'href' => $this->baseUrl . '/user/0?_format=hal_json', - 'lang' => 'en', - ], - ], - ], - '_embedded' => [ - $this->baseUrl . '/rest/relation/comment/comment/entity_id' => [ - [ - '_links' => [ - 'self' => [ - 'href' => $this->baseUrl . '/entity_test/1?_format=hal_json', - ], - 'type' => [ - 'href' => $this->baseUrl . '/rest/type/entity_test/bar', - ], - ], - 'uuid' => [ - ['value' => $commented_entity->uuid()] - ], - ], - ], - $this->baseUrl . '/rest/relation/comment/comment/uid' => [ - [ - '_links' => [ - 'self' => [ - 'href' => $this->baseUrl . '/user/0?_format=hal_json', - ], - 'type' => [ - 'href' => $this->baseUrl . '/rest/type/user/user', - ], - ], - 'uuid' => [ - ['value' => $author->uuid()] - ], - 'lang' => 'en', - ], - ], - ], - ]; - } - - /** - * {@inheritdoc} - */ - protected function getNormalizedPostEntity() { - return parent::getNormalizedPostEntity() + [ - '_links' => [ - 'type' => [ - 'href' => $this->baseUrl . '/rest/type/comment/comment', - ], - ], - ]; - } + protected static $patchProtectedFields = [ + 'changed', + 'thread', + 'entity_type', + 'field_name', + 'entity_id', + ]; } diff --git a/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonBasicAuthTest.php b/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonBasicAuthTest.php index 6b42f3b..8b6e4a3 100644 --- a/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonBasicAuthTest.php +++ b/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonBasicAuthTest.php @@ -8,7 +8,7 @@ /** * @group hal */ -class CommentHalJsonBasicAuthTest extends CommentHalJsonAnonTest { +class CommentHalJsonBasicAuthTest extends CommentHalJsonTestBase { use BasicAuthResourceTestTrait; diff --git a/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonCookieTest.php b/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonCookieTest.php index 760b79a..292dc94 100644 --- a/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonCookieTest.php +++ b/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonCookieTest.php @@ -7,7 +7,7 @@ /** * @group hal */ -class CommentHalJsonCookieTest extends CommentHalJsonAnonTest { +class CommentHalJsonCookieTest extends CommentHalJsonTestBase { use CookieResourceTestTrait; diff --git a/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonTestBase.php b/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonTestBase.php new file mode 100644 index 0000000..9ad4ead --- /dev/null +++ b/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonTestBase.php @@ -0,0 +1,133 @@ +applyHalFieldNormalization($default_normalization); + + $author = User::load($this->entity->getOwnerId()); + $commented_entity = EntityTest::load(1); + return $normalization + [ + '_links' => [ + 'self' => [ + 'href' => $this->baseUrl . '/comment/1?_format=hal_json', + ], + 'type' => [ + 'href' => $this->baseUrl . '/rest/type/comment/comment', + ], + $this->baseUrl . '/rest/relation/comment/comment/entity_id' => [ + [ + 'href' => $this->baseUrl . '/entity_test/1?_format=hal_json', + ], + ], + $this->baseUrl . '/rest/relation/comment/comment/uid' => [ + [ + 'href' => $this->baseUrl . '/user/' . $author->id() . '?_format=hal_json', + 'lang' => 'en', + ], + ], + ], + '_embedded' => [ + $this->baseUrl . '/rest/relation/comment/comment/entity_id' => [ + [ + '_links' => [ + 'self' => [ + 'href' => $this->baseUrl . '/entity_test/1?_format=hal_json', + ], + 'type' => [ + 'href' => $this->baseUrl . '/rest/type/entity_test/bar', + ], + ], + 'uuid' => [ + ['value' => $commented_entity->uuid()] + ], + ], + ], + $this->baseUrl . '/rest/relation/comment/comment/uid' => [ + [ + '_links' => [ + 'self' => [ + 'href' => $this->baseUrl . '/user/' . $author->id() . '?_format=hal_json', + ], + 'type' => [ + 'href' => $this->baseUrl . '/rest/type/user/user', + ], + ], + 'uuid' => [ + ['value' => $author->uuid()] + ], + 'lang' => 'en', + ], + ], + ], + ]; + } + + /** + * {@inheritdoc} + */ + protected function getNormalizedPostEntity() { + return parent::getNormalizedPostEntity() + [ + '_links' => [ + 'type' => [ + 'href' => $this->baseUrl . '/rest/type/comment/comment', + ], + ], + ]; + } + +} diff --git a/core/modules/hal/tests/src/Functional/EntityResource/HalEntityNormalizationTrait.php b/core/modules/hal/tests/src/Functional/EntityResource/HalEntityNormalizationTrait.php index ade5239..e3b8d9c 100644 --- a/core/modules/hal/tests/src/Functional/EntityResource/HalEntityNormalizationTrait.php +++ b/core/modules/hal/tests/src/Functional/EntityResource/HalEntityNormalizationTrait.php @@ -5,6 +5,7 @@ use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Field\EntityReferenceFieldItemListInterface; use Drupal\Core\Field\FieldItemListInterface; +use Drupal\Core\Url; trait HalEntityNormalizationTrait { @@ -51,4 +52,22 @@ protected function applyHalFieldNormalization(array $normalization) { return $normalization; } + /** + * {@inheritdoc} + */ + protected function removeFieldsFromNormalization(array $normalization, $field_names) { + $normalization = parent::removeFieldsFromNormalization($normalization, $field_names); + foreach ($field_names as $field_name) { + $relation_url = Url::fromUri('base:rest/relation/' . static::$entityType . '/' . $this->entity->bundle() . '/' . $field_name) + ->setAbsolute(TRUE) + ->toString(); + $normalization['_links'] = array_diff_key($normalization['_links'], [$relation_url => TRUE]); + if (isset($normalization['_embedded'])) { + $normalization['_embedded'] = array_diff_key($normalization['_embedded'], [$relation_url => TRUE]); + } + } + + return array_diff_key($normalization, array_flip($field_names)); + } + } diff --git a/core/modules/hal/tests/src/Functional/EntityResource/Node/NodeHalJsonAnonTest.php b/core/modules/hal/tests/src/Functional/EntityResource/Node/NodeHalJsonAnonTest.php index 29c048a..e755c91 100644 --- a/core/modules/hal/tests/src/Functional/EntityResource/Node/NodeHalJsonAnonTest.php +++ b/core/modules/hal/tests/src/Functional/EntityResource/Node/NodeHalJsonAnonTest.php @@ -38,12 +38,24 @@ class NodeHalJsonAnonTest extends NodeResourceTestBase { /** * {@inheritdoc} */ + protected static $patchProtectedFields = [ + 'created', + 'changed', + 'promote', + 'sticky', + 'revision_timestamp', + 'revision_uid', + ]; + + /** + * {@inheritdoc} + */ protected function getExpectedNormalizedEntity() { $default_normalization = parent::getExpectedNormalizedEntity(); $normalization = $this->applyHalFieldNormalization($default_normalization); - $author = User::load(0); + $author = User::load($this->entity->getOwnerId()); return $normalization + [ '_links' => [ 'self' => [ @@ -54,13 +66,13 @@ protected function getExpectedNormalizedEntity() { ], $this->baseUrl . '/rest/relation/node/camelids/uid' => [ [ - 'href' => $this->baseUrl . '/user/0?_format=hal_json', + 'href' => $this->baseUrl . '/user/' . $author->id() . '?_format=hal_json', 'lang' => 'en', ], ], $this->baseUrl . '/rest/relation/node/camelids/revision_uid' => [ [ - 'href' => $this->baseUrl . '/user/0?_format=hal_json', + 'href' => $this->baseUrl . '/user/' . $author->id() . '?_format=hal_json', ], ], ], @@ -69,7 +81,7 @@ protected function getExpectedNormalizedEntity() { [ '_links' => [ 'self' => [ - 'href' => $this->baseUrl . '/user/0?_format=hal_json', + 'href' => $this->baseUrl . '/user/' . $author->id() . '?_format=hal_json', ], 'type' => [ 'href' => $this->baseUrl . '/rest/type/user/user', @@ -85,7 +97,7 @@ protected function getExpectedNormalizedEntity() { [ '_links' => [ 'self' => [ - 'href' => $this->baseUrl . '/user/0?_format=hal_json', + 'href' => $this->baseUrl . '/user/' . $author->id() . '?_format=hal_json', ], 'type' => [ 'href' => $this->baseUrl . '/rest/type/user/user', diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentJsonAnonTest.php b/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentJsonAnonTest.php index 47478aa..10b2e31 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentJsonAnonTest.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentJsonAnonTest.php @@ -26,4 +26,25 @@ class CommentJsonAnonTest extends CommentResourceTestBase { */ protected static $expectedErrorMimeType = 'application/json'; + /** + * {@inheritdoc} + * + * Anononymous users cannot edit their own comments. + * + * @see \Drupal\comment\CommentAccessControlHandler::checkAccess + * + * Therefore we grant them the 'administer comments' permission for the + * purpose of this test. + * + * @see ::setUpAuthorization + */ + protected static $patchProtectedFields = [ + 'pid', + 'entity_id', + 'changed', + 'thread', + 'entity_type', + 'field_name', + ]; + } diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentResourceTestBase.php index ef37d1b..2ff0ce7 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentResourceTestBase.php @@ -25,6 +25,23 @@ protected static $entityType = 'comment'; /** + * {@inheritdoc} + */ + protected static $patchProtectedFields = [ + 'pid', + 'entity_id', + 'uid', + 'name', + 'homepage', + 'created', + 'changed', + 'status', + 'thread', + 'entity_type', + 'field_name', + ]; + + /** * @var \Drupal\comment\CommentInterface */ protected $entity; @@ -41,6 +58,17 @@ protected function setUpAuthorization($method) { $this->grantPermissionsToTestedRole(['post comments']); break; case 'PATCH': + // Anononymous users are not ever allowed to edit their own comments. To + // be able to test PATCHing comments as the anonymous user, the more + // permissive 'administer comments' permission must be granted. + // @see \Drupal\comment\CommentAccessControlHandler::checkAccess + if (static::$auth) { + $this->grantPermissionsToTestedRole(['edit own comments']); + } + else { + $this->grantPermissionsToTestedRole(['administer comments']); + } + break; case 'DELETE': $this->grantPermissionsToTestedRole(['administer comments']); break; @@ -76,6 +104,7 @@ protected function createEntity() { 'field_name' => 'comment', ]); $comment->setSubject('Llama') + ->setOwnerId(static::$auth ? $this->account->id() : 0) ->setPublished(TRUE) ->setCreatedTime(123456789) ->setChangedTime(123456789); @@ -88,7 +117,7 @@ protected function createEntity() { * {@inheritdoc} */ protected function getExpectedNormalizedEntity() { - $author = User::load(0); + $author = User::load($this->entity->getOwnerId()); return [ 'cid' => [ ['value' => 1], @@ -135,10 +164,10 @@ protected function getExpectedNormalizedEntity() { ], 'uid' => [ [ - 'target_id' => '0', + 'target_id' => $author->id(), 'target_type' => 'user', 'target_uuid' => $author->uuid(), - 'url' => base_path() . 'user/0', + 'url' => base_path() . 'user/' . $author->id(), ], ], 'pid' => [], diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php index ca0dc6b..1906020 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php @@ -34,6 +34,13 @@ protected static $entityType = NULL; /** + * The fields that are protected against modification during PATCH requests. + * + * @var string[] + */ + protected static $patchProtectedFields; + + /** * Optionally specify which field is the 'label' field. Some entities specify * a 'label_callback', but not a 'label' entity key. For example: User. * @@ -684,6 +691,26 @@ public function testPatch() { $this->assertResourceErrorResponse(403, "Access denied on updating field 'field_rest_test'.", $response); + // DX: 403 when sending PATCH request with read-only fields. + // First send all fields (the "maximum normalization"). Assert the expected + // error message for the first PATCH-protected field. Remove that field from + // the normalization, send another request, assert the next PATCH-protected + // field error message. And so on. + $max_normalization = $this->getNormalizedPatchEntity() + $this->serializer->normalize($this->entity, static::$format); + for ($i = 0; $i < count(static::$patchProtectedFields); $i++) { + $max_normalization = $this->removeFieldsFromNormalization($max_normalization, array_slice(static::$patchProtectedFields, 0, $i)); + $request_options[RequestOptions::BODY] = $this->serializer->serialize($max_normalization, static::$format); + $response = $this->request('PATCH', $url, $request_options); + $this->assertResourceErrorResponse(403, "Access denied on updating field '" . static::$patchProtectedFields[$i] . "'.", $response); + } + + // 200 for well-formed request that sends the maximum number of fields. + $max_normalization = $this->removeFieldsFromNormalization($max_normalization, static::$patchProtectedFields); + $request_options[RequestOptions::BODY] = $this->serializer->serialize($max_normalization, static::$format); + $response = $this->request('PATCH', $url, $request_options); + $this->assertResourceResponse(200, FALSE, $response); + + $request_options[RequestOptions::BODY] = $parseable_valid_request_body; @@ -691,6 +718,7 @@ public function testPatch() { // edge cases to also be tested. $this->assertAuthenticationEdgeCases('PATCH', $url, $request_options); + // 200 for well-formed request. $response = $this->request('PATCH', $url, $request_options); $this->assertResourceResponse(200, FALSE, $response); @@ -846,6 +874,23 @@ protected function makeNormalizationInvalid(array $normalization) { } /** + * Removes fields from a normalization. + * + * @param array $normalization + * An entity normalization. + * @param string[] $field_names + * The field names to remove from the entity normalization. + * + * @return array + * The updated entity normalization. + * + * @see ::testPatch + */ + protected function removeFieldsFromNormalization(array $normalization, $field_names) { + return array_diff_key($normalization, array_flip($field_names)); + } + + /** * Asserts a 406 response… or in some cases a 403 response, because weirdness. * * Asserting a 406 response should be easy, but it's not, due to bugs. diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestResourceTestBase.php index ab375f9..437d9eb 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestResourceTestBase.php @@ -19,6 +19,11 @@ protected static $entityType = 'entity_test'; /** + * {@inheritdoc} + */ + protected static $patchProtectedFields = []; + + /** * @var \Drupal\entity_test\Entity\EntityTest */ protected $entity; diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php index e77467a..bb8b495 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php @@ -20,6 +20,19 @@ protected static $entityType = 'node'; /** + * {@inheritdoc} + */ + protected static $patchProtectedFields = [ + 'uid', + 'created', + 'changed', + 'promote', + 'sticky', + 'revision_timestamp', + 'revision_uid', + ]; + + /** * @var \Drupal\node\NodeInterface */ protected $entity; @@ -59,6 +72,7 @@ protected function createEntity() { // Create a "Llama" node. $node = Node::create(['type' => 'camelids']); $node->setTitle('Llama') + ->setOwnerId(static::$auth ? $this->account->id() : 0) ->setPublished(TRUE) ->setCreatedTime(123456789) ->setChangedTime(123456789) @@ -72,7 +86,7 @@ protected function createEntity() { * {@inheritdoc} */ protected function getExpectedNormalizedEntity() { - $author = User::load(0); + $author = User::load($this->entity->getOwnerId()); return [ 'nid' => [ ['value' => 1], @@ -142,18 +156,18 @@ protected function getExpectedNormalizedEntity() { ], 'uid' => [ [ - 'target_id' => '0', + 'target_id' => $author->id(), 'target_type' => 'user', 'target_uuid' => $author->uuid(), - 'url' => base_path() . 'user/0', + 'url' => base_path() . 'user/' . $author->id(), ], ], 'revision_uid' => [ [ - 'target_id' => '0', + 'target_id' => $author->id(), 'target_type' => 'user', 'target_uuid' => $author->uuid(), - 'url' => base_path() . 'user/0', + 'url' => base_path() . 'user/' . $author->id(), ], ], 'revision_log' => [ diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php index dd56380..d8bc693 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php @@ -19,6 +19,13 @@ protected static $entityType = 'taxonomy_term'; /** + * {@inheritdoc} + */ + protected static $patchProtectedFields = [ + 'changed', + ]; + + /** * @var \Drupal\taxonomy\TermInterface */ protected $entity; diff --git a/core/modules/rest/tests/src/Functional/EntityResource/User/UserResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/User/UserResourceTestBase.php index ef04868..5630fd5 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/User/UserResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/User/UserResourceTestBase.php @@ -20,6 +20,13 @@ protected static $entityType = 'user'; /** + * {@inheritdoc} + */ + protected static $patchProtectedFields = [ + 'changed', + ]; + + /** * @var \Drupal\user\UserInterface */ protected $entity;