diff --git a/modules/media/js/plugins/drupalmedia/plugin.es6.js b/modules/media/js/plugins/drupalmedia/plugin.es6.js index ef7ea3f9b58..66152eb481b 100644 --- a/modules/media/js/plugins/drupalmedia/plugin.es6.js +++ b/modules/media/js/plugins/drupalmedia/plugin.es6.js @@ -472,6 +472,9 @@ 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( diff --git a/modules/media/js/plugins/drupalmedia/plugin.js b/modules/media/js/plugins/drupalmedia/plugin.js index 7061bf81447..9f323d37e92 100644 --- a/modules/media/js/plugins/drupalmedia/plugin.js +++ b/modules/media/js/plugins/drupalmedia/plugin.js @@ -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')); diff --git a/modules/media/src/Controller/MediaFilterController.php b/modules/media/src/Controller/MediaFilterController.php index 4b9ea6bfefe..06e11a1848b 100644 --- a/modules/media/src/Controller/MediaFilterController.php +++ b/modules/media/src/Controller/MediaFilterController.php @@ -7,10 +7,12 @@ 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 @@ public static function create(ContainerInterface $container) { * @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 @@ public static function formatUsesMediaEmbedFilter(FilterFormatInterface $filter_ ->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(); + } + } + } diff --git a/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php b/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php index c3aff900d65..d475f1ab9c7 100644 --- a/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php +++ b/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php @@ -98,7 +98,9 @@ public function getFile() { * {@inheritdoc} */ public function getConfig(Editor $editor) { - return []; + return [ + 'drupalMedia_previewCsrfToken' => \Drupal::csrfToken()->get('X-Drupal-MediaPreview-CSRF-Token'), + ]; } /** diff --git a/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php b/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php index 3909303322d..3074efcfb37 100644 --- a/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php +++ b/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php @@ -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 @@ public function linkabilityProvider() { * @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 @@ public function testEmbedPreviewAccess($media_embed_enabled, $can_use_format) { $permissions[] = $format->getPermissionName(); } $this->drupalLogin($this->drupalCreateUser($permissions)); - - $text = ''; - $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.'); } }