Restrict page view access to specific users/customers - django

Certainly not a new question I think, but here it goes:
In my Django based Order system each user (who is not staff) is related to a CustomerProfile object which matches that user to the correct Customer object. This customer users can log in and view outstanding Invoices. To view a customer's invoices you navigate to something like this:
/invoices/customer/97/
(Customer Invoice #97)
Which is fine but I need to incorporate some authentication so a user who is part of a Customer's profile can't view another customer's invoices by manually entering /invoices/customer/92/ for example (invoice 92 belongs to another customer).
I've got this but it's really not good code (and doesn't work):
def customer_invoice_detail(request, object_id):
user = threadlocals.get_current_user()
try:
userprofile = UserProfile.objects.get(user=user)
user_customer = userprofile.customer.id
except UserProfile.DoesNotExist:
user_customer = None
if (request.user.is_authenticated() and user_customer is not null) or request.user.is_staff():
invoice = CustomerInvoice.objects.get(pk=object_id)
product_list = CustomerInvoiceOrder.objects.filter(invoice=object_id)
context = {
'object': invoice,
'product_list': product_list,
}
return render_to_response("invoices/customer_invoice_detail.html", context, context_instance=RequestContext(request))
else:
return HttpResponse("You are not authorised to view this invoice")
Must be a better/easier way to deal with this - any ideas?
Cheers

Add a field to your invoice model called user:
user = models.ForeignKey(User, related_name="invoices")
then retrieve records for a specific user like this:
invoice = CustomerInvoice.objects.get(pk=object_id, user=request.user)
Retrieving invoices for a given user is then trivial with the reverse relation:
request.user.invoices.all()
Also, look at the #login_required decorator.

I'd recommend making some business logic for your customer model. This way you could have a get_invoices() method that returns a list of invoices for that customer only. This method in turn would call a is_authenticated() method that ensures that the current state allows retrieval of protected customer data, or raises an exception.
With this, no matter where your code tries to get invoices for a customer, an exception will always be thrown if the current state does not have access to the invoices, and you won't have to worry about inconsistent behavior as long as you use these methods.

Related

model relationship and Queries in Django not a clear

what does these lines of code mean in Django View: i couldn't find a details explanation, I came to Django from a Laravel background, so I can understand the models and relationships... thanks
customer = request.user.customer
product = Product.objects.get(id=productId)
order, created = Order.objects.get_or_create(customer=customer, complete=False)
orderItem, created = OrderItem.objects.get_or_create(order=order, product=product)
customer = request.user.customer
The request object has a user, the user is the authenticated user (if no user is authenticated then the AnonymousUser object is returned instead). In this example the User model (i.e. the user table) has a field called customer and we are accessing that field.
product = Product.objects.get(id=productId)
Here we are simply querying the Product table for a specific product with the given productId. Note, Django will raise an error if two records are returned when you use the .get() method (i.e. if two rows in the Product table have the same productId.
order, created = Order.objects.get_or_create(customer=customer, complete=False)
Next we use the get_or_create() method to look up an order based off of the customer (the value of which we extracted above. If an order cannot be found we will create one instead. The value of createdwill be True if a neworder` was created or False if one already existed.
orderItem, created = OrderItem.objects.get_or_create(order=order, product=product)
Just as above we are getting or creating an OrderItem using the order and product fields.

Django url manipulation - detail page with pk / id should not show based on attributes of that object - Best Practice

I have a django page that displays a list of links. Each link points to the detail page of the respective object. The link contains the pk/id of that object (something like ../5/detailObject/). The list is generated on the backend and has some filtering baked into it, e.g. only generate a link if that object has state x, etc.
Clicking on the links works, but users can still manipulate the url and pass a valid link with an incorrect state (a wrong pk/id is being handled with the get or 404 shortcut).
What is the best practice for handling this kind of scenario with django? Should that kind of filtering be placed in the object's model class instead of using function-based views as I do now?
Function based view:
If you want to restrict a set of objects to a particular user (for instance a user's orders), then you would need to set up the Order model to foreign key to the User model and then look up the order by both id and user:
views.py:
def get_order(request, id=0)
if request.method == 'GET':
try:
order = Order.objects.get(user=request.user, pk=id)
except Order.DoesNotExist:
return redirect(...)
And set up a url to handle:
url(r'^order/(?P<id>\d+)/$', views.get_order, name='get_order_by_id'),
As far as adding a slug field on the model after the fact, set up a second url:
url(r'^order/(?P<slug>[\w-]+)/$', views.get_order, name='get_order_by_slug')
And change the above view logic to first do a lookup by pk if pk is greater than 0 and then redirect back to the function using the slug from the looked up order (this assumes all looked-up records have slugs):
def get_order(request, slug='', id=0)
if request.method == 'GET':
try:
if id > 0:
order = Order.objects.get(user=request.user, pk=id)
return redirect(reverse('get_order_by_slug'), permanent=True, slug=order.slug)
order = Order.objects.get(user=request.user, slug=slug)
except Order.DoesNotExist:
return redirect(...)
You should also put unique=True on the slug field and ensure that the user is authenticated by placing the #login_required decorator on your view.
To restrict orders by a particular status, you could:
Create a set of statuses for your Order model, and then you could:
Pass a value for a kwarg in the view when you filter, or
Create a custom manager on the Order model
There are several ways you could create your statuses:
as a set of choices on the Order model
use the SmartChoices library
as a database field
If you create choices on the Order model, it could be something like this:
class Order(models.model):
STATUSES = (
('PLCD', 'Placed'),
('INTR', 'In Transit'),
('DLVR', 'Delivered')
)
status = models.CharField(max_length=4, default='', choices=STATUSES)
An acquaintance who is a very seasoned Django professional told me about the SmartChoices library. I have not used it yet but would like to try it at some point. The database field option would be my least preferred way of doing this because that seems to me like moving programming variables into the database; however, it would work.

How do you set Django permissions based on models linked to a user (and not based on a user's group)?

I have to make a system which will allow users to progressively fill out surveys based on whether or not the initial survey was accepted.
What this means is that after the user has logged in, they need to be able to fill in a preliminary survey for screening purposes. Only once this preliminary screening survey has been approved by an administrative user (superuser) will the user have access to the second (and final) survey.
I have a survey model which looks like this:
class Screening_survey(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=20)
surname = models.Charfield(max_length=20)
salary = models.IntegerField()
is_accepted = models.NullBooleanField()
When the user posts this initial survey, they only fill in the name, surname and salary fields. When the survey is initially posted, 'user' is set to the current logged in user (request.user) and is_accepted is simply set to 'unknown' automatically in the background. It is then up to the administrative superuser to change the is_accepted value to 'yes' or 'no'.
If the value of the is_accepted field is set to yes for a particular user's form, then that user should be able to access the next survey.
I have looked at Django permissions, but I do not think the default permissions will work for me seeing as I don't want users of a specific /group/ to have access to specific pages; rather I want users whose initial surveys have been approved to have access to the next view (the final survey).
I have also given the #user_passes_test decorator some thought, but again it seems as if it checks permissions only on a user basis and not on the basis of a user and a model that is linked to them.
I know this question seems simple -- I'm quite new to Django as well as programming in general).
Thank you for your help.
You're spot on - you will need to define your own logic to see if the user has been approved, not permissions. Permissions don't really allow you to test on a per-user or per-model basis the way you're looking for.
There are two places to define where this test will be. Either in the view itself, or via your own #user_passes_test decorator. You already have a Foreign Key to the user object, so you can write a test in the view, like this:
#login_required
def survey_view(request):
survey = Screening_survey.objects.filter(user=request.user)
if survey and survey.is_accepted is 'yes':
# Do the remaining view logic
else:
raise PermissionDenied
If you want to put this logic in the #user_passes_test, that would work well too. The user object is passed directly (see the docs for an example)

Django action (not model data directly) HTML view and API validation keeping a DRY rule

I would like to create an API for my project.
The models:
class Offer(models.Model):
user = models.ForeignKey(User)
data = models.TextField()
class Bid(models.Model):
user = models.ForeignKey()
offer = models.ForeignKey()
created_at = models.DateTimeField(auto_now_add=True)
Here's the simplified example (as there are more checks)
Both Offer & Bid users can cancel the Bid.
In my standard (HTML) view:
def cancel_bid(request, pk):
do_some checks_if request.user_is_either_Bid_or+Offer)creator()
check_if_Bid_has_been_created_for_less_than_5_minutes()
#as user can cancel his bid within 5 minutes
Now the same must be applied to Django Rest Framework (permissions or serializers).
The problem is that I need to return error messages and codes displayed in both json error response (when using api) and in my HTML views.
I have created a cancel(self, user, other kwargs) in my Bid model where check for these are performed and my custom PermissionDenied(message, code) is returned. Then in DRF and my views I simply put:
bid = Bid.objects.get(pk=pk)
try:
bid.cancel(user):
except PermissionDenied as e:
messages.error(request, e.message)
# or return HttpResponseForbidden(e.message)
in django rest framework:
class OrderCancelView(APIView):
def post(self, request, pk):
try:
order.cancel(request.user)
except PermissionDenied as e:
raise serializers.PermissionDenied(detail=e.message)
There are more actions performed in my clean() method not pointed out here.. which
makes the method very complex.
For example:
If offer expires all bids except the first one (earliest) are cancelled.. so there is no user,
as it's being made by system. I have to omit user checks then.. and so on.
What are your thoughts on this ? What is the best Django way of doing this kind of "action" validation and keeping DRY rule?
Seems like you've already made a good start, you just need to structure your cancel method on the model so that it has all of the information it needs to execute the business logic. If your logic has to be the same between DRF and the normal view code then I would extract all the business logic into the model. You might have to give it lots of arguments so that it can make all of the correct decisions, but that's better than having to repeat the logic twice.

How to restrict editing of records to the logged-in user?

Sorry, I am still new to Django, hopefully the question isn't out of place.
When I have the following in my template:
<td>{{ item.last_name }}</td>
By clicking on last name the user will be redirected to the following link in order to edit it.
http://127.0.0.1:8000/contact/edit/?id=1
But then what prevents any logged in user to just inject a different id in there on the browser and edit a record that doesn't belong to him?
Update
I just had an idea when I read the comment and answer below. Rather than using a third party app, couldn't I just create a UserProfile for each user and attach a unique company wide uuid.uuid1(). Each time a loggedin user attempts to edit something, his unique company uuid will be also passed in the link as an additional parameter.
On the edit side, it would harvest this guid and compare it against the logged in user and see if they match. If they do match, he is authorized to proceed with the editing, otherwise he will be redirected.
What do you think? Any weaknesses?
If you use Django's new class based views, e.g. the generic UpdateView, you can extend the dispatch handler.
def dispatch(self, request, *args, **kwargs):
handler = super(MyEditView, self).dispatch(request, *args, **kwargs)
# Only allow editing if current user is owner
if self.object.author != request.user:
return HttpResponseForbidden(u"Can't touch this.")
return handler
In this case, the code verifies that the author field of the model object corresponds to the currently logged in user, before even handling the rest of the request.
You can see a reallife example of this in a project of mine.
When you're using Django auth, always rely on the session mechanism to identify an user instead of making some other id such as uuid1() (except, for example, when you need extra sessions in an e-commerce site under HTTPS).
For the permission part, you could check the ownership directly, mainly as described by Koliber Services. The relationships between Company, User and Contact are crucial for the logic of permission checking. There are many ways to model the relationships and thus the code would differ much. For example:
# a modeling way
User.company -> Company : an user belongs to a company
Contact.contributor -> User : a contact is contributed by an user, would be invalid is user is leaving the company
# could check accessibility by
can_view = contact.contributor.company_id == current_user.company_id
# another modeling way
User.company -> Company : an user belongs to a company
Contact.company -> Company : a contact info is owned by a company, does not share globally
# could check accessibility by
can_view = contact.company_id == current_user.company_id
When can_view is False, user should get a 403 for his unauthorized attempting and get logged.
Normally the above method is enough for content protection(not yet in Django Admin). However, when you have many different types of permission checking and even row-permission checkings, it's better to use some uniform permission API.
Take Django-guardian for example, you could simply map companies to groups and assign can_view permission for a contact for the group representing the user's company. Or, assign the can_view permission to all users in a company when a contact is created by using signal or celery task.
Furthermore, you could use /contact/1/edit/ instead of /contact/edit/?id=1. In this way the int(request.GET('id')) part is moved to urlconf like r'^contact/(?P<pk>\d+)/$', less code and much clearer.
There are some third-party apps that does what you want, its called "row-level permission" where you can give different users different access to specific objects, "Row level" comes from SQL where each object is a row in the database
I use django-guardian to do the job
In the function handling the saving of the data, check to see if the object being edited has the same ID as the presently logged in user.
For example, if the object in question is called EmailPrefs and it has a field called user_id:
Load the EmailPrefs object with the ID of the object being edited
If the user_id does not match the current user, stop further processing
Modify the EmailPrefs object
Save the EmailPrefs object to the database