9

I have been fumbling around with trying to protect Django's media files with no luck so far! I am simply trying to make it where ONLY admin users can access the media folder. Here is my Nginx file.

server {
    listen 80;
    server_name xxxxxxxxxx;

    location = /favicon.ico {access_log off; log_not_found off;}
    location /static/ {
          alias /home/{site-name}/static_cdn/;
   }
   location /media/ {
          internal;
          root /home/{site-name}/;
   }

   location / {
this is setup and working. Didn't include Code though

}

My Url File

urlpatterns = [
    url(r'^media/', views.protectedMedia, name="protect_media"),
] 

And my view

def protectedMedia(request):

    if request.user.is_staff:
        response = HttpResponse()
        response['Content-Type'] = ''
        response['X-Accel-Redirect'] = request.path
        return response

    else:
        return HttpResponse(status=400)

This is producing a 404 Not Found Nginx error. Does anything look blatantly wrong here? Thanks!

BTW, I have tried adding /media/ to the end of the root URL in the Nginx settings.

Nazkter
  • 1,040
  • 1
  • 11
  • 31
Tyler Bell
  • 837
  • 10
  • 30
  • Seeing your configuration the problem seems to be the one identified in this answer: https://stackoverflow.com/a/45774975/1081569 . You can't use the same URL for the protected view as the one in the nginx configuration. – Paulo Almeida Aug 25 '17 at 01:27
  • Thanks, @PauloAlmeida. I did a few changes to the link you sent me and managed to get it working! – Tyler Bell Aug 25 '17 at 02:03

2 Answers2

18

This is what fixed this issue thanks to @Paulo Almeida.

In the nginx file I changed what I previosly had too...

   location /protectedMedia/ {
          internal;
          root /home/{site-name}/;
   }

My url is...

url(r'^media/', views.protectedMedia, name="protect_media"),

And the View is...

def protectedMedia(request):

    if request.user.is_staff:
        response = HttpResponse(status=200)
        response['Content-Type'] = ''
        response['X-Accel-Redirect'] = '/protectedMedia/' + request.path
        return response

    else:
        return HttpResponse(status=400)

This works perfectly! Now only admin users can access the media files stored in my media folder.

Tyler Bell
  • 837
  • 10
  • 30
  • beware. for me the main pitfall were the nuances with alias/root and trailing slashes! see here: https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile/ - one must really concentrate to not make any mistakes! – benzkji Nov 08 '19 at 09:31
  • What happens when a user goes directly to `/protectedMedia/+ request.path`? Wouldn't NGINX still serve that file to a non-staff user? – Emile Apr 06 '20 at 17:01
  • It was hard to me get the concept of this workflow, this post helps me a lot! – AtomiX84 Apr 15 '20 at 21:40
  • @Emile if I understood `X-Sendfile` or for nginx `X-Accel-Redirect` correct, it is simply an internal redirect to a protected url. So if you call `/protectedMedia/+ request.path` you are not able to access, because you are not redirected through the server. – Neneil Jun 15 '20 at 18:22
  • It seems you can remove the header tag completely with `del response['Content-Type']` – Antonin Rousset Jun 15 '21 at 11:04
8

This helped me a lot, just a small update and modification:

urls.py:

re_path(r'^media/(?P<path>.*)', protectedMedia, name="protect_media")

views.py:

from urllib.parse import quote
from django.http import HttpResponse
from django.contrib.admin.views.decorators import staff_member_required


@staff_member_required
def protectedMedia(request, path):
    response = HttpResponse(status=200)
    response["Content-Type"] = ''
    response['X-Accel-Redirect'] = '/protectedMedia/' + quote(path)
    return response

I had to change the nginx config to the following:

location /protectedMedia/ {
      internal;
      alias /home/{site-name}/;
}

Notes:

  • I prefer using the decorator, as it automatically redirects to the login page (when specified in settings) and sets the "next" page.
  • url() gets deprecated in Django 3.1 so just use re_path() instead
  • alias instead of root in nginx config: I don't want to have "/protectedMedia/" appear in the url (and it didn't work), see also nginx docs
  • edit from 2021-06-14: German-Umlauts (äüö etc.) didn't work in media paths so I edited the above answer to include quote from urllib.parse

If you're still stuck somewhere, this gave me further backround information: https://wellfire.co/learn/nginx-django-x-accel-redirects/

finnuss
  • 81
  • 2
  • 3