CommentFileSizeAuthor
#54 interdiff.txt718 byteswim leers
#54 drupal-private_files_in_editorimagedialog-2744197-54.patch8.23 KBwim leers
#35 interdiff-2744197-30-34.txt2.44 KBboobaa
#35 drupal-private_files_in_editorimagedialog-2744197-34.patch7.93 KBboobaa
#34 drupal-private_files_in_editorimagedialog-2744197-34-test-only.patch5.31 KBboobaa
#30 interdiff-2744197-23-30.txt6.65 KBboobaa
#30 drupal-private_files_in_editorimagedialog-2744197-30-test-only.patch4.8 KBboobaa
#30 drupal-private_files_in_editorimagedialog-2744197-30.patch7.43 KBboobaa
#28 interdiff-2744197-6-23.txt15.89 KBboobaa
#28 interdiff-2744197-8-23.txt17.25 KBboobaa
#28 interdiff-2744197-17-23.txt3.57 KBboobaa
#28 interdiff-2744197-20-23.txt3.78 KBboobaa
#28 interdiff-2744197-17-20.txt3.5 KBboobaa
#28 interdiff-2744197-8-17.txt16.88 KBboobaa
#23 drupal-private_files_in_editorimagedialog-2744197-23-test-only.patch4.81 KBboobaa
#23 drupal-private_files_in_editorimagedialog-2744197-23.patch10.82 KBboobaa
#20 drupal-private_files_in_editorimagedialog-2744197-20-test-only.patch4.63 KBboobaa
#20 drupal-private_files_in_editorimagedialog-2744197-20.patch10.64 KBboobaa
#17 drupal-private_files_in_editorimagedialog-2744197-17-test-only.patch4.24 KBboobaa
#17 drupal-private_files_in_editorimagedialog-2744197-17.patch10.24 KBboobaa
#8 interdiff-2744197-6-8.txt3.03 KBboobaa
#8 drupal-private_files_in_editorimagedialog-2744197-8.patch8.67 KBboobaa
#8 drupal-private_files_in_editorimagedialog-2744197-8-test-only.patch2.66 KBboobaa
#6 drupal-private_files_in_editorimagedialog-2744197-6.patch6.08 KBboobaa
#69 drupal-private_files_in_editorimagedialog-2744197-69.patch8.82 KBwim leers
#70 interdiff.txt1.58 KBwim leers
#71 interdiff.txt1.5 KBwim leers
#81 drupal-private_files_in_editorimagedialog-2744197-81.patch825 bytesboobaa
#71 drupal-private_files_in_editorimagedialog-2744197-71.patch9.64 KBwim leers

Comments

Boobaa created an issue. See original summary.

cilefen’s picture

This could perhaps be triaged up to major priority.

wim leers’s picture

Title: Private files uploaded via editor are unconditionally inaccessible » Private file support for images uploaded via EditorImageDialog
Version: 8.1.x-dev » 8.2.x-dev
Category: Bug report » Feature request

We simply never added support for private files. You're the first to report/request this AFAIK, which explains how it went unnoticed all this time. Which is why this is IMO a feature request, not a bug.

Related reading: https://www.drupal.org/documentation/modules/file#content-accessing-priv...

boobaa’s picture

Title: Private file support for images uploaded via EditorImageDialog » Proper private file support for images uploaded via EditorImageDialog
Assigned: Unassigned » boobaa
Category: Feature request » Bug report
Issue summary: View changes

With great respect, it was your very own patch in #1932652-100: Add image uploading to WYSIWYGs through editor.module which added this feature stating that "(a)ny visible, writable wrapper can potentially be used for uploads". Later changes to this file (including, but not limited to, #2028109: Convert hook_stream_wrappers() to tagged services.) haven't really changed how this feature works in this regard: the private file system stayed there as an option.

I think we have two ways ahead now: either remove the possibility to upload files to the private file system via EditorImageDialog, or provide a way to allow downloading such files. As the first option would be a regression by now, I'm clearly determined pursuing the second one.

wim leers’s picture

Issue tags: +Needs tests

#4: where exactly did I say that? I don't see it in comment #100 that you linked to. I'd love to read my own thinking about this from back then :)

