Issue #2824851 by Wim Leers, arshadcn, amateescu, effulgentsia, tedbow, timmillwood, cburschka, tstoeckler, Berdir, xjm, catch: EntityResource::patch() makes an incorrect assumption about entity keys, hence results in incorrect behavior
parent
0c20200d12
commit
0dc30938c7
|
@ -25,11 +25,11 @@ class CommentHalJsonAnonTest extends CommentHalJsonTestBase {
|
||||||
* @see ::setUpAuthorization
|
* @see ::setUpAuthorization
|
||||||
*/
|
*/
|
||||||
protected static $patchProtectedFieldNames = [
|
protected static $patchProtectedFieldNames = [
|
||||||
|
'entity_id',
|
||||||
'changed',
|
'changed',
|
||||||
'thread',
|
'thread',
|
||||||
'entity_type',
|
'entity_type',
|
||||||
'field_name',
|
'field_name',
|
||||||
'entity_id',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,25 +26,6 @@ abstract class CommentHalJsonTestBase extends CommentResourceTestBase {
|
||||||
*/
|
*/
|
||||||
protected static $mimeType = 'application/hal+json';
|
protected static $mimeType = 'application/hal+json';
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* The HAL+JSON format causes different PATCH-protected fields. For some
|
|
||||||
* reason, the 'pid' and 'homepage' fields are NOT PATCH-protected, even
|
|
||||||
* though they are for non-HAL+JSON serializations.
|
|
||||||
*
|
|
||||||
* @todo fix in https://www.drupal.org/node/2824271
|
|
||||||
*/
|
|
||||||
protected static $patchProtectedFieldNames = [
|
|
||||||
'status',
|
|
||||||
'created',
|
|
||||||
'changed',
|
|
||||||
'thread',
|
|
||||||
'entity_type',
|
|
||||||
'field_name',
|
|
||||||
'entity_id',
|
|
||||||
'uid',
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
|
|
|
@ -70,24 +70,6 @@ trait HalEntityNormalizationTrait {
|
||||||
return $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::$entityTypeId . '/' . $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));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -30,19 +30,6 @@ class NodeHalJsonAnonTest extends NodeResourceTestBase {
|
||||||
*/
|
*/
|
||||||
protected static $mimeType = 'application/hal+json';
|
protected static $mimeType = 'application/hal+json';
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
protected static $patchProtectedFieldNames = [
|
|
||||||
'revision_timestamp',
|
|
||||||
'created',
|
|
||||||
'changed',
|
|
||||||
'promote',
|
|
||||||
'sticky',
|
|
||||||
'path',
|
|
||||||
'revision_uid',
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -11,6 +11,7 @@ use Drupal\Core\Entity\FieldableEntityInterface;
|
||||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||||
use Drupal\Core\Entity\EntityInterface;
|
use Drupal\Core\Entity\EntityInterface;
|
||||||
use Drupal\Core\Entity\EntityStorageException;
|
use Drupal\Core\Entity\EntityStorageException;
|
||||||
|
use Drupal\Core\Field\FieldItemListInterface;
|
||||||
use Drupal\Core\Http\Exception\CacheableAccessDeniedHttpException;
|
use Drupal\Core\Http\Exception\CacheableAccessDeniedHttpException;
|
||||||
use Drupal\rest\Plugin\ResourceBase;
|
use Drupal\rest\Plugin\ResourceBase;
|
||||||
use Drupal\rest\ResourceResponse;
|
use Drupal\rest\ResourceResponse;
|
||||||
|
@ -226,38 +227,18 @@ class EntityResource extends ResourceBase implements DependentPluginInterface {
|
||||||
throw new AccessDeniedHttpException($entity_access->getReason() ?: $this->generateFallbackAccessDeniedMessage($entity, 'update'));
|
throw new AccessDeniedHttpException($entity_access->getReason() ?: $this->generateFallbackAccessDeniedMessage($entity, 'update'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overwrite the received properties.
|
// Overwrite the received fields.
|
||||||
$entity_keys = $entity->getEntityType()->getKeys();
|
|
||||||
foreach ($entity->_restSubmittedFields as $field_name) {
|
foreach ($entity->_restSubmittedFields as $field_name) {
|
||||||
$field = $entity->get($field_name);
|
$field = $entity->get($field_name);
|
||||||
|
// It is not possible to set the language to NULL as it is automatically
|
||||||
// Entity key fields need special treatment: together they uniquely
|
// re-initialized. As it must not be empty, skip it if it is.
|
||||||
// identify the entity. Therefore it does not make sense to modify any of
|
// @todo Remove in https://www.drupal.org/project/drupal/issues/2933408.
|
||||||
// them. However, rather than throwing an error, we just ignore them as
|
if ($entity->getEntityType()->hasKey('langcode') && $field_name === $entity->getEntityType()->getKey('langcode') && $field->isEmpty()) {
|
||||||
// long as their specified values match their current values.
|
continue;
|
||||||
if (in_array($field_name, $entity_keys, TRUE)) {
|
|
||||||
// @todo Work around the wrong assumption that entity keys need special
|
|
||||||
// treatment, when only read-only fields need it.
|
|
||||||
// This will be fixed in https://www.drupal.org/node/2824851.
|
|
||||||
if ($entity->getEntityTypeId() == 'comment' && $field_name == 'status' && !$original_entity->get($field_name)->access('edit')) {
|
|
||||||
throw new AccessDeniedHttpException("Access denied on updating field '$field_name'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unchanged values for entity keys don't need access checking.
|
|
||||||
if ($original_entity->get($field_name)->equals($field)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// It is not possible to set the language to NULL as it is automatically
|
|
||||||
// re-initialized. As it must not be empty, skip it if it is.
|
|
||||||
elseif (isset($entity_keys['langcode']) && $field_name === $entity_keys['langcode'] && $field->isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if ($this->checkPatchFieldAccess($original_entity->get($field_name), $field)) {
|
||||||
if (!$original_entity->get($field_name)->access('edit')) {
|
$original_entity->set($field_name, $field->getValue());
|
||||||
throw new AccessDeniedHttpException("Access denied on updating field '$field_name'.");
|
|
||||||
}
|
}
|
||||||
$original_entity->set($field_name, $field->getValue());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the received data before saving.
|
// Validate the received data before saving.
|
||||||
|
@ -274,6 +255,49 @@ class EntityResource extends ResourceBase implements DependentPluginInterface {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the given field should be PATCHed.
|
||||||
|
*
|
||||||
|
* @param \Drupal\Core\Field\FieldItemListInterface $original_field
|
||||||
|
* The original (stored) value for the field.
|
||||||
|
* @param \Drupal\Core\Field\FieldItemListInterface $received_field
|
||||||
|
* The received value for the field.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
* Whether the field should be PATCHed or not.
|
||||||
|
*
|
||||||
|
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
||||||
|
* Thrown when the user sending the request is not allowed to update the
|
||||||
|
* field. Only thrown when the user could not abuse this information to
|
||||||
|
* determine the stored value.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
protected function checkPatchFieldAccess(FieldItemListInterface $original_field, FieldItemListInterface $received_field) {
|
||||||
|
// 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')) {
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The user might not have access to edit the field, but still needs to
|
||||||
|
// submit the current field value as part of the PATCH request. For
|
||||||
|
// example, the entity keys required by denormalizers. Therefore, if the
|
||||||
|
// received value equals the stored value, return FALSE without throwing an
|
||||||
|
// exception. But only for fields that the user has access to view, because
|
||||||
|
// the user has no legitimate way of knowing the current value of fields
|
||||||
|
// that they are not allowed to view, and we must not make the presence or
|
||||||
|
// absence of a 403 response a way to find that out.
|
||||||
|
if ($original_field->equals($received_field) && $original_field->access('view')) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's helpful and safe to let the user know when they are not allowed to
|
||||||
|
// update a field.
|
||||||
|
$field_name = $received_field->getName();
|
||||||
|
throw new AccessDeniedHttpException("Access denied on updating field '$field_name'.");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Responds to entity DELETE requests.
|
* Responds to entity DELETE requests.
|
||||||
*
|
*
|
||||||
|
|
|
@ -3,15 +3,20 @@
|
||||||
namespace Drupal\Tests\rest\Functional\EntityResource;
|
namespace Drupal\Tests\rest\Functional\EntityResource;
|
||||||
|
|
||||||
use Drupal\Component\Utility\NestedArray;
|
use Drupal\Component\Utility\NestedArray;
|
||||||
|
use Drupal\Component\Utility\Random;
|
||||||
use Drupal\Core\Cache\Cache;
|
use Drupal\Core\Cache\Cache;
|
||||||
use Drupal\Core\Cache\CacheableResponseInterface;
|
use Drupal\Core\Cache\CacheableResponseInterface;
|
||||||
use Drupal\Core\Cache\CacheableMetadata;
|
use Drupal\Core\Cache\CacheableMetadata;
|
||||||
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
||||||
use Drupal\Core\Entity\ContentEntityNullStorage;
|
use Drupal\Core\Entity\ContentEntityNullStorage;
|
||||||
|
use Drupal\Core\Entity\EntityInterface;
|
||||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||||
|
use Drupal\Core\Field\Plugin\Field\FieldType\BooleanItem;
|
||||||
|
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
|
||||||
use Drupal\Core\Url;
|
use Drupal\Core\Url;
|
||||||
use Drupal\field\Entity\FieldConfig;
|
use Drupal\field\Entity\FieldConfig;
|
||||||
use Drupal\field\Entity\FieldStorageConfig;
|
use Drupal\field\Entity\FieldStorageConfig;
|
||||||
|
use Drupal\path\Plugin\Field\FieldType\PathItem;
|
||||||
use Drupal\rest\ResourceResponseInterface;
|
use Drupal\rest\ResourceResponseInterface;
|
||||||
use Drupal\Tests\rest\Functional\ResourceTestBase;
|
use Drupal\Tests\rest\Functional\ResourceTestBase;
|
||||||
use GuzzleHttp\RequestOptions;
|
use GuzzleHttp\RequestOptions;
|
||||||
|
@ -906,6 +911,10 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
|
||||||
$parseable_valid_request_body_2 = $this->serializer->encode($this->getNormalizedPatchEntity(), static::$format);
|
$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 = $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_2 = $this->serializer->encode($this->getNormalizedPatchEntity() + ['field_rest_test' => [['value' => $this->randomString()]]], 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
|
// The URL and Guzzle request options that will be used in this test. The
|
||||||
// request options will be modified/expanded throughout this test:
|
// request options will be modified/expanded throughout this test:
|
||||||
|
@ -997,22 +1006,31 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
|
||||||
$response = $this->request('PATCH', $url, $request_options);
|
$response = $this->request('PATCH', $url, $request_options);
|
||||||
$this->assertResourceErrorResponse(403, "Access denied on updating field 'field_rest_test'.", $response);
|
$this->assertResourceErrorResponse(403, "Access denied on updating field 'field_rest_test'.", $response);
|
||||||
|
|
||||||
// DX: 403 when sending PATCH request with read-only fields.
|
$request_options[RequestOptions::BODY] = $parseable_invalid_request_body_3;
|
||||||
// First send all fields (the "maximum normalization"). Assert the expected
|
|
||||||
// error message for the first PATCH-protected field. Remove that field from
|
// DX: 403 when entity contains field without 'edit' nor 'view' access, even
|
||||||
// the normalization, send another request, assert the next PATCH-protected
|
// when the value for that field matches the current value. This is allowed
|
||||||
// field error message. And so on.
|
// in principle, but leads to information disclosure.
|
||||||
$max_normalization = $this->getNormalizedPatchEntity() + $this->serializer->normalize($this->entity, static::$format);
|
$response = $this->request('PATCH', $url, $request_options);
|
||||||
|
$this->assertResourceErrorResponse(403, "Access denied on updating field 'field_rest_test'.", $response);
|
||||||
|
|
||||||
|
// DX: 403 when sending PATCH request with updated read-only fields.
|
||||||
|
list($modified_entity, $original_values) = static::getModifiedEntityForPatchTesting($this->entity);
|
||||||
|
// Send PATCH request by serializing the modified entity, assert the error
|
||||||
|
// response, change the modified entity field that caused the error response
|
||||||
|
// back to its original value, repeat.
|
||||||
for ($i = 0; $i < count(static::$patchProtectedFieldNames); $i++) {
|
for ($i = 0; $i < count(static::$patchProtectedFieldNames); $i++) {
|
||||||
$max_normalization = $this->removeFieldsFromNormalization($max_normalization, array_slice(static::$patchProtectedFieldNames, 0, $i));
|
$patch_protected_field_name = static::$patchProtectedFieldNames[$i];
|
||||||
$request_options[RequestOptions::BODY] = $this->serializer->serialize($max_normalization, static::$format);
|
$request_options[RequestOptions::BODY] = $this->serializer->serialize($modified_entity, static::$format);
|
||||||
$response = $this->request('PATCH', $url, $request_options);
|
$response = $this->request('PATCH', $url, $request_options);
|
||||||
$this->assertResourceErrorResponse(403, "Access denied on updating field '" . static::$patchProtectedFieldNames[$i] . "'.", $response);
|
$this->assertResourceErrorResponse(403, "Access denied on updating field '" . $patch_protected_field_name . "'.", $response);
|
||||||
|
$modified_entity->get($patch_protected_field_name)->setValue($original_values[$patch_protected_field_name]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 200 for well-formed request that sends the maximum number of fields.
|
// 200 for well-formed PATCH request that sends all fields (even including
|
||||||
$max_normalization = $this->removeFieldsFromNormalization($max_normalization, static::$patchProtectedFieldNames);
|
// read-only ones, but with unchanged values).
|
||||||
$request_options[RequestOptions::BODY] = $this->serializer->serialize($max_normalization, static::$format);
|
$valid_request_body = $this->getNormalizedPatchEntity() + $this->serializer->normalize($this->entity, static::$format);
|
||||||
|
$request_options[RequestOptions::BODY] = $this->serializer->serialize($valid_request_body, static::$format);
|
||||||
$response = $this->request('PATCH', $url, $request_options);
|
$response = $this->request('PATCH', $url, $request_options);
|
||||||
$this->assertResourceResponse(200, FALSE, $response);
|
$this->assertResourceResponse(200, FALSE, $response);
|
||||||
|
|
||||||
|
@ -1234,6 +1252,57 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
|
||||||
return $has_create_url ? Url::fromUri('internal:' . $this->entity->getEntityType()->getLinkTemplate('create')) : Url::fromUri('base:entity/' . static::$entityTypeId);
|
return $has_create_url ? Url::fromUri('internal:' . $this->entity->getEntityType()->getLinkTemplate('create')) : Url::fromUri('base:entity/' . static::$entityTypeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clones the given entity and modifies all PATCH-protected fields.
|
||||||
|
*
|
||||||
|
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||||
|
* The entity being tested and to modify.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* Contains two items:
|
||||||
|
* 1. The modified entity object.
|
||||||
|
* 2. The original field values, keyed by field name.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
protected static function getModifiedEntityForPatchTesting(EntityInterface $entity) {
|
||||||
|
$modified_entity = clone $entity;
|
||||||
|
$original_values = [];
|
||||||
|
foreach (static::$patchProtectedFieldNames as $field_name) {
|
||||||
|
$field = $modified_entity->get($field_name);
|
||||||
|
$original_values[$field_name] = $field->getValue();
|
||||||
|
switch ($field->getItemDefinition()->getClass()) {
|
||||||
|
case EntityReferenceItem::class:
|
||||||
|
// EntityReferenceItem::generateSampleValue() picks one of the last 50
|
||||||
|
// entities of the supported type & bundle. We don't care if the value
|
||||||
|
// is valid, we only care that it's different.
|
||||||
|
$field->setValue(['target_id' => 99999]);
|
||||||
|
break;
|
||||||
|
case BooleanItem::class:
|
||||||
|
// BooleanItem::generateSampleValue() picks either 0 or 1. So a 50%
|
||||||
|
// chance of not picking a different value.
|
||||||
|
$field->value = ((int) $field->value) === 1 ? '0' : '1';
|
||||||
|
break;
|
||||||
|
case PathItem::class:
|
||||||
|
// PathItem::generateSampleValue() doesn't set a PID, which causes
|
||||||
|
// PathItem::postSave() to fail. Keep the PID (and other properties),
|
||||||
|
// just modify the alias.
|
||||||
|
$value = $field->getValue();
|
||||||
|
$value['alias'] = str_replace(' ', '-', strtolower((new Random())->sentences(3)));
|
||||||
|
$field->setValue($value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$original_field = clone $field;
|
||||||
|
while ($field->equals($original_field)) {
|
||||||
|
$field->generateSampleItems();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$modified_entity, $original_values];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes the given entity normalization invalid.
|
* Makes the given entity normalization invalid.
|
||||||
*
|
*
|
||||||
|
@ -1251,23 +1320,6 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
|
||||||
return $normalization;
|
return $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.
|
* Asserts a 406 response… or in some cases a 403 response, because weirdness.
|
||||||
*
|
*
|
||||||
|
|
|
@ -235,20 +235,6 @@ abstract class NodeResourceTestBase extends EntityResourceTestBase {
|
||||||
$response = $this->request('GET', $url, $this->getAuthenticationRequestOptions('GET'));
|
$response = $this->request('GET', $url, $this->getAuthenticationRequestOptions('GET'));
|
||||||
$normalization = $this->serializer->decode((string) $response->getBody(), static::$format);
|
$normalization = $this->serializer->decode((string) $response->getBody(), static::$format);
|
||||||
|
|
||||||
// @todo In https://www.drupal.org/node/2824851, we will be able to stop
|
|
||||||
// unsetting these fields from the normalization, because
|
|
||||||
// EntityResource::patch() will ignore any fields that are sent that
|
|
||||||
// match the current value (and obviously we're sending the current
|
|
||||||
// value).
|
|
||||||
$normalization = $this->removeFieldsFromNormalization($normalization, [
|
|
||||||
'revision_timestamp',
|
|
||||||
'revision_uid',
|
|
||||||
'created',
|
|
||||||
'changed',
|
|
||||||
'promote',
|
|
||||||
'sticky',
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Change node's path alias.
|
// Change node's path alias.
|
||||||
$normalization['path'][0]['alias'] .= 's-rule-the-world';
|
$normalization['path'][0]['alias'] .= 's-rule-the-world';
|
||||||
|
|
||||||
|
@ -258,8 +244,11 @@ abstract class NodeResourceTestBase extends EntityResourceTestBase {
|
||||||
$request_options = array_merge_recursive($request_options, $this->getAuthenticationRequestOptions('PATCH'));
|
$request_options = array_merge_recursive($request_options, $this->getAuthenticationRequestOptions('PATCH'));
|
||||||
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
|
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
|
||||||
|
|
||||||
// PATCH request: 403 when creating URL aliases unauthorized.
|
// PATCH request: 403 when creating URL aliases unauthorized. Before
|
||||||
|
// asserting the 403 response, assert that the stored path alias remains
|
||||||
|
// unchanged.
|
||||||
$response = $this->request('PATCH', $url, $request_options);
|
$response = $this->request('PATCH', $url, $request_options);
|
||||||
|
$this->assertSame('/llama', $this->entityStorage->loadUnchanged($this->entity->id())->get('path')->alias);
|
||||||
$this->assertResourceErrorResponse(403, "Access denied on updating field 'path'.", $response);
|
$this->assertResourceErrorResponse(403, "Access denied on updating field 'path'.", $response);
|
||||||
|
|
||||||
// Grant permission to create URL aliases.
|
// Grant permission to create URL aliases.
|
||||||
|
|
|
@ -208,15 +208,6 @@ abstract class TermResourceTestBase extends EntityResourceTestBase {
|
||||||
$response = $this->request('GET', $url, $this->getAuthenticationRequestOptions('GET'));
|
$response = $this->request('GET', $url, $this->getAuthenticationRequestOptions('GET'));
|
||||||
$normalization = $this->serializer->decode((string) $response->getBody(), static::$format);
|
$normalization = $this->serializer->decode((string) $response->getBody(), static::$format);
|
||||||
|
|
||||||
// @todo In https://www.drupal.org/node/2824851, we will be able to stop
|
|
||||||
// unsetting these fields from the normalization, because
|
|
||||||
// EntityResource::patch() will ignore any fields that are sent that
|
|
||||||
// match the current value (and obviously we're sending the current
|
|
||||||
// value).
|
|
||||||
$normalization = $this->removeFieldsFromNormalization($normalization, [
|
|
||||||
'changed',
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Change term's path alias.
|
// Change term's path alias.
|
||||||
$normalization['path'][0]['alias'] .= 's-rule-the-world';
|
$normalization['path'][0]['alias'] .= 's-rule-the-world';
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue