How to to make a file private by securing the url that only authenticated users can see
Asked Answered
L

3

13

I was wondering if there is a way to secure an image or a file to be hidden when it is not authenticated.

Suppose there is an image in my website which can only be seen if that user is authenticated. But the thing is I can copy the url or open the image in the new tab.

http://siteis.com/media/uploaded_files/1421499811_82_Chrysanthemum.jpg

And again, even if I am not authenticated, I can view that particular image by going to that url. So, my my problem is, how do I secure the files, so that only authenticated users will see?

Update:

view:

def pictures(request, user_id):
    user = User.objects.get(id=user_id)
    all = user.photo_set.all()
    return render(request, 'pictures.html',{
        'pictures': all
    })

models:

def get_upload_file_name(instance, filename):
    return "uploaded_files/%s_%s" %(str(time()).replace('.','_'), filename)

class Photo(models.Model):
    photo_privacy = models.CharField(max_length=1,choices=PRIVACY, default='F')
    user = models.ForeignKey(User)
    image = models.ImageField(upload_to=get_upload_file_name)

settings:

if DEBUG:
    MEDIA_URL = '/media/'
    STATIC_ROOT = os.path.join(os.path.dirname(BASE_DIR), "myproject", "static", "static-only")
    MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), "myproject", "static", "media")
    STATICFILES_DIRS = (
    os.path.join(os.path.dirname(BASE_DIR), "myproject", "static", "static"),
    )

Update:

template:

{% if pictures %}
    {% for photo in pictures %}
        <img src="/media/{{ photo.image }}" width="300" alt="{{ photo.caption }}"/>
    {% endfor %}
{% else %}
    <p>You have no picture</p>
{% endif %}

url:

url(r'^(?P<user_name>[\w@%.]+)/photos/$', 'pictures.views.photos', name='photos'),

if settings.DEBUG:
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Laser answered 18/1, 2015 at 6:37 Comment(4)
A way would be to have a controller handle the URL /media/uploaded_files/* and check for credentials. If not authorized, then return a 404 status.Accessible
@Accessible Could you please have a look at the update, and advice me how to do it. Thank you.Laser
The URL for the files should now be a logical URL, not part of static. And then I'd do something similar to what @Burhan Khalid posted.Accessible
@Accessible How will I make it logical? In the settings.py? Can you please answer it and not comment. I am quite new to django. I will really appreciate if you could.Laser
P
11

By securing any media file not to serve by anonymous user, better way url protection.

Code ( Updated ):

from django.conf.urls import patterns, include, url
from django.contrib.auth.decorators import login_required
from django.views.static import serve
from django.conf import settings

from django.core.exceptions import ObjectDoesNotExist
from django.shortcuts import HttpResponse

@login_required
def protected_serve(request, path, document_root=None):
    try:
        obj = Photobox.objects.get(user=request.user.id)
        obj_image_url = obj.image.url
        correct_image_url = obj_image_url.replace("/media/", "")
        if correct_image_url == path:
            return serve(request, path, document_root)
    except ObjectDoesNotExist:
        return HttpResponse("Sorry you don't have permission to access this file")


url(r'^{}(?P<path>.*)$'.format(settings.MEDIA_URL[1:]), protected_serve, {'file_root': settings.MEDIA_ROOT}),

Note: previously any logged in user can access any page, now this update restrict non user to view other files...

Peoria answered 18/1, 2015 at 7:28 Comment(10)
Could you please also show me how do I show the photos in the template? I will really appreciate it. Thank you.Laser
Yes, I have working code. And it does displays me the photo in the template. The thing is I can copy the url of the image and open it as anonymous user. I tried your code, copied your views and url. But nothing has changed. Will appreciate if you could elaborate more.Laser
This works on my machine. But not when I'm using amazon s3. Could you please update the answer for that?Laser
@Laser Actually there is a security bug in above method. Any logged in user can access that file. So i will look that first.Peoria
Thank you! That's very kind of you.Laser
@Laser Thanks for the delay... I made some changes in that code. Now logged in user only access that page... CheersPeoria
@RajaSimon how would you adapt this to protect .csv files in the same way? I am not sure what Photobox is doing in your code otherwise I would try to adapt it myself. Thanks for your help!Aliment
@RenelChesak I'm pretty sure the above code handles .csv file also when that .csv files are served using Django media serve concept. Photobox is just an Django models.Peoria
And if you want more complex better solution. Checkout github.com/edoburu/django-private-storagePeoria
@RajaSimon could you show me your code above adapted to .csv files? I spent several hours trying to use the github package you mentioned but I think there are compatibility issues between my Django 1.8 and it being built for Django 1.9.Aliment
S
3

It would be better to handle just the authentication, and let your webserver handle the serving of files. It's probably good to put them in a different directory than your settings.MEDIA_ROOT, to prevent your webserver from serving the files before you handle the request, e.g. project_root/web-private/media/.

import os

@login_required
def protected_file(request, path):
    # set PRIVATE_MEDIA_ROOT to the root folder of your private media files
    name = os.path.join(settings.PRIVATE_MEDIA_ROOT, path)
    if not os.path.isfile(name):
        raise Http404("File not found.")

    # set PRIVATE_MEDIA_USE_XSENDFILE in your deployment-specific settings file
    # should be false for development, true when your webserver supports xsendfile
    if settings.PRIVATE_MEDIA_USE_XSENDFILE:
        response = HttpResponse()
        response['X-Accel-Redirect'] = filename # Nginx
        response['X-Sendfile'] = filename # Apache 2 with mod-xsendfile
        del response['Content-Type'] # let webserver regenerate this
        return response
    else:
        # fallback method
        from django.views.static import serve
        return serve(request, path, settings.PRIVATE_MEDIA_ROOT)

As your webserver is way better at serving static files than Django, this will speed up your website. Check django.views.static.serve for an idea how to sanitize file names etc.

Starve answered 18/1, 2015 at 12:43 Comment(4)
To use this method, do I need to modify anything on Apache 2 configuration?Aliment
@RenelChesak I mostly use this with Ngnix. You may have to install and enable mod_xsendfile on Apache, but I'm not sure on the details.Starve
This is a very helpful tutorial on setting it up for Apache: h3xed.com/web-development/…. @Starve as for your code above, does this need to be in the main project's urls.py, or does it need to be a Function-Based View in the app's views.py?Aliment
Can your code above be used to control access to a file that is designated in another Function-Based View? Also, what do you recommend to put in the urls.py?Aliment
E
1

The easiest option is to serve the file from django, and then add the @login_required decorator to the view, like this:

import os
import mimetypes
from django.core.servers.basehttp import FileWrapper
from django.contrib.auth.decorators import login_required

@login_required
def sekret_view(request, path=None):
   filename = os.path.basename(path)
   response = HttpResponse(FileWrapper(open(path)),
                           content_type=mimetypes.guess_type(path)[0])
   response['Content-Length'] = os.path.getsize(path)
   return response
Exorcise answered 18/1, 2015 at 6:45 Comment(1)
Thank you for the answer. But I am quite new to django didn't get that much as to how to show it in the template. Maybe if you could you please have a look at the update and tell me how, I will be very much grateful.Laser

© 2022 - 2024 — McMap. All rights reserved.