Skip to content

Separate handling of protected media in Django, with X-Sendfile support

License

Notifications You must be signed in to change notification settings

cobusc/django-protected-media

Repository files navigation

Build Status

Django Protected Media

Django Protected Media is a Django app that manages media that are considered sensitive in a protected fashion.

Not only does the media get stored in a separate filesystem location, but authorisation is also required to access it.

The application allows for setups where Django performs the authorisation, but hands off the serving of the file to a web server, like Nginx.

Quick start

  1. Install the package with pip like this:
pip install django-protected-media
  1. Add "protected_media" to your INSTALLED_APPS setting like this:
INSTALLED_APPS = [
    ...
    'protected_media.apps.ProtectedMediaConfig',
]
  1. Include the URLconf in your project urls.py like this::
path('protected/', include('protected_media.urls')),
  1. Add the following settings to settings.py if the defaults need to be tweaked:
PROTECTED_MEDIA_ROOT = "%s/protected/" % BASE_DIR
PROTECTED_MEDIA_URL = "/protected"
PROTECTED_MEDIA_SERVER = "nginx"  # Defaults to "django"
PROTECTED_MEDIA_LOCATION_PREFIX = "/internal"  # Prefix used in nginx config
PROTECTED_MEDIA_AS_DOWNLOADS = False  # Controls inclusion of a Content-Disposition header
  1. Use the new field classes in your models:
from protected_media.models import ProtectedImageField, ProtectedFileField

def SomeModel(models.Model):
    document = ProtectedFileField(upload_to="uploads/")
    picture = ProtectedImageField(upload_to="uploads/")
    # Files will be stored under PROTECTED_MEDIA_ROOT + upload_to

Overview

Django manages media based on the following definitions:

BASE_DIR = "/some/application/dir/"
MEDIA_ROOT = "%s/media/" % BASE_DIR
MEDIA_URL = "/media/"

File- and image fields are typically defined as:

document = models.FileField(upload_to="uploads/")
picture = models.ImageField(upload_to="uploads/")
# Files will be stored under MEDIA_ROOT + upload_to

In a typical production environment one would let nginx (or some other server) serve the media:

# Publicly accessible media
location ^~ /media/ {
    alias /some/application/dir/media
}

This works well when the media should be publically accessible. However, if the media should be protected, we need a way for Django to check whether the request for the media should only be allowed for logged in (or more stringent criteria) users.

The protected_media application

The protected_media application consists of

  • new settings.py attributes,
  • a customized FileSystemStorage class,
  • a custom handler for the protected media URL and
  • additional web server configuration if serving via nginx or something similar.

Protected media is stored in a different physical location to publically accessible media. The following settings can be specified in settings.py:

PROTECTED_MEDIA_ROOT = "/some/application/dir/protected/"
PROTECTED_MEDIA_URL = "/protected"
PROTECTED_MEDIA_SERVER = "nginx"  # Defaults to "django"
PROTECTED_MEDIA_LOCATION_PREFIX = "/internal"  # Prefix used in nginx config

When defining a file or image field that needs to be protected, we use one of the classes provided by the protected_media application:

  • ProtectedFileField
  • ProtectedImageField

Protected file- and image fields are typically defined as:

document = ProtectedFileField(upload_to="uploads/")
picture = ProtectedImageField(upload_to="uploads/")
# Files will be stored under PROTECTED_MEDIA_ROOT + upload_to

These classes have a custom storage backend ProtectedFileSystemStorage which mananges the filesystem location and URLs associated with protected media.

When nginx is used, the configuration must be updated to look like this:

# Publicly accessible media
location /media  {
    alias /some/application/dir/media;
}

# Protected media
location /internal  {
    internal;
    alias /some/application/dir/protected;
}