Merge 8.9.19
commit
4228606449
|
@ -897,7 +897,11 @@
|
|||
},
|
||||
{
|
||||
"name": "drupal/core-project-message",
|
||||
<<<<<<< HEAD
|
||||
"version": "8.9.x-dev",
|
||||
=======
|
||||
"version": "8.9.19",
|
||||
>>>>>>> 8.9.19
|
||||
"dist": {
|
||||
"type": "path",
|
||||
"url": "composer/Plugin/ProjectMessage",
|
||||
|
@ -930,7 +934,11 @@
|
|||
},
|
||||
{
|
||||
"name": "drupal/core-vendor-hardening",
|
||||
<<<<<<< HEAD
|
||||
"version": "8.9.x-dev",
|
||||
=======
|
||||
"version": "8.9.19",
|
||||
>>>>>>> 8.9.19
|
||||
"dist": {
|
||||
"type": "path",
|
||||
"url": "composer/Plugin/VendorHardening",
|
||||
|
|
|
@ -82,7 +82,7 @@ class Drupal {
|
|||
/**
|
||||
* The current system version.
|
||||
*/
|
||||
const VERSION = '8.9.19-dev';
|
||||
const VERSION = '8.9.20-dev';
|
||||
|
||||
/**
|
||||
* Core API compatibility.
|
||||
|
|
|
@ -180,6 +180,11 @@ class QuickEditIntegrationTest extends QuickEditTestBase {
|
|||
|
||||
// Verify metadata.
|
||||
$items = $entity->get($this->fieldName);
|
||||
\Drupal::state()->set('quickedit_test_field_access', 'forbidden');
|
||||
$this->assertSame(['access' => FALSE], $this->metadataGenerator->generateFieldMetadata($items, 'default'));
|
||||
\Drupal::state()->set('quickedit_test_field_access', 'neutral');
|
||||
$this->assertSame(['access' => FALSE], $this->metadataGenerator->generateFieldMetadata($items, 'default'));
|
||||
\Drupal::state()->set('quickedit_test_field_access', 'allowed');
|
||||
$metadata = $this->metadataGenerator->generateFieldMetadata($items, 'default');
|
||||
$expected = [
|
||||
'access' => TRUE,
|
||||
|
|
|
@ -255,15 +255,27 @@ class FileUploadResource extends ResourceBase {
|
|||
$file->setOwnerId($this->currentUser->id());
|
||||
$file->setFilename($prepared_filename);
|
||||
$file->setMimeType($this->mimeTypeGuesser->guess($prepared_filename));
|
||||
$file->setFileUri($file_uri);
|
||||
$file->setFileUri($temp_file_path);
|
||||
// Set the size. This is done in File::preSave() but we validate the file
|
||||
// before it is saved.
|
||||
$file->setSize(@filesize($temp_file_path));
|
||||
|
||||
// Validate the file entity against entity-level validation and field-level
|
||||
// validators.
|
||||
$this->validate($file, $validators);
|
||||
// Validate the file against field-level validators first while the file is
|
||||
// still a temporary file. Validation is split up in 2 steps to be the same
|
||||
// as in _file_save_upload_single().
|
||||
// For backwards compatibility this part is copied from ::validate() to
|
||||
// leave that method behavior unchanged.
|
||||
// @todo Improve this with a file uploader service in
|
||||
// https://www.drupal.org/project/drupal/issues/2940383
|
||||
$errors = file_validate($file, $validators);
|
||||
|
||||
if (!empty($errors)) {
|
||||
$message = "Unprocessable Entity: file validation failed.\n";
|
||||
$message .= implode("\n", array_map([PlainTextOutput::class, 'renderFromHtml'], $errors));
|
||||
throw new UnprocessableEntityHttpException($message);
|
||||
}
|
||||
|
||||
$file->setFileUri($file_uri);
|
||||
// Move the file to the correct location after validation. Use
|
||||
// FileSystemInterface::EXISTS_ERROR as the file location has already been
|
||||
// determined above in FileSystem::getDestinationFilename().
|
||||
|
@ -274,6 +286,9 @@ class FileUploadResource extends ResourceBase {
|
|||
throw new HttpException(500, 'Temporary file could not be moved to file location');
|
||||
}
|
||||
|
||||
// Second step of the validation on the file object itself now.
|
||||
$this->resourceValidate($file);
|
||||
|
||||
$file->save();
|
||||
|
||||
$this->lock->release($lock_id);
|
||||
|
@ -426,6 +441,11 @@ class FileUploadResource extends ResourceBase {
|
|||
/**
|
||||
* Validates the file.
|
||||
*
|
||||
* @todo this method is unused in this class because file validation needs to
|
||||
* be split up in 2 steps in ::post(). Add a deprecation notice as soon as a
|
||||
* central core file upload service can be used in this class.
|
||||
* See https://www.drupal.org/project/drupal/issues/2940383
|
||||
*
|
||||
* @param \Drupal\file\FileInterface $file
|
||||
* The file entity to validate.
|
||||
* @param array $validators
|
||||
|
|
|
@ -42,9 +42,19 @@ class QuickEditImageTest extends QuickEditJavascriptTestBase {
|
|||
|
||||
// Create the Article node type.
|
||||
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that quick editor works correctly with images.
|
||||
*
|
||||
* @covers ::isCompatible
|
||||
* @covers ::getAttachments
|
||||
*
|
||||
* @dataProvider providerTestImageInPlaceEditor
|
||||
*/
|
||||
public function testImageInPlaceEditor($admin_permission = FALSE) {
|
||||
// Log in as a content author who can use Quick Edit and edit Articles.
|
||||
$this->contentAuthorUser = $this->drupalCreateUser([
|
||||
$permissions = [
|
||||
'access contextual links',
|
||||
'access toolbar',
|
||||
'access in-place editing',
|
||||
|
@ -52,17 +62,13 @@ class QuickEditImageTest extends QuickEditJavascriptTestBase {
|
|||
'create article content',
|
||||
'edit any article content',
|
||||
'delete any article content',
|
||||
]);
|
||||
];
|
||||
if ($admin_permission) {
|
||||
$permissions[] = 'administer nodes';
|
||||
}
|
||||
$this->contentAuthorUser = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($this->contentAuthorUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that quick editor works correctly with images.
|
||||
*
|
||||
* @covers ::isCompatible
|
||||
* @covers ::getAttachments
|
||||
*/
|
||||
public function testImageInPlaceEditor() {
|
||||
// Create a field with a basic filetype restriction.
|
||||
$field_name = strtolower($this->randomMachineName());
|
||||
$field_settings = [
|
||||
|
@ -127,13 +133,25 @@ class QuickEditImageTest extends QuickEditJavascriptTestBase {
|
|||
$this->assertEntityInstanceStates([
|
||||
'node/1[0]' => 'closed',
|
||||
]);
|
||||
|
||||
$admin_inactive = [];
|
||||
$admin_candidate = [];
|
||||
if ($admin_permission) {
|
||||
$admin_inactive = [
|
||||
'node/1/uid/en/full' => 'inactive',
|
||||
'node/1/created/en/full' => 'inactive',
|
||||
];
|
||||
$admin_candidate = [
|
||||
'node/1/uid/en/full' => 'candidate',
|
||||
'node/1/created/en/full' => 'candidate',
|
||||
];
|
||||
}
|
||||
|
||||
$this->assertEntityInstanceFieldStates('node', 1, 0, [
|
||||
'node/1/title/en/full' => 'inactive',
|
||||
'node/1/uid/en/full' => 'inactive',
|
||||
'node/1/created/en/full' => 'inactive',
|
||||
'node/1/body/en/full' => 'inactive',
|
||||
'node/1/' . $field_name . '/en/full' => 'inactive',
|
||||
]);
|
||||
] + $admin_inactive);
|
||||
|
||||
// Start in-place editing of the article node.
|
||||
$this->startQuickEditViaToolbar('node', 1, 0);
|
||||
|
@ -143,11 +161,9 @@ class QuickEditImageTest extends QuickEditJavascriptTestBase {
|
|||
$this->assertQuickEditEntityToolbar((string) $node->label(), NULL);
|
||||
$this->assertEntityInstanceFieldStates('node', 1, 0, [
|
||||
'node/1/title/en/full' => 'candidate',
|
||||
'node/1/uid/en/full' => 'candidate',
|
||||
'node/1/created/en/full' => 'candidate',
|
||||
'node/1/body/en/full' => 'candidate',
|
||||
'node/1/' . $field_name . '/en/full' => 'candidate',
|
||||
]);
|
||||
] + $admin_candidate);
|
||||
|
||||
// Click the image field.
|
||||
$this->click($field_selector);
|
||||
|
@ -155,21 +171,17 @@ class QuickEditImageTest extends QuickEditJavascriptTestBase {
|
|||
$this->assertSession()->elementExists('css', $field_selector . ' .quickedit-image-dropzone');
|
||||
$this->assertEntityInstanceFieldStates('node', 1, 0, [
|
||||
'node/1/title/en/full' => 'candidate',
|
||||
'node/1/uid/en/full' => 'candidate',
|
||||
'node/1/created/en/full' => 'candidate',
|
||||
'node/1/body/en/full' => 'candidate',
|
||||
'node/1/' . $field_name . '/en/full' => 'active',
|
||||
]);
|
||||
] + $admin_candidate);
|
||||
|
||||
// Type new 'alt' text.
|
||||
$this->typeInImageEditorAltTextInput('New text');
|
||||
$this->assertEntityInstanceFieldStates('node', 1, 0, [
|
||||
'node/1/title/en/full' => 'candidate',
|
||||
'node/1/uid/en/full' => 'candidate',
|
||||
'node/1/created/en/full' => 'candidate',
|
||||
'node/1/body/en/full' => 'candidate',
|
||||
'node/1/' . $field_name . '/en/full' => 'changed',
|
||||
]);
|
||||
] + $admin_candidate);
|
||||
|
||||
// Drag and drop an image.
|
||||
$this->dropImageOnImageEditor($valid_images[1]->uri);
|
||||
|
@ -184,11 +196,9 @@ class QuickEditImageTest extends QuickEditJavascriptTestBase {
|
|||
]);
|
||||
$this->assertEntityInstanceFieldStates('node', 1, 0, [
|
||||
'node/1/title/en/full' => 'candidate',
|
||||
'node/1/uid/en/full' => 'candidate',
|
||||
'node/1/created/en/full' => 'candidate',
|
||||
'node/1/body/en/full' => 'candidate',
|
||||
'node/1/' . $field_name . '/en/full' => 'saving',
|
||||
]);
|
||||
] + $admin_candidate);
|
||||
$this->assertEntityInstanceFieldMarkup('node', 1, 0, [
|
||||
'node/1/' . $field_name . '/en/full' => '.quickedit-changed',
|
||||
]);
|
||||
|
@ -207,4 +217,17 @@ class QuickEditImageTest extends QuickEditJavascriptTestBase {
|
|||
$this->assertSession()->elementExists('css', $entity_selector . ' ' . $field_selector . ' ' . $new_image_selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for ::testImageInPlaceEditor().
|
||||
*
|
||||
* @return array
|
||||
* Test cases.
|
||||
*/
|
||||
public function providerTestImageInPlaceEditor(): array {
|
||||
return [
|
||||
'with permission' => [TRUE],
|
||||
'without permission' => [FALSE],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ use Drupal\Core\Render\BubbleableMetadata;
|
|||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Utility\Token;
|
||||
use Drupal\Component\Render\PlainTextOutput;
|
||||
use Drupal\Core\Entity\EntityConstraintViolationList;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\file\Plugin\Field\FieldType\FileFieldItemList;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
@ -174,18 +175,37 @@ class TemporaryJsonapiFileFieldUploader {
|
|||
$file->setOwnerId($owner->id());
|
||||
$file->setFilename($prepared_filename);
|
||||
$file->setMimeType($this->mimeTypeGuesser->guess($prepared_filename));
|
||||
$file->setFileUri($file_uri);
|
||||
$file->setFileUri($temp_file_path);
|
||||
// Set the size. This is done in File::preSave() but we validate the file
|
||||
// before it is saved.
|
||||
$file->setSize(@filesize($temp_file_path));
|
||||
|
||||
// Validate the file entity against entity-level validation and field-level
|
||||
// validators.
|
||||
$violations = $this->validate($file, $validators);
|
||||
if ($violations->count() > 0) {
|
||||
// Validate the file against field-level validators first while the file is
|
||||
// still a temporary file. Validation is split up in 2 steps to be the same
|
||||
// as in _file_save_upload_single().
|
||||
// For backwards compatibility this part is copied from ::validate() to
|
||||
// leave that method behavior unchanged.
|
||||
// @todo Improve this with a file uploader service in
|
||||
// https://www.drupal.org/project/drupal/issues/2940383
|
||||
$errors = file_validate($file, $validators);
|
||||
if (!empty($errors)) {
|
||||
$violations = new EntityConstraintViolationList($file);
|
||||
$translator = new DrupalTranslator();
|
||||
$entity = EntityAdapter::createFromEntity($file);
|
||||
foreach ($errors as $error) {
|
||||
$violation = new ConstraintViolation($translator->trans($error),
|
||||
$error,
|
||||
[],
|
||||
$entity,
|
||||
'',
|
||||
NULL
|
||||
);
|
||||
$violations->add($violation);
|
||||
}
|
||||
return $violations;
|
||||
}
|
||||
|
||||
$file->setFileUri($file_uri);
|
||||
// Move the file to the correct location after validation. Use
|
||||
// FileSystemInterface::EXISTS_ERROR as the file location has already been
|
||||
// determined above in FileSystem::getDestinationFilename().
|
||||
|
@ -196,6 +216,16 @@ class TemporaryJsonapiFileFieldUploader {
|
|||
throw new HttpException(500, 'Temporary file could not be moved to file location');
|
||||
}
|
||||
|
||||
// Second step of the validation on the file object itself now.
|
||||
$violations = $file->validate();
|
||||
|
||||
// Remove violations of inaccessible fields as they cannot stem from our
|
||||
// changes.
|
||||
$violations->filterByFieldAccess();
|
||||
if ($violations->count() > 0) {
|
||||
return $violations;
|
||||
}
|
||||
|
||||
$file->save();
|
||||
|
||||
$this->lock->release($lock_id);
|
||||
|
@ -334,6 +364,11 @@ class TemporaryJsonapiFileFieldUploader {
|
|||
/**
|
||||
* Validates the file.
|
||||
*
|
||||
* @todo this method is unused in this class because file validation needs to
|
||||
* be split up in 2 steps in ::handleFileUploadForField(). Add a deprecation
|
||||
* notice as soon as a central core file upload service can be used in this
|
||||
* class. See https://www.drupal.org/project/drupal/issues/2940383
|
||||
*
|
||||
* @param \Drupal\file\FileInterface $file
|
||||
* The file entity to validate.
|
||||
* @param array $validators
|
||||
|
|
|
@ -400,7 +400,7 @@ class ResourceType {
|
|||
$this->relatableResourceTypesByField = array_reduce(array_map(function (ResourceTypeRelationship $field) {
|
||||
return [$field->getPublicName() => $field->getRelatableResourceTypes()];
|
||||
}, array_filter($this->fields, function (ResourceTypeField $field) {
|
||||
return $field instanceof ResourceTypeRelationship;
|
||||
return $field instanceof ResourceTypeRelationship && $field->isFieldEnabled();
|
||||
})), 'array_merge', []);
|
||||
}
|
||||
return $this->relatableResourceTypesByField;
|
||||
|
@ -418,7 +418,7 @@ class ResourceType {
|
|||
* @see self::getRelatableResourceTypes()
|
||||
*/
|
||||
public function getRelatableResourceTypesByField($field_name) {
|
||||
return ($field = $this->getFieldByPublicName($field_name)) && $field instanceof ResourceTypeRelationship
|
||||
return ($field = $this->getFieldByPublicName($field_name)) && $field instanceof ResourceTypeRelationship && $field->isFieldEnabled()
|
||||
? $field->getRelatableResourceTypes()
|
||||
: [];
|
||||
}
|
||||
|
|
|
@ -113,6 +113,14 @@ class LayoutBuilderQuickEditTest extends QuickEditJavascriptTestBase {
|
|||
|
||||
$this->drupalLogin($this->contentAuthorUser);
|
||||
$this->usingLayoutBuilder = TRUE;
|
||||
$this->assertQuickEditInit(['title']);
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'access contextual links',
|
||||
'access in-place editing',
|
||||
'access content',
|
||||
'edit any article content',
|
||||
'administer nodes',
|
||||
]));
|
||||
$this->assertQuickEditInit(['title', 'uid', 'created']);
|
||||
}
|
||||
|
||||
|
@ -124,7 +132,7 @@ class LayoutBuilderQuickEditTest extends QuickEditJavascriptTestBase {
|
|||
*
|
||||
* @dataProvider providerEnableDisableLayoutBuilder
|
||||
*/
|
||||
public function testEnableDisableLayoutBuilder($use_revisions) {
|
||||
public function testEnableDisableLayoutBuilder($use_revisions, $admin_permission = FALSE) {
|
||||
if (!$use_revisions) {
|
||||
$content_type = NodeType::load('article');
|
||||
$content_type->setNewRevision(FALSE);
|
||||
|
@ -132,10 +140,18 @@ class LayoutBuilderQuickEditTest extends QuickEditJavascriptTestBase {
|
|||
}
|
||||
$fields = [
|
||||
'title',
|
||||
'uid',
|
||||
'created',
|
||||
'body',
|
||||
];
|
||||
if ($admin_permission) {
|
||||
$fields = array_merge($fields, ['uid', 'created']);
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'access contextual links',
|
||||
'access in-place editing',
|
||||
'access content',
|
||||
'edit any article content',
|
||||
'administer nodes',
|
||||
]));
|
||||
}
|
||||
|
||||
// Test article with Layout Builder disabled.
|
||||
$this->assertQuickEditInit($fields);
|
||||
|
@ -169,8 +185,10 @@ class LayoutBuilderQuickEditTest extends QuickEditJavascriptTestBase {
|
|||
*/
|
||||
public function providerEnableDisableLayoutBuilder() {
|
||||
return [
|
||||
'use revisions' => [TRUE],
|
||||
'do not use revisions' => [FALSE],
|
||||
'use revisions, not admin' => [TRUE],
|
||||
'do not use revisions, not admin' => [FALSE],
|
||||
'use revisions, admin' => [TRUE, TRUE],
|
||||
'do not use revisions, admin' => [FALSE, TRUE],
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -472,6 +472,10 @@
|
|||
uuid: this.data.attributes['data-entity-uuid'],
|
||||
},
|
||||
dataType: 'html',
|
||||
headers: {
|
||||
'X-Drupal-MediaPreview-CSRF-Token':
|
||||
editor.config.drupalMedia_previewCsrfToken,
|
||||
},
|
||||
success: (previewHtml, textStatus, jqXhr) => {
|
||||
this.element.setHtml(previewHtml);
|
||||
this.setData(
|
||||
|
|
|
@ -314,6 +314,9 @@
|
|||
uuid: this.data.attributes['data-entity-uuid']
|
||||
},
|
||||
dataType: 'html',
|
||||
headers: {
|
||||
'X-Drupal-MediaPreview-CSRF-Token': editor.config.drupalMedia_previewCsrfToken
|
||||
},
|
||||
success: function success(previewHtml, textStatus, jqXhr) {
|
||||
_this3.element.setHtml(previewHtml);
|
||||
_this3.setData('label', jqXhr.getResponseHeader('Drupal-Media-Label'));
|
||||
|
|
|
@ -7,10 +7,12 @@ use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
|||
use Drupal\Core\Entity\ContentEntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityRepositoryInterface;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\filter\FilterFormatInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
|
@ -93,6 +95,8 @@ class MediaFilterController implements ContainerInjectionInterface {
|
|||
* @see \Drupal\editor\EditorController::getUntransformedText
|
||||
*/
|
||||
public function preview(Request $request, FilterFormatInterface $filter_format) {
|
||||
self::checkCsrf($request, \Drupal::currentUser());
|
||||
|
||||
$text = $request->query->get('text');
|
||||
$uuid = $request->query->get('uuid');
|
||||
if ($text == '' || $uuid == '') {
|
||||
|
@ -140,4 +144,30 @@ class MediaFilterController implements ContainerInjectionInterface {
|
|||
->addCacheableDependency($filter_format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an AccessDeniedHttpException if the request fails CSRF validation.
|
||||
*
|
||||
* This is used instead of \Drupal\Core\Access\CsrfAccessCheck, in order to
|
||||
* allow access for anonymous users.
|
||||
*
|
||||
* @todo Refactor this to an access checker.
|
||||
*/
|
||||
private static function checkCsrf(Request $request, AccountInterface $account) {
|
||||
$header = 'X-Drupal-MediaPreview-CSRF-Token';
|
||||
|
||||
if (!$request->headers->has($header)) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
if ($account->isAnonymous()) {
|
||||
// For anonymous users, just the presence of the custom header is
|
||||
// sufficient protection.
|
||||
return;
|
||||
}
|
||||
// For authenticated users, validate the token value.
|
||||
$token = $request->headers->get($header);
|
||||
if (!\Drupal::csrfToken()->validate($token, $header)) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -98,7 +98,9 @@ class DrupalMedia extends PluginBase implements ContainerFactoryPluginInterface,
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfig(Editor $editor) {
|
||||
return [];
|
||||
return [
|
||||
'drupalMedia_previewCsrfToken' => \Drupal::csrfToken()->get('X-Drupal-MediaPreview-CSRF-Token'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
namespace Drupal\Tests\media\FunctionalJavascript;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\editor\Entity\Editor;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\file\Entity\File;
|
||||
|
@ -1027,14 +1026,13 @@ class CKEditorIntegrationTest extends WebDriverTestBase {
|
|||
* @dataProvider previewAccessProvider
|
||||
*/
|
||||
public function testEmbedPreviewAccess($media_embed_enabled, $can_use_format) {
|
||||
$format = FilterFormat::create([
|
||||
'format' => $this->randomMachineName(),
|
||||
'name' => $this->randomString(),
|
||||
'filters' => [
|
||||
'filter_align' => ['status' => TRUE],
|
||||
'filter_caption' => ['status' => TRUE],
|
||||
'media_embed' => ['status' => $media_embed_enabled],
|
||||
],
|
||||
// Reconfigure the host entity's text format to suit our needs.
|
||||
/** @var \Drupal\filter\FilterFormatInterface $format */
|
||||
$format = FilterFormat::load($this->host->body->format);
|
||||
$format->set('filters', [
|
||||
'filter_align' => ['status' => TRUE],
|
||||
'filter_caption' => ['status' => TRUE],
|
||||
'media_embed' => ['status' => $media_embed_enabled],
|
||||
]);
|
||||
$format->save();
|
||||
|
||||
|
@ -1045,24 +1043,23 @@ class CKEditorIntegrationTest extends WebDriverTestBase {
|
|||
$permissions[] = $format->getPermissionName();
|
||||
}
|
||||
$this->drupalLogin($this->drupalCreateUser($permissions));
|
||||
|
||||
$text = '<drupal-media data-caption="baz" data-entity-type="media" data-entity-uuid="' . $this->media->uuid() . '"></drupal-media>';
|
||||
$route_parameters = ['filter_format' => $format->id()];
|
||||
$options = [
|
||||
'query' => [
|
||||
'text' => $text,
|
||||
'uuid' => $this->media->uuid(),
|
||||
],
|
||||
];
|
||||
$this->drupalGet(Url::fromRoute('media.filter.preview', $route_parameters, $options));
|
||||
$this->drupalGet($this->host->toUrl('edit-form'));
|
||||
|
||||
$assert_session = $this->assertSession();
|
||||
if ($media_embed_enabled && $can_use_format) {
|
||||
$assert_session->elementExists('css', 'img');
|
||||
$assert_session->responseContains('baz');
|
||||
if ($can_use_format) {
|
||||
$this->waitForEditor();
|
||||
$this->assignNameToCkeditorIframe();
|
||||
$this->getSession()->switchToIFrame('ckeditor');
|
||||
if ($media_embed_enabled) {
|
||||
$this->assertNotEmpty($assert_session->waitForElementVisible('css', 'article.media'));
|
||||
}
|
||||
else {
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$assert_session->elementNotExists('css', 'article.media');
|
||||
}
|
||||
}
|
||||
else {
|
||||
$assert_session->responseContains('You are not authorized to access this page.');
|
||||
$assert_session->pageTextContains('This field has been disabled because you do not have sufficient permissions to edit it.');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -526,6 +526,9 @@
|
|||
options.success.call(entityModel);
|
||||
}
|
||||
};
|
||||
entitySaverAjax.options.headers = entitySaverAjax.options.headers || {};
|
||||
entitySaverAjax.options.headers['X-Drupal-Quickedit-CSRF-Token'] =
|
||||
drupalSettings.quickedit.csrf_token;
|
||||
// Trigger the AJAX request, which will will return the
|
||||
// quickeditEntitySaved AJAX command to which we then react.
|
||||
entitySaverAjax.execute();
|
||||
|
|
|
@ -243,6 +243,8 @@
|
|||
options.success.call(entityModel);
|
||||
}
|
||||
};
|
||||
entitySaverAjax.options.headers = entitySaverAjax.options.headers || {};
|
||||
entitySaverAjax.options.headers['X-Drupal-Quickedit-CSRF-Token'] = drupalSettings.quickedit.csrf_token;
|
||||
|
||||
entitySaverAjax.execute();
|
||||
},
|
||||
|
|
|
@ -53,6 +53,7 @@ function quickedit_page_attachments(array &$page) {
|
|||
return;
|
||||
}
|
||||
|
||||
$page['#attached']['drupalSettings']['quickedit']['csrf_token'] = \Drupal::csrfToken()->get('X-Drupal-Quickedit-CSRF-Token');
|
||||
$page['#attached']['library'][] = 'quickedit/quickedit';
|
||||
}
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ class MetadataGenerator implements MetadataGeneratorInterface {
|
|||
|
||||
// Early-return if user does not have access.
|
||||
$access = $this->accessChecker->accessEditEntityField($entity, $field_name);
|
||||
if (!$access) {
|
||||
if (!$access->isAllowed()) {
|
||||
return ['access' => FALSE];
|
||||
}
|
||||
|
||||
|
|
|
@ -6,10 +6,12 @@ use Drupal\Core\Controller\ControllerBase;
|
|||
use Drupal\Core\Entity\EntityRepositoryInterface;
|
||||
use Drupal\Core\Form\FormState;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\TempStore\PrivateTempStoreFactory;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Drupal\Core\Ajax\AjaxResponse;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
|
@ -165,6 +167,32 @@ class QuickEditController extends ControllerBase {
|
|||
return new JsonResponse($metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an AccessDeniedHttpException if the request fails CSRF validation.
|
||||
*
|
||||
* This is used instead of \Drupal\Core\Access\CsrfAccessCheck, in order to
|
||||
* allow access for anonymous users.
|
||||
*
|
||||
* @todo Refactor this to an access checker.
|
||||
*/
|
||||
private static function checkCsrf(Request $request, AccountInterface $account) {
|
||||
$header = 'X-Drupal-Quickedit-CSRF-Token';
|
||||
|
||||
if (!$request->headers->has($header)) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
if ($account->isAnonymous()) {
|
||||
// For anonymous users, just the presence of the custom header is
|
||||
// sufficient protection.
|
||||
return;
|
||||
}
|
||||
// For authenticated users, validate the token value.
|
||||
$token = $request->headers->get($header);
|
||||
if (!\Drupal::csrfToken()->validate($token, $header)) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns AJAX commands to load in-place editors' attachments.
|
||||
*
|
||||
|
@ -315,6 +343,8 @@ class QuickEditController extends ControllerBase {
|
|||
* The Ajax response.
|
||||
*/
|
||||
public function entitySave(EntityInterface $entity) {
|
||||
self::checkCsrf(\Drupal::request(), \Drupal::currentUser());
|
||||
|
||||
// Take the entity from PrivateTempStore and save in entity storage.
|
||||
// fieldForm() ensures that the PrivateTempStore copy exists ahead.
|
||||
$tempstore = $this->tempStoreFactory->get('quickedit');
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Drupal\quickedit_test;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\quickedit\Access\QuickEditEntityFieldAccessCheckInterface;
|
||||
|
||||
|
@ -14,7 +15,19 @@ class MockQuickEditEntityFieldAccessCheck implements QuickEditEntityFieldAccessC
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function accessEditEntityField(EntityInterface $entity, $field_name) {
|
||||
return TRUE;
|
||||
switch (\Drupal::state()->get('quickedit_test_field_access')) {
|
||||
case 'allowed':
|
||||
return AccessResult::allowed();
|
||||
|
||||
case 'neutral':
|
||||
return AccessResult::neutral();
|
||||
|
||||
case 'forbidden':
|
||||
return AccessResult::forbidden();
|
||||
|
||||
default:
|
||||
throw new \OutOfRangeException("The state for the 'quickedit_test_field_access' key must be either 'allowed', 'neutral' or 'forbidden'.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -146,8 +146,6 @@ class QuickEditIntegrationTest extends QuickEditJavascriptTestBase {
|
|||
]);
|
||||
$this->assertEntityInstanceFieldStates('node', 1, 0, [
|
||||
'node/1/title/en/full' => 'inactive',
|
||||
'node/1/uid/en/full' => 'inactive',
|
||||
'node/1/created/en/full' => 'inactive',
|
||||
'node/1/body/en/full' => 'inactive',
|
||||
'node/1/field_tags/en/full' => 'inactive',
|
||||
]);
|
||||
|
@ -160,8 +158,6 @@ class QuickEditIntegrationTest extends QuickEditJavascriptTestBase {
|
|||
$this->assertQuickEditEntityToolbar((string) $node->label(), NULL);
|
||||
$this->assertEntityInstanceFieldStates('node', 1, 0, [
|
||||
'node/1/title/en/full' => 'candidate',
|
||||
'node/1/uid/en/full' => 'candidate',
|
||||
'node/1/created/en/full' => 'candidate',
|
||||
'node/1/body/en/full' => 'candidate',
|
||||
'node/1/field_tags/en/full' => 'candidate',
|
||||
]);
|
||||
|
@ -174,8 +170,6 @@ class QuickEditIntegrationTest extends QuickEditJavascriptTestBase {
|
|||
$this->assertQuickEditEntityToolbar((string) $node->label(), 'Title');
|
||||
$this->assertEntityInstanceFieldStates('node', 1, 0, [
|
||||
'node/1/title/en/full' => 'active',
|
||||
'node/1/uid/en/full' => 'candidate',
|
||||
'node/1/created/en/full' => 'candidate',
|
||||
'node/1/body/en/full' => 'candidate',
|
||||
'node/1/field_tags/en/full' => 'candidate',
|
||||
]);
|
||||
|
@ -188,8 +182,6 @@ class QuickEditIntegrationTest extends QuickEditJavascriptTestBase {
|
|||
$this->awaitEntityInstanceFieldState('node', 1, 0, 'title', 'en', 'changed');
|
||||
$this->assertEntityInstanceFieldStates('node', 1, 0, [
|
||||
'node/1/title/en/full' => 'changed',
|
||||
'node/1/uid/en/full' => 'candidate',
|
||||
'node/1/created/en/full' => 'candidate',
|
||||
'node/1/body/en/full' => 'candidate',
|
||||
'node/1/field_tags/en/full' => 'candidate',
|
||||
]);
|
||||
|
@ -201,8 +193,6 @@ class QuickEditIntegrationTest extends QuickEditJavascriptTestBase {
|
|||
$this->assertQuickEditEntityToolbar((string) $node->label(), 'Body');
|
||||
$this->assertEntityInstanceFieldStates('node', 1, 0, [
|
||||
'node/1/title/en/full' => 'saving',
|
||||
'node/1/uid/en/full' => 'candidate',
|
||||
'node/1/created/en/full' => 'candidate',
|
||||
'node/1/body/en/full' => 'active',
|
||||
'node/1/field_tags/en/full' => 'candidate',
|
||||
]);
|
||||
|
@ -225,8 +215,6 @@ class QuickEditIntegrationTest extends QuickEditJavascriptTestBase {
|
|||
$assert_session->waitForElement('css', '.quickedit-toolbar-field div[id*="tags"]');
|
||||
$this->assertQuickEditEntityToolbar((string) $node->label(), 'Tags');
|
||||
$this->assertEntityInstanceFieldStates('node', 1, 0, [
|
||||
'node/1/uid/en/full' => 'candidate',
|
||||
'node/1/created/en/full' => 'candidate',
|
||||
'node/1/body/en/full' => 'candidate',
|
||||
'node/1/field_tags/en/full' => 'activating',
|
||||
'node/1/title/en/full' => 'candidate',
|
||||
|
@ -241,8 +229,6 @@ class QuickEditIntegrationTest extends QuickEditJavascriptTestBase {
|
|||
// Wait for the form to load.
|
||||
$this->assertJsCondition('document.querySelector(\'.quickedit-form-container > .quickedit-form[role="dialog"] > .placeholder\') === null');
|
||||
$this->assertEntityInstanceFieldStates('node', 1, 0, [
|
||||
'node/1/uid/en/full' => 'candidate',
|
||||
'node/1/created/en/full' => 'candidate',
|
||||
'node/1/body/en/full' => 'candidate',
|
||||
'node/1/field_tags/en/full' => 'active',
|
||||
'node/1/title/en/full' => 'candidate',
|
||||
|
@ -252,8 +238,6 @@ class QuickEditIntegrationTest extends QuickEditJavascriptTestBase {
|
|||
$this->typeInFormEditorTextInputField('field_tags[target_id]', 'foo, bar');
|
||||
$this->awaitEntityInstanceFieldState('node', 1, 0, 'field_tags', 'en', 'changed');
|
||||
$this->assertEntityInstanceFieldStates('node', 1, 0, [
|
||||
'node/1/uid/en/full' => 'candidate',
|
||||
'node/1/created/en/full' => 'candidate',
|
||||
'node/1/body/en/full' => 'candidate',
|
||||
'node/1/field_tags/en/full' => 'changed',
|
||||
'node/1/title/en/full' => 'candidate',
|
||||
|
@ -266,8 +250,6 @@ class QuickEditIntegrationTest extends QuickEditJavascriptTestBase {
|
|||
'node/1[0]' => 'committing',
|
||||
]);
|
||||
$this->assertEntityInstanceFieldStates('node', 1, 0, [
|
||||
'node/1/uid/en/full' => 'candidate',
|
||||
'node/1/created/en/full' => 'candidate',
|
||||
'node/1/body/en/full' => 'candidate',
|
||||
'node/1/field_tags/en/full' => 'saving',
|
||||
'node/1/title/en/full' => 'candidate',
|
||||
|
|
|
@ -97,6 +97,11 @@ class MetadataGeneratorTest extends QuickEditTestBase {
|
|||
|
||||
// Verify metadata for field 1.
|
||||
$items_1 = $entity->get($field_1_name);
|
||||
\Drupal::state()->set('quickedit_test_field_access', 'forbidden');
|
||||
$this->assertSame(['access' => FALSE], $this->metadataGenerator->generateFieldMetadata($items_1, 'default'));
|
||||
\Drupal::state()->set('quickedit_test_field_access', 'neutral');
|
||||
$this->assertSame(['access' => FALSE], $this->metadataGenerator->generateFieldMetadata($items_1, 'default'));
|
||||
\Drupal::state()->set('quickedit_test_field_access', 'allowed');
|
||||
$metadata_1 = $this->metadataGenerator->generateFieldMetadata($items_1, 'default');
|
||||
$expected_1 = [
|
||||
'access' => TRUE,
|
||||
|
@ -107,6 +112,11 @@ class MetadataGeneratorTest extends QuickEditTestBase {
|
|||
|
||||
// Verify metadata for field 2.
|
||||
$items_2 = $entity->get($field_2_name);
|
||||
\Drupal::state()->set('quickedit_test_field_access', 'forbidden');
|
||||
$this->assertSame(['access' => FALSE], $this->metadataGenerator->generateFieldMetadata($items_2, 'default'));
|
||||
\Drupal::state()->set('quickedit_test_field_access', 'neutral');
|
||||
$this->assertSame(['access' => FALSE], $this->metadataGenerator->generateFieldMetadata($items_2, 'default'));
|
||||
\Drupal::state()->set('quickedit_test_field_access', 'allowed');
|
||||
$metadata_2 = $this->metadataGenerator->generateFieldMetadata($items_2, 'default');
|
||||
$expected_2 = [
|
||||
'access' => TRUE,
|
||||
|
@ -163,6 +173,11 @@ class MetadataGeneratorTest extends QuickEditTestBase {
|
|||
|
||||
// Verify metadata.
|
||||
$items = $entity->get($field_name);
|
||||
\Drupal::state()->set('quickedit_test_field_access', 'forbidden');
|
||||
$this->assertSame(['access' => FALSE], $this->metadataGenerator->generateFieldMetadata($items, 'default'));
|
||||
\Drupal::state()->set('quickedit_test_field_access', 'neutral');
|
||||
$this->assertSame(['access' => FALSE], $this->metadataGenerator->generateFieldMetadata($items, 'default'));
|
||||
\Drupal::state()->set('quickedit_test_field_access', 'allowed');
|
||||
$metadata = $this->metadataGenerator->generateFieldMetadata($items, 'default');
|
||||
$expected = [
|
||||
'access' => TRUE,
|
||||
|
|
Loading…
Reference in New Issue