summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Pott2016-09-22 12:59:44 (GMT)
committerAlex Pott2016-09-22 12:59:44 (GMT)
commit05d832edc12f65a4652123ac6d16f0219b8a85e3 (patch)
tree8d6f76ba5813a504d05dd7a6cd917951d43f07ad
parent2fb6246e3dcf991d76142e2060b4498f35a726cc (diff)
Issue #2744197 by Boobaa, Wim Leers, alexpott: Proper private file support for images uploaded via EditorImageDialog
-rw-r--r--core/modules/editor/editor.module72
-rw-r--r--core/modules/editor/src/Tests/EditorPrivateFileReferenceFilterTest.php100
-rw-r--r--core/modules/editor/tests/editor_private_test/config/install/editor.editor.private_images.yml34
-rw-r--r--core/modules/editor/tests/editor_private_test/config/install/filter.format.private_images.yml23
-rw-r--r--core/modules/editor/tests/editor_private_test/editor_private_test.info.yml9
5 files changed, 238 insertions, 0 deletions
diff --git a/core/modules/editor/editor.module b/core/modules/editor/editor.module
index d6fa16c..0101779 100644
--- a/core/modules/editor/editor.module
+++ b/core/modules/editor/editor.module
@@ -468,6 +468,78 @@ function _editor_delete_file_usage(array $uuids, EntityInterface $entity, $count
}
/**
+ * Implements hook_file_download().
+ *
+ * @see file_file_download()
+ * @see file_get_file_references()
+ */
+function editor_file_download($uri) {
+ // Get the file record based on the URI. If not in the database just return.
+ /** @var \Drupal\file\FileInterface[] $files */
+ $files = \Drupal::entityTypeManager()
+ ->getStorage('file')
+ ->loadByProperties(['uri' => $uri]);
+ if (count($files)) {
+ foreach ($files as $item) {
+ // Since some database servers sometimes use a case-insensitive comparison
+ // by default, double check that the filename is an exact match.
+ if ($item->getFileUri() === $uri) {
+ $file = $item;
+ break;
+ }
+ }
+ }
+ if (!isset($file)) {
+ return;
+ }
+
+ // Temporary files are handled by file_file_download(), so nothing to do here
+ // about them.
+ // @see file_file_download()
+
+ // Find out if any editor-backed field contains the file.
+ $usage_list = \Drupal::service('file.usage')->listUsage($file);
+
+ // Stop processing if there are no references in order to avoid returning
+ // headers for files controlled by other modules. Make an exception for
+ // temporary files where the host entity has not yet been saved (for example,
+ // an image preview on a node creation form) in which case, allow download by
+ // the file's owner.
+ if (empty($usage_list['editor']) && ($file->isPermanent() || $file->getOwnerId() != \Drupal::currentUser()->id())) {
+ return;
+ }
+
+ // Editor.module MUST NOT call $file->access() here (like file_file_download()
+ // does) as checking the 'download' access to a file entity would end up in
+ // FileAccessControlHandler->checkAccess() and ->getFileReferences(), which
+ // calls file_get_file_references(). This latter one would allow downloading
+ // files only handled by the file.module, which is exactly not the case right
+ // here. So instead we must check if the current user is allowed to view any
+ // of the entities that reference the image using the 'editor' module.
+ if ($file->isPermanent()) {
+ $referencing_entity_is_accessible = FALSE;
+ $references = empty($usage_list['editor']) ? [] : $usage_list['editor'];
+ foreach ($references as $entity_type => $entity_ids) {
+ $referencing_entities = entity_load_multiple($entity_type, $entity_ids);
+ /** @var \Drupal\Core\Entity\EntityInterface $referencing_entity */
+ foreach ($referencing_entities as $referencing_entity) {
+ if ($referencing_entity->access('view', NULL, TRUE)->isAllowed()) {
+ $referencing_entity_is_accessible = TRUE;
+ break 2;
+ }
+ }
+ }
+ if (!$referencing_entity_is_accessible) {
+ return -1;
+ }
+ }
+
+ // Access is granted.
+ $headers = file_get_content_headers($file);
+ return $headers;
+}
+
+/**
* Finds all files referenced (data-entity-uuid) by formatted text fields.
*
* @param EntityInterface $entity
diff --git a/core/modules/editor/src/Tests/EditorPrivateFileReferenceFilterTest.php b/core/modules/editor/src/Tests/EditorPrivateFileReferenceFilterTest.php
new file mode 100644
index 0000000..5f689b3
--- /dev/null
+++ b/core/modules/editor/src/Tests/EditorPrivateFileReferenceFilterTest.php
@@ -0,0 +1,100 @@
+<?php
+
+namespace Drupal\editor\Tests;
+
+use Drupal\file\Entity\File;
+use Drupal\Tests\BrowserTestBase;
+use Drupal\user\Entity\Role;
+use Drupal\user\RoleInterface;
+
+/**
+ * Tests Editor module's file reference filter with private files.
+ *
+ * @group editor
+ */
+class EditorPrivateFileReferenceFilterTest extends BrowserTestBase {
+
+ /**
+ * Modules to enable.
+ *
+ * @var array
+ */
+ public static $modules = [
+ // Needed for the config: this is the only module in core that utilizes the
+ // functionality in editor.module to be tested, and depends on that.
+ 'ckeditor',
+ // Depends on filter.module (indirectly).
+ 'node',
+ // Pulls in the config we're using during testing which create a text format
+ // - with the filter_html_image_secure filter DISABLED,
+ // - with the editor set to CKEditor,
+ // - with drupalimage.image_upload.scheme set to 'private',
+ // - with drupalimage.image_upload.directory set to ''.
+ 'editor_private_test',
+ ];
+
+ /**
+ * Tests the editor file reference filter with private files.
+ */
+ function testEditorPrivateFileReferenceFilter() {
+ $author = $this->drupalCreateUser();
+ $this->drupalLogin($author);
+
+ // Create a content type with a body field.
+ $this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
+
+ // Create a file in the 'private:// ' stream.
+ $filename = 'test.png';
+ $src = '/system/files/' . $filename;
+ /** @var \Drupal\file\FileInterface $file */
+ $file = File::create([
+ 'uri' => 'private://' . $filename,
+ ]);
+ $file->setTemporary();
+ $file->setOwner($author);
+ // Create the file itself.
+ file_put_contents($file->getFileUri(), $this->randomString());
+ $file->save();
+
+ // The image should be visible for its author.
+ $this->drupalGet($src);
+ $this->assertSession()->statusCodeEquals(200);
+ // The not-yet-permanent image should NOT be visible for anonymous.
+ $this->drupalLogout();
+ $this->drupalGet($src);
+ $this->assertSession()->statusCodeEquals(403);
+
+ // Resave the file to be permanent.
+ $file->setPermanent();
+ $file->save();
+
+ // Create a node with its body field properly pointing to the just-created
+ // file.
+ $node = $this->drupalCreateNode([
+ 'type' => 'page',
+ 'body' => [
+ 'value' => '<img alt="alt" data-entity-type="file" data-entity-uuid="' . $file->uuid() . '" src="' . $src . '" />',
+ 'format' => 'private_images',
+ ],
+ 'uid' => $author->id(),
+ ]);
+
+ // Do the actual test. The image should be visible for anonymous users,
+ // because they can view the referencing entity.
+ $this->drupalGet($node->toUrl());
+ $this->assertSession()->statusCodeEquals(200);
+ $this->drupalGet($src);
+ $this->assertSession()->statusCodeEquals(200);
+
+ // Disallow anonymous users to view the entity, which then should also
+ // disallow them to view the image.
+ Role::load(RoleInterface::ANONYMOUS_ID)
+ ->revokePermission('access content')
+ ->save();
+ $this->drupalGet($node->toUrl());
+ $this->assertSession()->statusCodeEquals(403);
+ $this->drupalGet($src);
+ $this->assertSession()->statusCodeEquals(403);
+ }
+
+}
diff --git a/core/modules/editor/tests/editor_private_test/config/install/editor.editor.private_images.yml b/core/modules/editor/tests/editor_private_test/config/install/editor.editor.private_images.yml
new file mode 100644
index 0000000..2de126a
--- /dev/null
+++ b/core/modules/editor/tests/editor_private_test/config/install/editor.editor.private_images.yml
@@ -0,0 +1,34 @@
+format: private_images
+status: true
+langcode: en
+editor: ckeditor
+settings:
+ toolbar:
+ rows:
+ -
+ -
+ name: Media
+ items:
+ - DrupalImage
+ -
+ name: Tools
+ items:
+ - Source
+ plugins:
+ language:
+ language_list: un
+ stylescombo:
+ styles: ''
+image_upload:
+ status: true
+ scheme: private
+ directory: ''
+ max_size: ''
+ max_dimensions:
+ width: null
+ height: null
+dependencies:
+ config:
+ - filter.format.private_images
+ module:
+ - ckeditor
diff --git a/core/modules/editor/tests/editor_private_test/config/install/filter.format.private_images.yml b/core/modules/editor/tests/editor_private_test/config/install/filter.format.private_images.yml
new file mode 100644
index 0000000..261bd90
--- /dev/null
+++ b/core/modules/editor/tests/editor_private_test/config/install/filter.format.private_images.yml
@@ -0,0 +1,23 @@
+format: private_images
+name: 'Private images'
+status: true
+langcode: en
+filters:
+ editor_file_reference:
+ id: editor_file_reference
+ provider: editor
+ status: true
+ weight: 0
+ settings: { }
+ filter_html:
+ id: filter_html
+ provider: filter
+ status: false
+ weight: -10
+ settings:
+ allowed_html: '<img src alt data-entity-type data-entity-uuid>'
+ filter_html_help: true
+ filter_html_nofollow: false
+dependencies:
+ module:
+ - editor
diff --git a/core/modules/editor/tests/editor_private_test/editor_private_test.info.yml b/core/modules/editor/tests/editor_private_test/editor_private_test.info.yml
new file mode 100644
index 0000000..474fda6
--- /dev/null
+++ b/core/modules/editor/tests/editor_private_test/editor_private_test.info.yml
@@ -0,0 +1,9 @@
+name: 'Text Editor Private test'
+type: module
+description: 'Support module for the Text Editor Private module tests.'
+core: 8.x
+package: Testing
+version: VERSION
+dependencies:
+ - filter
+ - ckeditor