Revised issue motivation
We would like to allow for sending redirect responses, instead of direct file responses, when responding to requests handled by the Media Entity Download module. This change would help from multiple perspectives by:
- improving performance regarding external and Drupal-based caching systems;
- allowing for external file storage (as the original motivation for this issue).
Original issue motivation
When files are not served from the local Drupal installation's files directory, the file_exists in the controller will fail and cause the exception to be thrown. It seems like it would be nice to have a configuration setting to allow external file storage. If this were checked, it would still make sure the file entity exists, but bypass the file_exists check and return a TrustedRedirectResponse object instead of the BinaryFileResponse object.
I will work on a patch for this, but if anyone has a different solution, I would love to hear about it.
My situation is that I have .htaccess rules forwarding requests to files from my local development environment or from test environments to production. This means I can't test or show the functionality until it gets to production. Once on production, I believe it should work without the external file storage configuration turned on. This could also make sense with CDN integration.
Issue fork media_entity_download-2925406
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
tnathanjames commentedI have added a simple configuration form to allow external file storage and updated the controller to use that setting to determine if a redirect should be returned instead of the file. The patch is attached.
Comment #3
tnathanjames commentedComment #4
kevinquillen commentedComment #5
tnathanjames commentedI did a bit of cleanup on my patch. Here is the new version.
Comment #6
michael_wojcik commentedI've refactored the patch written by @tnathanjames in #5, to rename the config being created so that users can decide to use redirects vs. file responses in general, since this has many potential use cases and doesn't just apply to external file storage. For examples, using redirects instead of file responses can have many benefits related to performance (re: caching) and security (re: private file access control).
Comment #7
michael_wojcik commentedIncluding interdiff for changes from #5 to #6.
Comment #8
rbayliss commentedThis patch is looking really good, but in order to get the performance benefits (cacheability of the redirect), the redirect would need to be a 301, but TrustedRedirectResponse defaults to a 302. Does it make sense to make the redirect code be configurable here? One way to do it would be to have "redirect_download" be an integer, where 0 = no redirect, and it can be set to 301 or 302.
Comment #9
michael_wojcik commentedRe-rolled patch to include redirect configuration options (301, 302, or 307). Thanks, @rbayliss!
Comment #10
webflo commentedLets add it to 8.x-2.x first and then backport to 8.x-1.x if still needed?
Comment #11
chop commentedRerolled and adapted for 8.x-2.x
Need this for Drupal 8.4+ Media in core.
Comment #13
damienmckennaJust tagging this with "security" as it affects content security.
Comment #14
nicrodgersupdating status, and assignee based on the last 9 comments.
Comment #15
nicrodgersneeds work as tests in #11 failed.
Comment #16
guedressel commentedFrankly: I don't like this approach.
To me redirects only really make sense for external media sources. Security wise @vitalie's approach is good and integrates access control for "private://" files. Thinking of custom filename suggestion in forced-download situations (content-disposition: attachment): HTTP redirects to "public://" files would prevent any control of HTTP response header from Drupal/PHP since those files are delivered solely by the HTTP daemon - hence would prevent such features from being implemented.
What do you think of my more general approach to handle needs of external media sources in 3012528?
Comment #17
bkosborneRemember that the original goal of this module was to provide a stable URL for a file so that editors can link to it without worrying about it changing when files are replaced.
If we keep that goal in mind, then always redirecting to the file URL makes the most sense to me. Editors just need an easy way to link to the vanity path and Drupal handles the redirect to the actual file when someone requests the path.
But now I'm a bit conflicted. If a redirect is done for something like an image or a PDF, then it's possible that by default a user's browser may open that URL directly in the browser. That would expose the actual file URL to the browser, which means that a visitor may copy and paste that link to and give it to someone else... which is what we don't want.
I don't know how much I should really be concerned about that.
Perhaps the bigger concern should be: Do we really want to be bootstrapping Drupal to proxy the download of public file assets? I think the performance implications of doing so are great enough that we shouldn't. Imagine a swath of visitors with very slow internet connections, and they all request to download a large file using this method. Now PHP is stuck handling this slow I/O instead of the web server. This is of course unavoidable for private files.
Comment #18
bkosborneIn the mean time while this issue is fleshed out more, I created a small contrib module that redirects the canonical path for file-based media entities to the actual file source URL: Media Entity File Redirect.
Comment #19
chop commentedI agree with @bkosborne above:
Redirects to a managed file, that is uploaded for a Media entity, come with problems. Particularly around caching on High Availability architectures where Varnish, Cloudflare and other reverse-proxy caches are used. Cachability metadata is not set and/or returned by this module's download route either which I'm planning to address in another issue.
The main question you need to think about is "what happens if the File changes?"
Will your redirect still be cached at the Edge, or anywhere else between the Origin and the Visitor, as pointing to the old file?
If it is cached, will it be cleared from cache because the redirect response returned appropriate cache metadata (ie. cache tags, contexts, max age, etc.)
There are already patterns provided by other modules that can do a redirect from a Media entity to its file quite well. I'd point to the Rabbit Hole module, in tandem with the Tokens module:
- https://dgo.to/rabbit_hole
- https://dgo.to/token
Using these you set-up your Media type with Rabbit Hole so any request for a Media of that type is redirected (HTTP 301), using tokens, to the File URI of the attached file.
Here's the config export:
config/default/rabbit_hole.behavior_settings.media_type_document.yml
FWIW I think the focus of this module is not really related to doing redirects.
Comment #20
bkosborneRemoving the security concern from the issue motivation, since that is being addressed in #2986486: Check entity access instead of global permission in download controller
These concerns around caching is not justified - This is easily solvable by adding the proper cache metadata to the redirect response. My alternate module Media Entity File Redirect does this. If the file on a "Document" media entity is replaced, the redirect response will be cache invalidated correctly.
Comment #22
clarentina commentedRerolled and adapted for 8.x-2.x
Need this for Drupal 8.8+ Media in core.
Comment #23
gisleChanging status to request review of patch in #22.
Comment #27
steven jones commentedI've opened a MR that takes the patch from #22 and then fixes a couple of bugs.
Seems to work though!
Comment #29
berdirComment #30
dave reidComment #31
dave reidI think this is ready for re-review.
Comment #33
man-1982 commentedtested this approach with Stage File proxy module, and it works perfectly. Made some minor changes to @dave reid PR.
Overall all works perfectly.
Using this patch for couple our Acquia site factory projects
Have some questions about tests. What kind tests do we need to cover this functionality? And how we can emulate remote file download for BrowserTestBase?
i have only one idea is save dummy file in the project repo and and use it permanent link in test module for download file.
thanks
Comment #34
man-1982 commented