I have multiple FileFields in my django app, which can belong to different users. I am looking for a good way to restrict access to files for user who aren't the owner of the file.
What is the best way to achieve this? Any ideas?
I have multiple FileFields in my django app, which can belong to different users. I am looking for a good way to restrict access to files for user who aren't the owner of the file.
What is the best way to achieve this? Any ideas?
Unfortuanately @Mikko's solution cannot actually work on a production environment since django is not designed to serve files. In a production environment files need to be served by your HTTP server (e.g apache, nginx etc) and not by your application/django server (e.g uwsgi, gunicorn, mod_wsgi etc).
That's why restricting file acccess is not very easy: You need a way for your HTTP server to ask the application server if it is ok to serve a file to a specific user requesting it. As you can understand thiss requires modification to both your application and your http server.
The best solution to the above problem is django-sendfile (https://github.com/johnsensible/django-sendfile) which uses the X-SendFile mechanism to implement the above. I'm copying from the project's description:
This is a wrapper around web-server specific methods for sending files to web clients. This is useful when Django needs to check permissions associated files, but does not want to serve the actual bytes of the file itself. i.e. as serving large files is not what Django is made for.
To understand more about the senfile mechanism, please read this answer: Django - Understanding X-Sendfile
2018 Update: Please notice that django-sendfile does not seem to be maintained anymore; probably it should still be working however if you want a more modern package with similar functionality take a look at https://github.com/edoburu/django-private-storage as commenter @surfer190 proposes. Especially make sure that you implement the "Optimizing large file transfers" section; you actuallyu need this for all transfers not only for large files.
If you need just moderate security, my approach would be the following:
1) When the user uploads the file, generate a hard to guess path for it. For example you can create a folder with a randomly generated name for each uploaded file in your /static folder. You can do this pretty simply using this sample code:
file_path = "/static/" + os.urandom(32).encode('hex') + "/" + file_name
In this way it will be very hard to guess where other users' files are stored.
2) In the database link the owner to the file. An example schema can be:
uploads(id, user_id, file_path)
3) Use a property for your FileFields in the model to restrict access to the file, in this way:
class YourModel(models.Model)
_secret_file = models.FileField()
def get_secret_file(self):
# check in db if the user owns the file
if True:
return self._secret_file
elif:
return None # or something meaningful depanding on your app
secret_file = property(get_secret_file)
Generally, you do not route private files through normal static file serving directly through Apache, Nginx or whatever web server you are using. Instead write a custom Django view which handles the permission checking and then returns the file as streaming download.
Make sure files are in a special private folder folder and not exposed through Django's MEDIA_URL
or STATIC_URL
Write a view which will
Check that the user has access to the file in your view logic
Open the file with Python's open()
Return HTTP response which gets the file's handle as the parameter http.HttpResponse(_file, content_type="text/plain")
For example see download()
here.
This is best handled by the server, e.g. nginx secure link module (nginx must be compiled with --with-http_secure_link_module
)
Example from the documentation:
location /some-url/ {
secure_link $arg_md5,$arg_expires;
secure_link_md5 "$secure_link_expires$uri$remote_addr some-secret";
if ($secure_link = "") {
return 403;
}
if ($secure_link = "0") {
return 410;
}
if ($secure_link = "1") {
// authorised...
}
}
The file would be accessed like:
/some-url/some-file?md5=_e4Nc3iduzkWRm01TBBNYw&expires=2147483647
(This would be both time-limited and bound to the user at that IP address).
Generating the token to pass to the user would use something like:
echo -n 'timestamp/some-url/some-file127.0.0.1 some-secret' | \
openssl md5 -binary | openssl base64 | tr +/ -_ | tr -d =