Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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)
like image 232
Robin Avatar asked Jan 18 '15 06:01

Robin


3 Answers

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.

like image 196
knbk Avatar answered Nov 05 '22 07:11

knbk


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...

like image 23
Raja Simon Avatar answered Nov 05 '22 09:11

Raja Simon


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
like image 1
Burhan Khalid Avatar answered Nov 05 '22 08:11

Burhan Khalid