You assigned this to yourself, so it sounds like you want to take this on. Let's start with writing a test that shows you can upload files while using the private file system, but you can't download them. That would then make it more clear what else we need.

boobaa’s picture

Assigned: boobaa » Unassigned
Issue summary: View changes
StatusFileSize
new6.08 KB

Well, that's quoted from http://cgit.drupalcode.org/drupal/tree/core/modules/editor/editor.admin.... and it's still there, untouched ever since it was introduced. :)

Attached is a first pass for a patch, without any test for this time. FTR, here are (some) steps for reproduction.

  1. Install standard.profile.
  2. Enable private file system by specifying it in settings.php (as usual, like $settings['file_private_path'] = '/tmp/private';).
  3. Visit http://example.com/admin/config/content/formats/manage/basic_html and
    1. disable "Restrict images to this site",
    2. set "File storage" to "Private local files served by Drupal.",
    3. click "Save configuration".
  4. Visit http://example.com/node/add/article and
    1. type anything as "Title",
    2. click on the "Image" button,
    3. in the popup, click the "Image" file upload widget,
    4. select any image to upload it,
    5. type anything as "Alternative text",
    6. click "Save" in the dialog.
  5. As the first test, the image should be visible in the editor.
  6. Click "Save and publish".
  7. As the second test, the image should be visible on the node view page.
  8. Logout.
  9. As the third test, the image should be visible on the node view page (for anonymous users).

Without the patch, the first test passes while the second and the third fail. With the patch, all tests pass.

As these steps include some client-side magic, I'll have to learn how to write tests for that. I'm unassigning this to allow anybody beating me on this. OTOH, I don't know how to test this in 8.1.x, so when everything seems to be OK maybe we should just wait for October's 8.2.0?

(One might argue why should "Restrict images to this site" be disabled, but that's a different issue. Basically _filter_html_image_secure_process() improperly recognizes URIs of private files as non-local.)

wim leers’s picture

Aha! Thanks for pointing that out :) (I was looking in the issue comments, that's why I was not able to find it.)

Wow, great first pass! :) Thanks so much!

The steps indeed include JS if you do it manually. But you can just as well do it without JS. You can use File::create([…])->save() to "upload" a file to the private:// stream. Next, you can interact with the form without JS like this test is already doing: \Drupal\Tests\editor\Kernel\EditorImageDialogTest::testEditorImageDialog().

RE: Basically _filter_html_image_secure_process() improperly recognizes URIs of private files as non-local. — yep, that's a known bug. +1 for excluding that from the test, so that it doesn't interfere.

boobaa’s picture

Status: Active » Needs review
Issue tags: -Needs tests
StatusFileSize
new2.66 KB
new8.67 KB
new3.03 KB

It turned out there's no real need to do the non-JS magic like \Drupal\Tests\editor\Kernel\EditorImageDialogTest::testEditorImageDialog(). If the body field's value is forged just like CKEditor would do it, that does the trick for us. And hey, we're not testing CKEditor here: everything we actually want to test has nothing to do with JS.

OTOH, the test file itself needs to be created with that file_put_contents() call: without that, we'd get 404 with or without the actual fix, which is somewhat expected. :)

Status: Needs review » Needs work

The last submitted patch, 8: drupal-private_files_in_editorimagedialog-2744197-8.patch, failed testing.

boobaa’s picture

Status: Needs work » Needs review

Testbot hiccup?

wim leers’s picture

Requested new tests for both — let's see :)

Status: Needs review » Needs work

The last submitted patch, 8: drupal-private_files_in_editorimagedialog-2744197-8.patch, failed testing.

wim leers’s picture

The failure root cause:

