django-private-files 1.0.0 documentation¶
This application provides utilities for controlling access to static files based on
conditions you can specify within your Django application.
It provides a PrivatedFileField
model field and appropriate signals for monitoring access to static content.
The basic goal is that you should be able to specify permissions for each PrivateFileField
instance in
one method (or callable) and leave the rest to django-private-files.
Additionally you should be able to switch server (eg. from nginx to lighttpd) without hassle and remove
this application from your project without changes to your database.
It supports the following methods for limiting access to files:
- Basic - files are served with Python (not recommended for production if you have another choice)
- Nginx (X-Accel-Redirect) - you can specify protected locations within your nginx configuration file
- xsendfile - Apache (with mod_xsendfile), lighttpd and cherokee (not tested yet)
It’s currently been tested with Django (1.9, 1.10, 1.11 and 2.0), Apache and Nginx. It should work with older versions of django. Cherokee and lighttpd use the same mechanism as Apache mod_xsendfile, so it should work, but it’s not been tested or documented.
Contents:
Installation¶
Install from PyPI with easy_install
or pip
:
pip install django-private-files
or download the source and do:
python setup.py install
or if you want to hack on the code symlink to it in your site-packages:
python setup.py develop
In your settings.py INSTALLED_APPS
add private_files
.
In your urls.py add the private_files
application urls:
from django.conf.urls.defaults import patterns, include, url
# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
# Examples:
url(r'^private_files/', include('private_files.urls')),
# Uncomment the admin/doc line below to enable admin documentation:
# url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment the next line to enable the admin:
url(r'^admin/', include(admin.site.urls)),
)
Usage¶
Limiting Access to Static Files¶
To protect a static file that you have reference to in the database you need
to use the PrivateFileField
model field. For example:
from django.db import models
from private_files import PrivateFileField
class FileSubmission(models.Model):
description = models.CharField("description", max_length = 200)
uploaded_file = PrivateFileField("file", upload_to = 'uploads')
By default it will check if the user is authenticated and let them download the file as an attachment.
If you want to do more complex checks for the permission to download the file you
need to pass your own callable to the condition
parameter:
from django.db import models
from django.contrib.auth.models import User
from private_files import PrivateFileField
def is_owner(request, instance):
return (not request.user.is_anonymous()) and request.user.is_authenticated and
instance.owner.pk == request.user.pk
class FileSubmission(models.Model):
description = models.CharField("description", max_length = 200)
owner = models.ForeignKey(User)
uploaded_file = PrivateFileField("file", upload_to = 'uploads', condition = is_owner)
This would check if the user requesting the file is the same user referenced in the owner
field and
serve the file if it’s true, otherwise it will throw PermissionDenied
.
condition
should return True
if the request
user should be able to download the file and False
otherwise.
Another optional parameter is attachment
. It allows you to control wether the content-disposition
header is sent or not.
By default it is True
, meaning the user will always be prompted to download the file by the browser.
Monitoring Access to Static Files¶
By using django-private-files
you can monitor when a file is requested for download.
By hooking to the pre_download
signal. This fires when a user is granted access to a file
and right before the server starts streaming the file to the user. The following is a simple
example of using the signal to provide a download counter:
from django.db import models
from django.contrib.auth.models import User
from private_files import PrivateFileField, pre_download
class CountedDownloads(models.Model):
description = models.CharField("description", max_length = 200)
downloadable = PrivateFileField("file", upload_to = 'downloadables')
downloads = models.PositiveIntegerField("downloads total", default = 0)
def handle_pre_download(instance, field_name, request, **kwargs):
instance.downloads += 1
instance.save()
pre_download.connect(handle_pre_download, sender = CountedDownloads)
Server configurations¶
All of the bellow examples assume that:
MEDIA_ROOT
is set to/media/psf/Home/Projects/django-private-files/testproject/static/
MEDIA_URL
is set to/media/
- Protected files are stored in two subfolders
uploads
anddownloadables
- Other static files stored in
MEDIA_ROOT
should be freely downloadable
Apache¶
If you serve your static content with Apache and have mod_xsendfile you can set PRIVATE_DOWNLOAD_HANDLER
to 'private_files.handlers.x_sendfile'
. Turn
XSendFile
on and deny access to the directory where you store your protected files (the value of upload_to
appended to MEDIA_ROOT
).
Here’s an exmple of a vhost configuration with mod_xsendfile and mod_wsgi:
<VirtualHost *:80>
ServerName django.test
XSendFile on
alias /adminmedia/ /media/psf/Home/Projects/django-private-files/testproject/static/
alias /media/ /home/vasil/src/django-trunk/django/contrib/admin/media/
WSGIDaemonProcess django-test user=vasil group=users threads=1 processes=5
WSGIProcessGroup django-test
WSGIScriptAlias / /media/psf/Home/Projects/django-private-files/testproject/django.wsgi
<Directory /media/psf/Home/Projects/django-private-files/testproject>
Order deny,allow
Allow from all
</Directory>
<Directory /media/psf/Home/Projects/django-private-files/testproject/static/uploads>
Order deny,allow
Deny from all
</Directory>
<Directory /media/psf/Home/Projects/django-private-files/testproject/static/downloadables>
Order deny,allow
Deny from all
</Directory>
<Directory /home/vasil/src/django-trunk/django/contrib/admin>
Order deny,allow
Allow from all
</Directory>
ErrorLog /var/log/httpd/test.err.log
</VirtualHost>
Nginx¶
When using Nginx PRIVATE_DOWNLOAD_HANDLER
needs to be set to 'private_files.handlers.x_accel_redirect'
.
Use the internal
directive like in this example:
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name django.test;
location /uploads/{
internal;
root /media/psf/Home/Projects/django-private-files/testproject/static;
}
location /downloadables/{
internal;
root /media/psf/Home/Projects/django-private-files/testproject/static;
}
location /media/{
alias /media/psf/Home/Projects/django-private-files/testproject/static/;
}
location /media/uploads/ {
deny all;
}
location /media/downloadables/ {
deny all;
}
location / {
fastcgi_pass localhost:3033;
fastcgi_param PATH_INFO $fastcgi_script_name;
include fastcgi.conf;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
}
}