Commit 0edd07b2 authored by alexpott's avatar alexpott

Issue #2853211 by vaplas, Wim Leers, arshadcn: EntityResource::post()...

Issue #2853211 by vaplas, Wim Leers, arshadcn: EntityResource::post() incorrectly assumes that every entity type has a canonical URL
parent 48261599
<?php
namespace Drupal\Tests\hal\Functional\EntityResource\EntityTestLabel;
use Drupal\Tests\hal\Functional\EntityResource\HalEntityNormalizationTrait;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\EntityTestLabel\EntityTestLabelResourceTestBase;
use Drupal\user\Entity\User;
/**
* @group hal
*/
class EntityTestLabelHalJsonAnonTest extends EntityTestLabelResourceTestBase {
use HalEntityNormalizationTrait;
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['hal'];
/**
* {@inheritdoc}
*/
protected static $format = 'hal_json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/hal+json';
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
$default_normalization = parent::getExpectedNormalizedEntity();
$normalization = $this->applyHalFieldNormalization($default_normalization);
$author = User::load(0);
return $normalization + [
'_links' => [
'self' => [
'href' => '',
],
'type' => [
'href' => $this->baseUrl . '/rest/type/entity_test_label/entity_test_label',
],
$this->baseUrl . '/rest/relation/entity_test_label/entity_test_label/user_id' => [
[
'href' => $this->baseUrl . '/user/0?_format=hal_json',
'lang' => 'en',
],
],
],
'_embedded' => [
$this->baseUrl . '/rest/relation/entity_test_label/entity_test_label/user_id' => [
[
'_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/entity_test_label/entity_test_label',
],
],
];
}
/**
* {@inheritdoc}
*/
protected function getExpectedCacheContexts() {
return [
'url.site',
'user.permissions',
];
}
}
<?php
namespace Drupal\Tests\hal\Functional\EntityResource\EntityTestLabel;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group hal
*/
class EntityTestLabelHalJsonBasicAuthTest extends EntityTestLabelHalJsonAnonTest {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}
<?php
namespace Drupal\Tests\hal\Functional\EntityResource\EntityTestLabel;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group hal
*/
class EntityTestLabelHalJsonCookieTest extends EntityTestLabelHalJsonAnonTest {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}
......@@ -189,8 +189,12 @@ public function post(EntityInterface $entity = NULL) {
// 201 Created responses return the newly created entity in the response
// body. These responses are not cacheable, so we add no cacheability
// metadata here.
$url = $entity->urlInfo('canonical', ['absolute' => TRUE])->toString(TRUE);
return new ModifiedResourceResponse($entity, 201, ['Location' => $url->getGeneratedUrl()]);
$headers = [];
if (in_array('canonical', $entity->uriRelationships(), TRUE)) {
$url = $entity->urlInfo('canonical', ['absolute' => TRUE])->toString(TRUE);
$headers['Location'] = $url->getGeneratedUrl();
}
return new ModifiedResourceResponse($entity, 201, $headers);
}
catch (EntityStorageException $e) {
throw new HttpException(500, 'Internal Server Error', $e);
......
......@@ -610,15 +610,11 @@ public function testPost() {
$request_options = [];
// DX: 404 when resource not provisioned, but HTML if canonical route.
// DX: 404 when resource not provisioned. HTML response because missing
// ?_format query string.
$response = $this->request('POST', $url, $request_options);
if ($has_canonical_url) {
$this->assertSame(404, $response->getStatusCode());
$this->assertSame(['text/html; charset=UTF-8'], $response->getHeader('Content-Type'));
}
else {
$this->assertResourceErrorResponse(404, 'No route found for "GET ' . str_replace($this->baseUrl, '', $this->getUrl()->setAbsolute()->toString()) . '"', $response);
}
$this->assertSame(404, $response->getStatusCode());
$this->assertSame(['text/html; charset=UTF-8'], $response->getHeader('Content-Type'));
$url->setOption('query', ['_format' => static::$format]);
......@@ -634,16 +630,12 @@ public function testPost() {
$url->setOption('query', []);
// DX: 415 when no Content-Type request header, plain text if canonical URL.
// DX: 415 when no Content-Type request header. HTML response because
// missing ?_format query string.
$response = $this->request('POST', $url, $request_options);
if ($has_canonical_url) {
$this->assertSame(415, $response->getStatusCode());
$this->assertSame(['text/plain; charset=UTF-8'], $response->getHeader('Content-Type'));
$this->assertContains(htmlspecialchars('No "Content-Type" request header specified'), (string) $response->getBody());
}
else {
$this->assertResourceErrorResponse(415, 'No "Content-Type" request header specified', $response);
}
$this->assertSame(415, $response->getStatusCode());
$this->assertSame(['text/plain; charset=UTF-8'], $response->getHeader('Content-Type'));
$this->assertContains(htmlspecialchars('No "Content-Type" request header specified'), (string) $response->getBody());
$url->setOption('query', ['_format' => static::$format]);
......@@ -741,8 +733,13 @@ public function testPost() {
// 201 for well-formed request.
$response = $this->request('POST', $url, $request_options);
$this->assertResourceResponse(201, FALSE, $response);
$location = $this->entityStorage->load(static::$firstCreatedEntityId)->toUrl('canonical')->setAbsolute(TRUE)->toString();
$this->assertSame([$location], $response->getHeader('Location'));
if ($has_canonical_url) {
$location = $this->entityStorage->load(static::$firstCreatedEntityId)->toUrl('canonical')->setAbsolute(TRUE)->toString();
$this->assertSame([$location], $response->getHeader('Location'));
}
else {
$this->assertSame([], $response->getHeader('Location'));
}
$this->assertFalse($response->hasHeader('X-Drupal-Cache'));
......@@ -762,8 +759,13 @@ public function testPost() {
// 201 for well-formed request.
$response = $this->request('POST', $url, $request_options);
$this->assertResourceResponse(201, FALSE, $response);
$location = $this->entityStorage->load(static::$secondCreatedEntityId)->toUrl('canonical')->setAbsolute(TRUE)->toString();
$this->assertSame([$location], $response->getHeader('Location'));
if ($has_canonical_url) {
$location = $this->entityStorage->load(static::$secondCreatedEntityId)->toUrl('canonical')->setAbsolute(TRUE)->toString();
$this->assertSame([$location], $response->getHeader('Location'));
}
else {
$this->assertSame([], $response->getHeader('Location'));
}
$this->assertFalse($response->hasHeader('X-Drupal-Cache'));
}
......@@ -796,7 +798,8 @@ public function testPatch() {
$request_options = [];
// DX: 404 when resource not provisioned, but 405 if canonical route.
// DX: 404 when resource not provisioned, 405 if canonical route. Plain text
// or HTML response because missing ?_format query string.
$response = $this->request('PATCH', $url, $request_options);
if ($has_canonical_url) {
$this->assertSame(405, $response->getStatusCode());
......@@ -804,17 +807,22 @@ public function testPatch() {
$this->assertSame(['text/plain; charset=UTF-8'], $response->getHeader('Content-Type'));
}
else {
$this->assertResourceErrorResponse(404, 'No route found for "PATCH ' . str_replace($this->baseUrl, '', $this->getUrl()->setAbsolute()->toString()) . '"', $response);
$this->assertSame(404, $response->getStatusCode());
$this->assertSame(['text/html; charset=UTF-8'], $response->getHeader('Content-Type'));
}
$url->setOption('query', ['_format' => static::$format]);
// DX: 405 when resource not provisioned.
// DX: 404 when resource not provisioned, 405 if canonical route.
$response = $this->request('PATCH', $url, $request_options);
$this->assertSame(['GET, POST, HEAD'], $response->getHeader('Allow'));
$this->assertResourceErrorResponse(405, 'No route found for "PATCH ' . str_replace($this->baseUrl, '', $this->getUrl()->setAbsolute()->toString()) . '": Method Not Allowed (Allow: GET, POST, HEAD)', $response);
if ($has_canonical_url) {
$this->assertResourceErrorResponse(405, 'No route found for "PATCH ' . str_replace($this->baseUrl, '', $this->getUrl()->setAbsolute()->toString()) . '": Method Not Allowed (Allow: GET, POST, HEAD)', $response);
}
else {
$this->assertResourceErrorResponse(404, 'No route found for "PATCH ' . str_replace($this->baseUrl, '', $this->getUrl()->setAbsolute()->toString()) . '"', $response);
}
$this->provisionEntityResource();
......@@ -822,16 +830,11 @@ public function testPatch() {
$url->setOption('query', []);
// DX: 415 when no Content-Type request header, but HTML if canonical route.
// DX: 415 when no Content-Type request header.
$response = $this->request('PATCH', $url, $request_options);
if ($has_canonical_url) {
$this->assertSame(415, $response->getStatusCode());
$this->assertSame(['text/plain; charset=UTF-8'], $response->getHeader('Content-Type'));
$this->assertTrue(FALSE !== strpos((string) $response->getBody(), htmlspecialchars('No "Content-Type" request header specified')));
}
else {
$this->assertResourceErrorResponse(415, 'No "Content-Type" request header specified', $response);
}
$this->assertSame(415, $response->getStatusCode());
$this->assertSame(['text/plain; charset=UTF-8'], $response->getHeader('Content-Type'));
$this->assertTrue(FALSE !== strpos((string) $response->getBody(), htmlspecialchars('No "Content-Type" request header specified')));
$url->setOption('query', ['_format' => static::$format]);
......@@ -990,7 +993,8 @@ public function testDelete() {
$request_options = [];
// DX: 404 when resource not provisioned, but 405 if canonical route.
// DX: 404 when resource not provisioned, but 405 if canonical route. Plain
// text or HTML response because missing ?_format query string.
$response = $this->request('DELETE', $url, $request_options);
if ($has_canonical_url) {
$this->assertSame(405, $response->getStatusCode());
......@@ -998,18 +1002,23 @@ public function testDelete() {
$this->assertSame(['text/plain; charset=UTF-8'], $response->getHeader('Content-Type'));
}
else {
$this->assertResourceErrorResponse(404, 'No route found for "DELETE ' . str_replace($this->baseUrl, '', $this->getUrl()->setAbsolute()->toString()) . '"', $response);
$this->assertSame(404, $response->getStatusCode());
$this->assertSame(['text/html; charset=UTF-8'], $response->getHeader('Content-Type'));
}
$url->setOption('query', ['_format' => static::$format]);
// DX: 405 when resource not provisioned.
// DX: 404 when resource not provisioned, 405 if canonical route.
$response = $this->request('DELETE', $url, $request_options);
$this->assertSame(['GET, POST, HEAD'], $response->getHeader('Allow'));
$this->assertResourceErrorResponse(405, 'No route found for "DELETE ' . str_replace($this->baseUrl, '', $this->getUrl()->setAbsolute()->toString()) . '": Method Not Allowed (Allow: GET, POST, HEAD)', $response);
if ($has_canonical_url) {
$this->assertSame(['GET, POST, HEAD'], $response->getHeader('Allow'));
$this->assertResourceErrorResponse(405, 'No route found for "DELETE ' . str_replace($this->baseUrl, '', $this->getUrl()->setAbsolute()->toString()) . '": Method Not Allowed (Allow: GET, POST, HEAD)', $response);
}
else {
$this->assertResourceErrorResponse(404, 'No route found for "DELETE ' . str_replace($this->baseUrl, '', $this->getUrl()->setAbsolute()->toString()) . '"', $response);
}
$this->provisionEntityResource();
......
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\EntityTestLabel;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class EntityTestLabelJsonAnonTest extends EntityTestLabelResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
}
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\EntityTestLabel;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class EntityTestLabelJsonBasicAuthTest extends EntityTestLabelResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\EntityTestLabel;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
*/
class EntityTestLabelJsonCookieTest extends EntityTestLabelResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\EntityTestLabel;
use Drupal\entity_test\Entity\EntityTestLabel;
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
use Drupal\user\Entity\User;
abstract class EntityTestLabelResourceTestBase extends EntityResourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['entity_test'];
/**
* {@inheritdoc}
*/
protected static $entityTypeId = 'entity_test_label';
/**
* {@inheritdoc}
*/
protected static $patchProtectedFieldNames = [];
/**
* @var \Drupal\entity_test\Entity\EntityTestLabel
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
switch ($method) {
case 'GET':
$this->grantPermissionsToTestedRole(['view test entity']);
break;
case 'POST':
$this->grantPermissionsToTestedRole([
'administer entity_test content',
'administer entity_test_with_bundle content',
'create entity_test entity_test_with_bundle entities',
]);
break;
case 'PATCH':
case 'DELETE':
$this->grantPermissionsToTestedRole(['administer entity_test content']);
break;
}
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
$entity_test_label = EntityTestLabel::create([
'name' => 'label_llama',
]);
$entity_test_label->setOwnerId(0);
$entity_test_label->save();
return $entity_test_label;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
$author = User::load(0);
$normalization = [
'uuid' => [
[
'value' => $this->entity->uuid(),
],
],
'id' => [
[
'value' => (int) $this->entity->id(),
],
],
'langcode' => [
[
'value' => 'en',
],
],
'type' => [
[
'value' => 'entity_test_label',
],
],
'name' => [
[
'value' => 'label_llama',
],
],
'created' => [
[
'value' => (int) $this->entity->get('created')->value,
],
],
'user_id' => [
[
'target_id' => (int) $author->id(),
'target_type' => 'user',
'target_uuid' => $author->uuid(),
'url' => $author->toUrl()->toString(),
],
],
];
return $normalization;
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
return [
'type' => 'entity_test_label',
'name' => [
[
'value' => 'label_llama',
],
],
];
}
/**
* {@inheritdoc}
*/
protected function getExpectedCacheContexts() {
return ['user.permissions'];
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
return parent::getExpectedUnauthorizedAccessMessage($method);
}
switch ($method) {
case 'GET':
return "The 'view test entity' permission is required.";
case 'POST':
return "The following permissions are required: 'administer entity_test content' OR 'administer entity_test_with_bundle content' OR 'create entity_test_label entity_test_with_bundle entities'.";
case 'PATCH':
case 'DELETE':
return "The 'administer entity_test content' permission is required.";
default:
return parent::getExpectedUnauthorizedAccessMessage($method);
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment