Issue #2744197 by Boobaa, Wim Leers: Proper private file support for images uploaded via EditorImageDialog

8.3.x
Alex Pott 2016-09-02 16:13:11 +01:00
parent ed8ceb7967
commit f2ed11ab58
5 changed files with 189 additions and 0 deletions

View File

@ -467,6 +467,60 @@ 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.
// Access is granted.
$headers = file_get_content_headers($file);
return $headers;
}
/**
* Finds all files referenced (data-entity-uuid) by formatted text fields.
*

View File

@ -0,0 +1,69 @@
<?php
namespace Drupal\editor\Tests;
use Drupal\file\Entity\File;
use Drupal\simpletest\WebTestBase;
/**
* Tests Editor module's file reference filter with private files.
*
* @group editor
*/
class EditorPrivateFileReferenceFilterTest extends WebTestBase {
/**
* 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() {
// 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;
$file = File::create([
'uri' => 'private://' . $filename,
'status' => FILE_STATUS_PERMANENT,
]);
// Create the file itself.
file_put_contents($file->getFileUri(), $this->randomString());
$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',
],
]);
$this->drupalGet('/node/' . $node->id());
// Do the actual test. The image should be visible for anonymous.
$this->drupalGet($src);
$this->assertResponse(200, 'Image is downloadable as anonymous.');
}
}

View File

@ -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

View File

@ -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

View File

@ -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