PHP Fatal error:  Class 'Drupal\editor\Tests\EditorPrivateFileReferenceFilterTest' not found in /var/www/html/core/scripts/run-tests.sh on line 745
  1. +++ b/core/modules/editor/src/Tests/EditorPrivateFileReferenceFilterTest.php
    @@ -0,0 +1,70 @@
    +namespace Drupal\Tests\editor\Kernel;
    

    This is why the patch always results in a fail: the namespace is wrong.

  2. +++ b/core/modules/editor/src/Tests/EditorPrivateFileReferenceFilterTest.php
    @@ -0,0 +1,70 @@
    +  // Install standard.profile.
    
    /**
     * {inheritdoc}
     */
    

    Also… let's please not do this — installing the "Standard" install profile is very very time consuming. We should only install the necessary modules, and then manually create the necessary configuration.

  3. +++ b/core/modules/editor/src/Tests/EditorPrivateFileReferenceFilterTest.php
    @@ -0,0 +1,70 @@
    +    // Visit /admin/config/content/formats/manage/basic_html.
    +    $edit = [
    +      // Disable "Restrict images to this site".
    +      'filters[filter_html_image_secure][status]' => FALSE,
    +      // Set "File storage" to "Private local files served by Drupal.".
    +      'editor[settings][plugins][drupalimage][image_upload][scheme]' => 'private',
    +      // Remove 'inline-images'.
    +      'editor[settings][plugins][drupalimage][image_upload][directory]' => '',
    +    ];
    +    // Click "Save configuration".
    +    $this->drupalPostForm('/admin/config/content/formats/manage/basic_html', $edit, t('Save configuration'));
    

    This works, but is very slow. There's no reason for us to do this via the UI. We can just create the necessary configuration directly.

  4. In fact, why even use WebTestBase? It's very slow. Let's use KernelTestBase. In fact … I think you can just add a new test method to \Drupal\Tests\editor\Kernel\EditorFileReferenceFilterTest :)
boobaa’s picture

Thanks for these hints, they all make sense, but it looks like I can't file_put_contents('private://llama.jpg', $this->randomMachineName()); in a KernelTestBase-derived class. I couldn't even find an already-existing example for this. Could you please give some ideas how to create a file in the private:// scheme in a KernelTestBase-derived class? Without such a file, I don't see any way to actually do any tests.

boobaa’s picture

Having a working, but slow web test is still better than not having a fast kernel test which works, I think. I couldn't find a way to create a file in a kernel test in the private:// scheme, and I couldn't find an example in core which does this.

Attached is still a web test, but this time with the testing.profile, and pulling in the config instead of creating it via the UI. IOW, all concerns from #15 have been addressed by this but the last one, for the above reasons.

The last submitted patch, 17: drupal-private_files_in_editorimagedialog-2744197-17.patch, failed testing.

Status: Needs review » Needs work
boobaa’s picture

Okay, let's not add config for editor tests to filter tests. :)

The last submitted patch, 20: drupal-private_files_in_editorimagedialog-2744197-20.patch, failed testing.

Status: Needs review » Needs work
boobaa’s picture

Okay, then let's introduce a dedicated test module to avoid messing with other already-existing things.

The last submitted patch, 23: drupal-private_files_in_editorimagedialog-2744197-23.patch, failed testing.

Status: Needs review » Needs work

wim leers’s picture

Great progress here! :) However, could you provide interdiffs from now on? https://www.drupal.org/documentation/git/interdiff

boobaa’s picture

Status: Needs work » Needs review
StatusFileSize
new16.88 KB
new3.5 KB
new3.78 KB
new3.57 KB
new17.25 KB
new15.89 KB

Okay, will do – here are some interdiffs for the already-existing patches for your review convenience. Setting the issue to "Needs review" as the only failing patch is the one that should fail (tho it has an unrelated fail as well, for some unknown reason). Hoping this won't trigger the testbots.

