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.
In django, the currently logged in user is available in your views as the property
user
of the request object.The idea is to filter your models by the logged in user first, and then if there are any results only show those results.
If the user is trying to access an object that doesn't belong to them, don't show the object.
One way to take care of all of that is to use the
get_object_or_404
shortcut function, which will raise a 404 error if an object that matches the given parameters is not found.Using this, we can just pass the primary key and the current logged in user to this method, if it returns an object, that means the primary key belongs to this user, otherwise it will return a 404 as if the page doesn't exist.
Its quite simple to plug it into your view:
Now, if the user tries to access a link with a pk that doesn't belong to them, a 404 is raised.
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.
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.
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):
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:
You're going to want to look into user authentication and authorization, which are both supplied by Django's Auth package. There's a big difference between the two things, as well.
Authentication is making sure someone is who they say they are. Think, logging in. You get someone to entire their user name and password to prove they are the owner of the account.
Authorization is making sure that someone is able to access what they are trying to access. So, a normal user for instance, won't be able to just switch PK's.
Authorization is well documented in the link I provided above. I'd start there and run through some of the sample code. Hopefully that answers your question. If not, hopefully it provides you with enough information to come back and ask a more specific question.
There are a few ways you can achieve this:
If you have the concept of login, just restrict the URL to:
and in the code,
user=request.user
and display data only for the logged in user.Another way would be:
and in the view:
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.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.