Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prevent user changing URL <pk> to see other submission data Django

I'm new to the web development world, to Django, and to applications that require securing the URL from users that change the foo/bar/pk to access other user data.

Is there a way to prevent this? Or is there a built-in way to prevent this from happening in Django?

E.g.: foo/bar/22 can be changed to foo/bar/14 and exposes past users data.

I have read the answers to several questions about this topic and I have had little luck in an answer that can clearly and coherently explain this and the approach to prevent this. I don't know a ton about this so I don't know how to word this question to investigate it properly. Please explain this to me like I'm 5.

like image 930
CodeBird Avatar asked Aug 14 '14 16:08

CodeBird


4 Answers

There are a few ways you can achieve this:

If you have the concept of login, just restrict the URL to:

/foo/bar/

and in the code, user=request.user and display data only for the logged in user.

Another way would be:

/foo/bar/{{request.user.id}}/

and in the view:

def myview(request, id):
    if id != request.user.id:
        HttpResponseForbidden('You cannot view what is not yours') #Or however you want to handle this

You could even write a middleware that would redirect the user to their page /foo/bar/userid - or to the login page if not logged in.

like image 172
karthikr Avatar answered Nov 01 '22 08:11

karthikr


I'd recommend using django-guardian if you'd like to control per-object access. Here's how it would look after configuring the settings and installing it (this is from django-guardian's docs):

>>> from django.contrib.auth.models import User
>>> boss = User.objects.create(username='Big Boss')
>>> joe = User.objects.create(username='joe')
>>> task = Task.objects.create(summary='Some job', content='', reported_by=boss)
>>> joe.has_perm('view_task', task)
False

If you'd prefer not to use an external library, there's also ways to do it in Django's views.

Here's how that might look:

from django.http import HttpResponseForbidden
from .models import Bar

def view_bar(request, pk):
    bar = Bar.objects.get(pk=pk)
    if not bar.user == request.user:
        return HttpResponseForbidden("You can't view this Bar.")
    # The rest of the view goes here...
like image 31
Kevin London Avatar answered Nov 01 '22 08:11

Kevin London


Just check that the object retrieved by the primary key belongs to the requesting user. In the view this would be

if some_object.user == request.user: ...

This requires that the model representing the object has a reference to the User model.

like image 41
crhodes Avatar answered Nov 01 '22 10:11

crhodes


In my project, for several models/tables, a user should only be able to see data that he/she entered, and not data that other users entered. For these models/tables, there is a user column.

In the list view, that is easy enough to implement, just filter the query set passed to the list view for model.user = loggged_id.user.

But for the detail/update/delete views, seeing the PK up there in the URL, it is conceivable that user could edit the PK in the URL and access another user's row/data.

I'm using Django's built in class based views.

The views with PK in the URL already have the LoginRequiredMixin, but that does not stop a user from changing the PK in the URL.

My solution: "Does Logged In User Own This Row Mixin" (DoesLoggedInUserOwnThisRowMixin) -- override the get_object method and test there.

from django.core.exceptions import PermissionDenied

class DoesLoggedInUserOwnThisRowMixin(object):

    def get_object(self):
        '''only allow owner (or superuser) to access the table row'''
        obj = super(DoesLoggedInUserOwnThisRowMixin, self).get_object()
        if self.request.user.is_superuser:
            pass
        elif obj.iUser != self.request.user:
            raise PermissionDenied(
                "Permission Denied -- that's not your record!")
        return obj

Voila!

Just put the mixin on the view class definition line after LoginRequiredMixin, and with a 403.html template that outputs the message, you are good to go.

like image 36
Rick Graves Avatar answered Nov 01 '22 09:11

Rick Graves