wim leers’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/editor/editor.module
    @@ -468,6 +470,131 @@ function _editor_delete_file_usage(array $uuids, EntityInterface $entity, $count
    +  $files = entity_load_multiple_by_properties('file', array('uri' => $uri));
    

    Nit: s/array()/[]/

    More importantly: let's not call a deprecated API function.

  2. +++ b/core/modules/editor/editor.module
    @@ -468,6 +470,131 @@ function _editor_delete_file_usage(array $uuids, EntityInterface $entity, $count
    +      // Since some database servers sometimes use a case-insensitive comparison
    +      // by default, double check that the filename is an exact match.
    

    Woah!

  3. +++ b/core/modules/editor/editor.module
    @@ -468,6 +470,131 @@ function _editor_delete_file_usage(array $uuids, EntityInterface $entity, $count
    +  // Temporary files are handled by file_file_download(), so nothing to do here
    +  // about them.
    

    This comment seems to be dangling. Does this belong with the !isset($file)?

  4. +++ b/core/modules/editor/editor.module
    @@ -468,6 +470,131 @@ function _editor_delete_file_usage(array $uuids, EntityInterface $entity, $count
    +  // Find out which (if any) fields of this type contain the file.
    +  $references = editor_get_file_references($file, EntityStorageInterface::FIELD_LOAD_CURRENT);
    

    "which fields" vs "$references" -> very confusing.

  5. +++ b/core/modules/editor/editor.module
    @@ -468,6 +470,131 @@ function _editor_delete_file_usage(array $uuids, EntityInterface $entity, $count
    +  // an image preview on a node/add form) in which case, allow download by the
    

    node/add form -> node creation form

  6. +++ b/core/modules/editor/editor.module
    @@ -468,6 +470,131 @@ function _editor_delete_file_usage(array $uuids, EntityInterface $entity, $count
    +  // 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() again. This latter one would allow
    +  // downloading files only handled by the file.module, which is exactly not the
    +  // case right here.
    +//  if (!($return = $file->access('download', NULL, TRUE))) {
    +//    return -1;
    +//  }
    

    This is very confusing.

    I think that:

    1. removing the "again"
    2. removing the commented out code

    would help a lot

  7. +++ b/core/modules/editor/editor.module
    @@ -468,6 +470,131 @@ function _editor_delete_file_usage(array $uuids, EntityInterface $entity, $count
    + * @param int $age
    + *   (optional) A constant that specifies which references to count. Use
    + *   EntityStorageInterface::FIELD_LOAD_REVISION (the default) to retrieve all
    + *   references within all revisions or
    + *   EntityStorageInterface::FIELD_LOAD_CURRENT to retrieve references only in
    + *   the current revisions of all entities that have references to this file.
    

    This extra complexity only exists here because this was based off of file_get_file_references().

    This is only ever called with FIELD_LOAD_CURRENT. So let's only support that.

  8. +++ b/core/modules/editor/editor.module
    @@ -468,6 +470,131 @@ function _editor_delete_file_usage(array $uuids, EntityInterface $entity, $count
    +function editor_get_file_references(FileInterface $file, $age = EntityStorageInterface::FIELD_LOAD_REVISION) {
    

    Let's prefix this with an underscore, to signal this is private.

    Let's also add @internal.

    Both of those things ensure we can change this function's implementation and interface over time. This is NOT a public API.

  9. +++ b/core/modules/editor/editor.module
    @@ -468,6 +470,131 @@ function _editor_delete_file_usage(array $uuids, EntityInterface $entity, $count
    +    $usage_list = \Drupal::service('file.usage')->listUsage($file);
    +    $file_usage_list = isset($usage_list['editor']) ? $usage_list['editor'] : array();
    

    In fact, why do we need to get all references? We never use it. All we need to know is whether the file that is being downloaded is in fact used, and used by editor module somewhere.

    The reason file module's hook_file_download() calls file_get_file_references() is because it provides the data it needs. It provides much more than is necessary, but it also contains what is necessary. So that's what it uses. But in fact that module could also just look at the file.usage service's entries for the file module.

    This entire function with all its complexity could then go away.

    And instead we'd just have

    $usage_list = \Drupal::service('file.usage')->listUsage($file);
    $is_accessible = !empty($usage_list['editor']);
    

    in editor_file_download().

    *So* much simpler.

    Or what am I missing?

boobaa’s picture

Thanks for all your precious input!

I've addressed everything mentioned in #29. Some notes:
For 2nd bullet point: Indeed. (And hey, this is present in the File module, too.)
For 3rd bullet point: This comment describes why we aren't doing any temporary-specific wizardry here (and where's the place to look for such).
For 9th bullet point: Yes, you're absolutely correct – and this made the code easier and the patch much smaller.

OTOH, I wonder what should be next if this patch gets committed. Most of it has been borrowed from the File module, and most of the improvement ideas listed here would apply there as well. (If I'm not mistaken, even the revision-related parts.) Probably a followup issue for cleaning up File module in the same sense?

Status: Needs review » Needs work
wim leers’s picture

#30: awesome!

RE: cleaning up File module in the same way: we can't do that; there could be contrib modules relying on this code, since the code there is not declared internal, hence it's a public API.

P.S.: upload the test-only patch first, then testbot won't mark this issue needs work :) Silly & annoying small thing you have to know when using d.o.


This now pretty much looks ready, but just needs a bit more test coverage.

  1. +++ b/core/modules/editor/src/Tests/EditorPrivateFileReferenceFilterTest.php
    @@ -0,0 +1,70 @@
    +class EditorPrivateFileReferenceFilterTest extends WebTestBase {
    

    We shouldn't add new WebTestBase tests. Please use BrowserTestBase instead – see #2469723: New PHPUnit based classes added for testing: BrowserTestBase and JavascriptTestBase

  2. +++ b/core/modules/editor/src/Tests/EditorPrivateFileReferenceFilterTest.php
    @@ -0,0 +1,70 @@
    +    // Do the actual test. The image should be visible for anonymous.
    +    $this->drupalGet($src);
    +    $this->assertResponse(200, 'Image is downloadable as anonymous.');
    

    This is only testing one case — the most common case.

    It's not yet testing the case of a file not yet being permanent, yet uploaded by the current user.


Thanks for your super awesome work here!

wim leers’s picture

Version: 8.2.x-dev » 8.1.x-dev

Oh and since this is a bugfix, this can go in 8.1!

boobaa’s picture

Status: Needs work » Needs review
StatusFileSize
new5.31 KB
  • s/WebTestBase/BrowserTestBase/g
  • Increased test coverage
boobaa’s picture

wim leers’s picture

Status: Needs review » Reviewed & tested by the community

This looks great! Thanks a lot for your awesome work here! :)

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 35: drupal-private_files_in_editorimagedialog-2744197-34.patch, failed testing.

boobaa’s picture

Status: Needs work » Reviewed & tested by the community

Test ran fine after testbot hiccup.

gábor hojtsy’s picture

Issue tags: +Media Initiative
gábor hojtsy’s picture

Issue tags: -Media Initiative +D8Media

Sorry I did not use the proper tag for D8 Media.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 35: drupal-private_files_in_editorimagedialog-2744197-34.patch, failed testing.

gábor hojtsy’s picture

Status: Needs work » Reviewed & tested by the community

Looks like a retest of this passed fine above :)

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 35: drupal-private_files_in_editorimagedialog-2744197-34.patch, failed testing.

wim leers’s picture

Status: Needs work » Reviewed & tested by the community

Retesting again. Random fail again.

duaelfr’s picture

+1 RTBC
Tested with Core image plugin and with Editor File contrib one.
Thank you all for your work :)

PS: private images are still considered as external images by the "Restrict images to this site" filter with this patch. Did someone open a followup issue?

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 35: drupal-private_files_in_editorimagedialog-2744197-34.patch, failed testing.

wim leers’s picture

Random fail in ConfigImportAllTest.

wim leers’s picture

Status: Needs work » Reviewed & tested by the community
boobaa’s picture

Issue summary: View changes

@DuaelFr in #46: Just posted the followup issue: #2772617: Handle files uploaded to the private storage as internal.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/modules/editor/src/Tests/EditorPrivateFileReferenceFilterTest.php
    @@ -0,0 +1,87 @@
    +    $this->drupalGet('/node/' . $node->id());
    

    What's the purpose of this get? Nothing is asserted afterwards? If it is necessary for the test it needs a comment.

  2. +++ b/core/modules/editor/editor.module
    @@ -468,6 +470,60 @@ function _editor_delete_file_usage(array $uuids, EntityInterface $entity, $count
    +  // 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())) {
    

    This logic in the if does not seem to match the comment.

    Isn't the matching logic:

    if (empty($usage_list['editor']) && (!$file->isPermanent() && $file->getOwnerId() != \Drupal::currentUser()->id())) { 
    

    If we can make this simpler by breaking it up into separate ifs i'd encourage that.

wim leers’s picture

  1. Nice nitpicky catch! I also can't tell what the purpose of that is. AFAICT it can be removed. I bet it was there for debugging.
  2. We have the exact same thing in file_file_download():
      // 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/add form) in which case, allow download by the
      // file's owner.
      if (empty($references) && ($file->isPermanent() || $file->getOwnerId() != \Drupal::currentUser()->id())) {
        return;
      }
    

Still NW for point 1.

alexpott’s picture

Oh ok re #51.2 I'm always wary of security logic in ifs that requires a big comments and quite a bit of think to understand how it works.

wim leers’s picture

Status: Needs work » Reviewed & tested by the community
StatusFileSize
new718 bytes
new8.23 KB

Just fixed point 1. Back to RTBC, because it's just a nit. Passed locally.

The last submitted patch, 54: drupal-private_files_in_editorimagedialog-2744197-54.patch, failed testing.

The last submitted patch, 54: drupal-private_files_in_editorimagedialog-2744197-54.patch, failed testing.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/modules/editor/editor.module
@@ -467,6 +469,60 @@ function _editor_delete_file_usage(array $uuids, EntityInterface $entity, $count
+  // Temporary files are handled by file_file_download(), so nothing to do here
+  // about them.
+  // @see file_file_download()
...
+  // 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;
+  }

The first comment seems to be in direct opposition to the later code. If there's nothing to do here about them why do we have... ($file->isPermanent() || $file->getOwnerId() != \Drupal::currentUser()->id()) - if file_file_download() handles this - is this necessary?

wim leers’s picture

Status: Needs work » Reviewed & tested by the community

The difference is that the temporary file handling (the first section) is not module-specific. The second section (the "stop processing" bit) is module-specific.


  // Temporary files are handled by file_file_download(), so nothing to do here
  // about them.
  // @see file_file_download()

refers to:

  // Find out if a temporary file is still used in the system.
  if ($file->isTemporary()) {
    $usage = \Drupal::service('file.usage')->listUsage($file);
    if (empty($usage) && $file->getOwnerId() != \Drupal::currentUser()->id()) {
      // Deny access to temporary files without usage that are not owned by the
      // same user. This prevents the security issue that a private file that
      // was protected by field permissions becomes available after its usage
      // was removed and before it is actually deleted from the file system.
      // Modules that depend on this behavior should make the file permanent
      // instead.
      return -1;
    }
  }

Which as you can tell does not list anything module-specific.

The second section however does module-specific things: file.module's file_file_download() calls file_get_file_references(), which does:

$file_usage_list = isset($usage_list['file']) ? $usage_list['file'] : array();

Note the key 'file', this corresponds to file module. This means this is only granting download access to files owned by the file module.

The corresponding second section in editor module's editor_file_download() checks not the 'file' key, but the 'editor' key: it grants download access to files owned by the editor module.

  if (empty($usage_list['editor']) && ($file->getOwnerId() != \Drupal::currentUser()->id())) {
    return;
  }

  • alexpott committed f2ed11a on 8.3.x
    Issue #2744197 by Boobaa, Wim Leers: Proper private file support for...

  • alexpott committed 6e46381 on 8.2.x
    Issue #2744197 by Boobaa, Wim Leers: Proper private file support for...
alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed and pushed f2ed11a to 8.3.x and 6e46381 to 8.2.x and 46664f6 to 8.1.x. Thanks!

Fixes on commit:

diff --git a/core/modules/editor/editor.module b/core/modules/editor/editor.module
index fe19bd1..2ecdb8a 100644
--- a/core/modules/editor/editor.module
+++ b/core/modules/editor/editor.module
@@ -16,8 +16,6 @@
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\filter\FilterFormatInterface;
 use Drupal\filter\Plugin\FilterInterface;
-use Drupal\Core\Entity\EntityStorageInterface;
-use Drupal\file\FileInterface;
 
 /**
  * Implements hook_help().
diff --git a/core/modules/editor/src/Tests/EditorPrivateFileReferenceFilterTest.php b/core/modules/editor/src/Tests/EditorPrivateFileReferenceFilterTest.php
index 3fe05ee..b5ff6db 100644
--- a/core/modules/editor/src/Tests/EditorPrivateFileReferenceFilterTest.php
+++ b/core/modules/editor/src/Tests/EditorPrivateFileReferenceFilterTest.php
@@ -3,7 +3,6 @@
 namespace Drupal\editor\Tests;
 
 use Drupal\file\Entity\File;
-use Drupal\file\FileInterface;
 use Drupal\simpletest\WebTestBase;
 
 /**
@@ -42,7 +41,7 @@ function testEditorPrivateFileReferenceFilter() {
     // Create a file in the 'private:// ' stream.
     $filename = 'test.png';
     $src = '/system/files/' . $filename;
-    /** @var FileInterface $file */
+
     $file = File::create([
       'uri' => 'private://' . $filename,
       'status' => FILE_STATUS_PERMANENT,

  • alexpott committed 46664f6 on 8.1.x
    Issue #2744197 by Boobaa, Wim Leers: Proper private file support for...
alexpott’s picture

Status: Reviewed & tested by the community » Fixed

  • alexpott committed 8e6bae2 on 8.3.x
    Revert "Issue #2744197 by Boobaa, Wim Leers: Proper private file support...

  • alexpott committed c606ed5 on 8.2.x
    Revert "Issue #2744197 by Boobaa, Wim Leers: Proper private file support...

  • alexpott committed ecb1f7a on 8.1.x
    Revert "Issue #2744197 by Boobaa, Wim Leers: Proper private file support...
alexpott’s picture

Status: Fixed » Needs review

I've reverted this change because after commit I suddenly got worried that this does not implement the correct security behaviour. We if we have a private file referenced in a node should we grant people access to it?

+++ b/core/modules/editor/src/Tests/EditorPrivateFileReferenceFilterTest.php
@@ -0,0 +1,86 @@
+    // Resave the file to be permanent.
+    $file->setPermanent();
+    $file->save();
+

If this is the correct solution. We should test that the anonymous user still does not have access after doing this.

alexpott’s picture

Also this grants access to the file regardless of whether the user has access to the particular entity afaics.

wim leers’s picture

#67:

+++ b/core/modules/editor/src/Tests/EditorPrivateFileReferenceFilterTest.php
@@ -0,0 +1,86 @@
+    // Resave the file to be permanent.
+    $file->setPermanent();
+    $file->save();
+

If this is the correct solution. We should test that the anonymous user still does not have access after doing this.

That code is simulating a call to \Drupal\file\FileUsage\FileUsageBase::add(), which is called by _editor_record_file_usage(), which in turn is called by either editor_entity_insert() (when the image is inserted when the entity is first created) or by editor_entity_update() (when the image is inserted when the entity is updated).

So did you quote the wrong code?

In any case, you're right that we need that test.


#68:

Also this grants access to the file regardless of whether the user has access to the particular entity afaics.

Ugh. You're totally right. And that's what the addition in #7 proved.


This is the problem:

  // 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.

This means we're not at all checking whether the user can access one of the editor-referenced entities before it grants access. I should've questioned it more; this patch has contained that since the very beginning. Sorry for missing that, Alex. The tests seemed right and passed, but thankfully you noticed just in time that this should be testing one more thing.

Also: this entire API really needs an overhaul in Drupal 9, because it's extremely brittle :/

This patch just expands the test coverage. This should fail.

wim leers’s picture

StatusFileSize
new1.58 KB

Forgot the interdiff.

wim leers’s picture

Status: Needs review » Reviewed & tested by the community
StatusFileSize
new9.64 KB
new1.5 KB

And here's the fix. It's 80% copied from \Drupal\file\FileAccessControlHandler::checkAccess(), 10% copied from file_file_download(). Sadly we can't reuse any of the existing code without enormous refactoring, so this feels simpler then. The logic is trivial.

Given that the logic is trivial and 90% copied, I'm going to self-RTBC, so Alex takes another look at it.

The last submitted patch, 69: drupal-private_files_in_editorimagedialog-2744197-69.patch, failed testing.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

alexpott’s picture

Status: Reviewed & tested by the community » Patch (to be ported)

Committed 05d832e and pushed to 8.3.x. Thanks!

Leaving at patch to be ported for eventual cherry-pick to 8.2.x once 8.2.0 is released.

diff --git a/core/modules/editor/editor.module b/core/modules/editor/editor.module
index 6c34730..0101779 100644
--- a/core/modules/editor/editor.module
+++ b/core/modules/editor/editor.module
@@ -16,8 +16,6 @@
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\filter\FilterFormatInterface;
 use Drupal\filter\Plugin\FilterInterface;
-use Drupal\Core\Entity\EntityStorageInterface;
-use Drupal\file\FileInterface;
 
 /**
  * Implements hook_help().
diff --git a/core/modules/editor/src/Tests/EditorPrivateFileReferenceFilterTest.php b/core/modules/editor/src/Tests/EditorPrivateFileReferenceFilterTest.php
index 84ef4e0..5f689b3 100644
--- a/core/modules/editor/src/Tests/EditorPrivateFileReferenceFilterTest.php
+++ b/core/modules/editor/src/Tests/EditorPrivateFileReferenceFilterTest.php
@@ -3,7 +3,6 @@
 namespace Drupal\editor\Tests;
 
 use Drupal\file\Entity\File;
-use Drupal\file\FileInterface;
 use Drupal\Tests\BrowserTestBase;
 use Drupal\user\Entity\Role;
 use Drupal\user\RoleInterface;
@@ -47,7 +46,7 @@ function testEditorPrivateFileReferenceFilter() {
     // Create a file in the 'private:// ' stream.
     $filename = 'test.png';
     $src = '/system/files/' . $filename;
-    /** @var FileInterface $file */
+    /** @var \Drupal\file\FileInterface $file */
     $file = File::create([
       'uri' => 'private://' . $filename,
     ]);

Unused uses fixed on commit.

  • alexpott committed 05d832e on 8.3.x
    Issue #2744197 by Boobaa, Wim Leers, alexpott: Proper private file...

  • alexpott committed e4aec7c on 8.2.x
    Issue #2744197 by Boobaa, Wim Leers, alexpott: Proper private file...
alexpott’s picture

Status: Patch (to be ported) » Fixed

Committed e4aec7c and pushed to 8.2.x. Thanks!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

mabuweb’s picture

Issue summary: View changes

I've applied the patch form #71 to my Drupal 8.2.1 installation. It does'nt work for me, all inline images still get an access denied.
I think there is a problem with the editor_file_download($uri) hook.
The $usage_list contains an array like this: $references[$usage->module][$usage->type][$usage->id] = $usage->count;
The line
$referencing_entities = entity_load_multiple($entity_type, $entity_ids);
expects $entity_ids to be an array of id's, but it isn't, its an associative array like this: [id1=>usage_count1, id2=>usage_count2, ..., idn=>usage_countn,]
A simple way to fix this is to apply array_keys($entity_ids):
$referencing_entities = entity_load_multiple($entity_type, array_keys($entity_ids));
Applying this fix the patch works for me.

wim leers’s picture

@mabuweb: Next time, please create a new issue. Nobody saw your comment because the issue was already closed. Somebody just reported the same bug, and included a patch to fix it: #2831392: Text Editor module uses file.usage service incorrectly.

boobaa’s picture

As #2831392: Text Editor module uses file.usage service incorrectly is 403 for me, I'm attaching what @mabuweb suggested in #79 here as a patch.

boobaa’s picture

maxilein’s picture

@Wim Leers #80

Is your link 403 on purpose? I cannot access it - just like #81

quicksketch’s picture

@maxilein: That issue is a 403 because it has been unpublished. That issue revealed a security vulnerability, and so it was unpublished in accordance with the Drupal security policies. It was fixed just today with the release of Drupal 8.2.7. See https://www.drupal.org/SA-2017-001. So for now if you need that fix the easiest thing to do is to update to the latest version of Drupal. You could pull the fix out of the Git history of course, but it's policy not to unnecessarily publicize the exact code that contains vulnerabilities.

maxilein’s picture

@quickscetch
Thank you for the clarification. Makes sense now!