updated core to 8.6.3
This commit is contained in:
@@ -229,6 +229,8 @@ class EntityResource extends ResourceBase implements DependentPluginInterface {
|
||||
}
|
||||
|
||||
// Overwrite the received fields.
|
||||
// @todo Remove $changed_fields in https://www.drupal.org/project/drupal/issues/2862574.
|
||||
$changed_fields = [];
|
||||
foreach ($entity->_restSubmittedFields as $field_name) {
|
||||
$field = $entity->get($field_name);
|
||||
// It is not possible to set the language to NULL as it is automatically
|
||||
@@ -238,12 +240,18 @@ class EntityResource extends ResourceBase implements DependentPluginInterface {
|
||||
continue;
|
||||
}
|
||||
if ($this->checkPatchFieldAccess($original_entity->get($field_name), $field)) {
|
||||
$changed_fields[] = $field_name;
|
||||
$original_entity->set($field_name, $field->getValue());
|
||||
}
|
||||
}
|
||||
|
||||
// If no fields are changed, we can send a response immediately!
|
||||
if (empty($changed_fields)) {
|
||||
return new ModifiedResourceResponse($original_entity, 200);
|
||||
}
|
||||
|
||||
// Validate the received data before saving.
|
||||
$this->validate($original_entity);
|
||||
$this->validate($original_entity, $changed_fields);
|
||||
try {
|
||||
$original_entity->save();
|
||||
$this->logger->notice('Updated entity %type with ID %id.', ['%type' => $original_entity->getEntityTypeId(), '%id' => $original_entity->id()]);
|
||||
@@ -275,13 +283,6 @@ class EntityResource extends ResourceBase implements DependentPluginInterface {
|
||||
* @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.
|
||||
$field_edit_access = $original_field->access('edit', NULL, TRUE);
|
||||
if ($field_edit_access->isAllowed()) {
|
||||
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
|
||||
@@ -294,6 +295,13 @@ class EntityResource extends ResourceBase implements DependentPluginInterface {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 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.
|
||||
$field_edit_access = $original_field->access('edit', NULL, TRUE);
|
||||
if ($field_edit_access->isAllowed()) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// It's helpful and safe to let the user know when they are not allowed to
|
||||
// update a field.
|
||||
$field_name = $received_field->getName();
|
||||
|
||||
@@ -14,16 +14,23 @@ use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
|
||||
trait EntityResourceValidationTrait {
|
||||
|
||||
/**
|
||||
* Verifies that the whole entity does not violate any validation constraints.
|
||||
* Verifies that an entity does not violate any validation constraints.
|
||||
*
|
||||
* The validation errors will be filtered to not include fields to which the
|
||||
* current user does not have access and if $fields_to_validate is provided
|
||||
* will only include fields in that array.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to validate.
|
||||
* @param string[] $fields_to_validate
|
||||
* (optional) An array of field names. If specified, filters the violations
|
||||
* list to include only this set of fields.
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException
|
||||
* If validation errors are found.
|
||||
*/
|
||||
protected function validate(EntityInterface $entity) {
|
||||
// @todo Remove when https://www.drupal.org/node/2164373 is committed.
|
||||
protected function validate(EntityInterface $entity, array $fields_to_validate = []) {
|
||||
// @todo Update this check in https://www.drupal.org/node/2300677.
|
||||
if (!$entity instanceof FieldableEntityInterface) {
|
||||
return;
|
||||
}
|
||||
@@ -33,6 +40,11 @@ trait EntityResourceValidationTrait {
|
||||
// changes.
|
||||
$violations->filterByFieldAccess();
|
||||
|
||||
if ($fields_to_validate) {
|
||||
// Filter violations by explicitly provided array of field names.
|
||||
$violations->filterByFields(array_diff(array_keys($entity->getFieldDefinitions()), $fields_to_validate));
|
||||
}
|
||||
|
||||
if ($violations->count() > 0) {
|
||||
$message = "Unprocessable Entity: validation failed.\n";
|
||||
foreach ($violations as $violation) {
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
* Contains hook implementations for testing REST module.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
@@ -40,6 +42,30 @@ function rest_test_entity_field_access($operation, FieldDefinitionInterface $fie
|
||||
}
|
||||
}
|
||||
|
||||
// @see \Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase::testGet()
|
||||
// @see \Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase::testPatch()
|
||||
if ($field_definition->getName() === 'rest_test_validation') {
|
||||
switch ($operation) {
|
||||
case 'view':
|
||||
// Never ever allow this field to be viewed: this lets
|
||||
// EntityResourceTestBase::testGet() test in a "vanilla" way.
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
// No opinion.
|
||||
return AccessResult::neutral();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_base_field_info().
|
||||
*/
|
||||
function rest_test_entity_base_field_info(EntityTypeInterface $entity_type) {
|
||||
$fields = [];
|
||||
$fields['rest_test_validation'] = BaseFieldDefinition::create('string')
|
||||
->setLabel(t('REST test validation field'))
|
||||
->setDescription(t('A text field with some special validations attached used for testing purposes'))
|
||||
->addConstraint('rest_test_validation');
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\rest_test\Plugin\Validation\Constraint;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
/**
|
||||
* Adds some validations for a REST test field.
|
||||
*
|
||||
* @Constraint(
|
||||
* id = "rest_test_validation",
|
||||
* label = @Translation("REST test validation", context = "Validation")
|
||||
* )
|
||||
*
|
||||
* @see \Drupal\Core\TypedData\OptionsProviderInterface
|
||||
*/
|
||||
class RestTestConstraint extends Constraint {
|
||||
|
||||
public $message = 'REST test validation failed';
|
||||
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\rest_test\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
|
||||
/**
|
||||
* Validator for \Drupal\rest_test\Plugin\Validation\Constraint\RestTestConstraint.
|
||||
*/
|
||||
class RestTestConstraintValidator extends ConstraintValidator {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate($value, Constraint $constraint) {
|
||||
if ($value instanceof FieldItemListInterface) {
|
||||
$value = $value->getValue();
|
||||
if (!empty($value[0]['value']) && $value[0]['value'] === 'ALWAYS_FAIL') {
|
||||
$this->context->addViolation($constraint->message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -236,6 +236,7 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
|
||||
// Set a default value on the fields.
|
||||
$this->entity->set('field_rest_test', ['value' => 'All the faith he had had had had no effect on the outcome of his life.']);
|
||||
$this->entity->set('field_rest_test_multivalue', [['value' => 'One'], ['value' => 'Two']]);
|
||||
$this->entity->set('rest_test_validation', ['value' => 'allowed value']);
|
||||
$this->entity->save();
|
||||
}
|
||||
}
|
||||
@@ -667,6 +668,7 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
|
||||
// ::formatExpectedTimestampValue() to generate the timestamp value. This
|
||||
// will take into account the above config setting.
|
||||
$expected = $this->getExpectedNormalizedEntity();
|
||||
|
||||
// Config entities are not affected.
|
||||
// @see \Drupal\serialization\Normalizer\ConfigEntityNormalizer::normalize()
|
||||
static::recursiveKSort($expected);
|
||||
@@ -1144,6 +1146,39 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
|
||||
$modified_entity->get($patch_protected_field_name)->setValue($original_values[$patch_protected_field_name]);
|
||||
}
|
||||
|
||||
if ($this->entity instanceof FieldableEntityInterface) {
|
||||
// Change the rest_test_validation field to prove that then its validation
|
||||
// does run.
|
||||
$override = [
|
||||
'rest_test_validation' => [
|
||||
[
|
||||
'value' => 'ALWAYS_FAIL',
|
||||
],
|
||||
],
|
||||
];
|
||||
$valid_request_body = $override + $this->getNormalizedPatchEntity() + $this->serializer->normalize($modified_entity, static::$format);
|
||||
$request_options[RequestOptions::BODY] = $this->serializer->serialize($valid_request_body, static::$format);
|
||||
$response = $this->request('PATCH', $url, $request_options);
|
||||
$this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nrest_test_validation: REST test validation failed\n", $response);
|
||||
|
||||
// Set the rest_test_validation field to always fail validation, which
|
||||
// allows asserting that not modifying that field does not trigger
|
||||
// validation errors.
|
||||
$this->entity->set('rest_test_validation', 'ALWAYS_FAIL');
|
||||
$this->entity->save();
|
||||
|
||||
// Information disclosure prevented: when a malicious user correctly
|
||||
// guesses the current invalid value of a field, ensure a 200 is not sent
|
||||
// because this would disclose to the attacker what the current value is.
|
||||
// @see rest_test_entity_field_access()
|
||||
$response = $this->request('PATCH', $url, $request_options);
|
||||
$this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nrest_test_validation: REST test validation failed\n", $response);
|
||||
|
||||
// All requests after the above one will not include this field (neither
|
||||
// its current value nor any other), and therefore all subsequent test
|
||||
// assertions should not trigger a validation error.
|
||||
}
|
||||
|
||||
// 200 for well-formed PATCH request that sends all fields (even including
|
||||
// read-only ones, but with unchanged values).
|
||||
$valid_request_body = $this->getNormalizedPatchEntity() + $this->serializer->normalize($this->entity, static::$format);
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\ModeratedNode;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class ModeratedNodeJsonAnonTest extends ModeratedNodeResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\ModeratedNode;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class ModeratedNodeJsonBasicAuthTest extends ModeratedNodeResourceTestBase {
|
||||
|
||||
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';
|
||||
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\ModeratedNode;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class ModeratedNodeJsonCookieTest extends ModeratedNodeResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\ModeratedNode;
|
||||
|
||||
use Drupal\Tests\content_moderation\Traits\ContentModerationTestTrait;
|
||||
use Drupal\Tests\node\Functional\Rest\NodeResourceTestBase;
|
||||
|
||||
/**
|
||||
* Extend the Node resource test base and apply moderation to the entity.
|
||||
*/
|
||||
abstract class ModeratedNodeResourceTestBase extends NodeResourceTestBase {
|
||||
|
||||
use ContentModerationTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['content_moderation'];
|
||||
|
||||
/**
|
||||
* The test editorial workflow.
|
||||
*
|
||||
* @var \Drupal\workflows\WorkflowInterface
|
||||
*/
|
||||
protected $workflow;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
parent::setUpAuthorization($method);
|
||||
|
||||
switch ($method) {
|
||||
case 'POST':
|
||||
case 'PATCH':
|
||||
case 'DELETE':
|
||||
$this->grantPermissionsToTestedRole(['use editorial transition publish', 'use editorial transition create_new_draft']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
$entity = parent::createEntity();
|
||||
if (!$this->workflow) {
|
||||
$this->workflow = $this->createEditorialWorkflow();
|
||||
}
|
||||
$this->workflow->getTypePlugin()->addEntityTypeAndBundle($entity->getEntityTypeId(), $entity->bundle());
|
||||
$this->workflow->save();
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
return array_merge(parent::getExpectedNormalizedEntity(), [
|
||||
'moderation_state' => [
|
||||
[
|
||||
'value' => 'published',
|
||||
],
|
||||
],
|
||||
'vid' => [
|
||||
[
|
||||
'value' => (int) $this->entity->getRevisionId(),
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\ModeratedNode;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class ModeratedNodeXmlAnonTest extends ModeratedNodeResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testPatchPath() {
|
||||
// Deserialization of the XML format is not supported.
|
||||
$this->markTestSkipped();
|
||||
}
|
||||
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\ModeratedNode;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class ModeratedNodeXmlBasicAuthTest extends ModeratedNodeResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testPatchPath() {
|
||||
// Deserialization of the XML format is not supported.
|
||||
$this->markTestSkipped();
|
||||
}
|
||||
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\ModeratedNode;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class ModeratedNodeXmlCookieTest extends ModeratedNodeResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testPatchPath() {
|
||||
// Deserialization of the XML format is not supported.
|
||||
$this->markTestSkipped();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -661,7 +661,7 @@ class StyleSerializerTest extends ViewTestBase {
|
||||
}
|
||||
|
||||
// Test that multiple raw body fields are shown.
|
||||
// Make the body field unlimited cardinatlity.
|
||||
// Set the body field to unlimited cardinality.
|
||||
$storage_definition = $node->getFieldDefinition('body')->getFieldStorageDefinition();
|
||||
$storage_definition->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
|
||||
$storage_definition->save();
|
||||
|
||||
Reference in New Issue
Block a user