Serve protected media files with django

user2496550 picture user2496550 · Sep 28, 2016 · Viewed 9.6k times · Source

I'd like Django to serve some media files (e.g. user-uploaded files) only for logged-in users. Since my site is quite low-traffic, I think I will keep things simple and do not use django-sendfile to tell Nginx when to serve a file. Instead I'll let Django/Gunicorn do the job. To me this seems a lot simpler and for a low traffic site this maybe more secure.

But what is the best way to organize the file storage location? Media files are all stored below MEDIA_ROOT and this directory is served by Nginx in production. If I upload my files to MEDIA_ROOT/protected/ I have to tell Nginx not to serve the files in the subdirectory protected.

But is this a good idea? It seems a litte risky to me to allow Nginx access /media/ in the first place and then protect the subdirectory /media/protected/. Wouldn't it be better not to use a subdirectory of MEDIA_ROOT to store protected files?

But if I try something like this quick-and-dirty in my model:

upload_to='../protected/documents/%Y/%m/'

Django complains:

SuspiciousFileOperation at /admin/core/document/add/
The joined path (/home/me/projects/project/protected/documents/2016/09/test.file) is located outside of the base path component (/home/me/projects/project/media)

So I thing it is not good practice to "leave" the MEDIA_ROOT.

What is the best solution to store and serve protected media files?

Answer

Renjith Thankachan picture Renjith Thankachan · Apr 5, 2017

Serving media files ( that may be large files) from view directly is not good. You can use sendfile extension available in nginx server; a sample nginx configuration is like below.

 location /projects/project/media/{
    # this path is not public
    internal;
    # absolute path
    alias /projects/project/media/;
 }

change your view to

@login_required
def serve_protected_document(request, file):
    document = get_object_or_404(ProtectedDocument, file="protected/documents/" + file)

    # Split the elements of the path
    path, file_name = os.path.split(file)

    response = HttpResponse()
    response["Content-Disposition"] = "attachment; filename=" + file_name
    # nginx uses this path to serve the file
    response["X-Accel-Redirect"] = document.name # path to file
    return response

Link: More details on configuring sendfile extension on nginx is here