Django UserPassesTestMixin confusion/questions? - django

I am currently working on an admin dashboard, which include specific views for company administrators only which are labeled as Business users.
The app is going to have about 10 views, and I had a few questions in regards to the UserPassesTestMixin
Basically, all of my views would include this,
def test_func(self):
return self.request.user.user_type == 'Business'
To make sure the users are Business users I am protecting the views that way.
A couple questions that I am having trouble solving on my own are:
Now with that being repeated say 10 times, is there a cleaner way to do this, rather than having
def test_func in every CBV?
The other question that comes up, if the user doesn't pass test, it redirects to the login page, which I don't really like either. These views are all returning json. If the user does not pass test, I would like to just send them to something like,
JsonResponse({'message': 'Only company administrators have access to this view'})
How would I able to change that redirect only if the user does not pass test? Keeping in mind that these views also inherit from LoginRequiredMixin as well, in which if the user is not logged in I want to keep the original redirect to the login page in tact.
Any help with this is very appreciated. This side of Django is something fairly new to me!

Now with that being repeated say 10 times, is there a cleaner way to do this, rather than having def test_func in every CBV?
Yes, you can simply make a mixin that implements the check:
from django.contrib.auth.mixins import UserPassesTestMixin
class BusinessUserMixin(LoginRequiredMixin, UserPassesTestMixin):
def test_func(self):
return self.request.user.user_type == 'Business'
def handle_no_permission(self):
return JsonResponse(
{'message': 'Only company administrators have access to this view'}
)
and then you use this mixin in your views, for example:
class MyView1(BusinessUserMixin, ListView):
# …
pass
class MyView2(BusinessUserMixin, DetailView):
# …
pass
class MyView3(BusinessUserMixin, CreateView):
# …
pass
if the user doesn't pass test, it redirects to the login page, which I don't really like either. These views are all returning json. If the user does not pass test, I would like to just send them to something like.
You can override the handle_no_permission method as well, the view will return the result of this method as result when the test fails.

Related

Django admin add entry to specific groups

Hej!
I'm looking for a possibility to add entries by different users and groups.
In my app there are different users and different groups where one user can belong to multiple groups. The goal is that some of the groups have sensitive data so I only want members of this group to be able to read the entries but some data should be available in multiple groups.
It should also be possible to only see some information. (e.g. See the name but not the address)
For now I have the decorator #login_required for all my views and #permission_required for a persons_view. Therefore you have to be a registered user to see anything, which is great but not quite enough.
I also created groups (via the admin area) but can't filter the data for each model/view. The group either sees the data or they don't.
The registration of users is connceted to an already existing ldap system (where those groups are pre-defined. Would be great to use those!)
Is there a possibility to add entries only for a special group and view only some of the given information?
Thanks for the help!
If you want to restrict or specify a view for a group, you should use two mixins: UserPassesTestMixin and LoginRequiredMixin.
LoginRequiredMixin will just make sure the user in the request is logged in
UserPassesTestMixin will check the request from a boolean expression we define and if it returns True it will let the user in
We can set this up using a test_func method that will return either True or False
Here is an example that you can use in your view
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
class Index(LoginRequiredMixin, UserPassesTestMixin,View):
def get(self, request):
return render(request, 'nioulboy/index.html')
def test_func(self):
return self.request.user.groups.filter(name='Admin')
In the test_func method, we are returning true if the request.user has a group with the name Admin.
If it returns True, it will allow the user to view the index page if it returns False it will send back a 403 error.
for NOT class based views works this function to give permission to a whole view.
# views.py
from django.contrib.auth.decorators import login_required, user_passes_test
def user_check(user):
return user.groups.filter(name="Admin")
#user_passes_test(user_check)
#login_required
def view(request):
....

How can I restrict an authenticated user from posting on behalf of someone else?

If I am logged in as user1 and I am accessing a ViewSet called RecipeSubmissionViewSet, the POST takes a recipe_id of the recipe the user wants to submit. How do I ensure that user1 does not submit user2's recipe, assuming the Recipe model has an owner field on it and I can just compare it to request.user? Should I use a permission class for this or is there a better way? I'm speaking from an backend point of view and not taking into account that the front end would of course filter out the recipes that belong to the user and only show them their own recipes.
There can be two ways. You can filter out queryset or define permission class.
If you override get_queryset method like this.
class RecipeSubmissionViewSet(...):
def get_queryset(self):
return Recipe.objects.filter(owner=self.request.user)
# you can also use filtration based on action name like this
# if self.action == 'update':
# return Recipe.objects.filter(owner=self.request.user)
# return Recipe.objects.all()
User will get 404 response and will never be able to access objects other than he owns.
Second choice is permission class. You can define custom permission class and check ownership explicitly like this.
from rest_framework.permissions import BasePermission
class RecipeSubmissionPermission(BasePermission):
def has_object_permission(self, request, view, obj):
# you can also check permission here based on action
# if view.action == 'update':
# pass
return request.user.is_authenticated and obj.owner == request.user
class RecipeSubmissionViewSet(...):
permission_classes=[RecipeSubmissionPermission]
In this case user will get 403 permission error.
If you use both of these methods. 404 will be preferred.
You can use whichever method you want or both of these. Permission class looks more programmatic and structured way of doing it but user will know that object with this id exists but he did not have permission to update it. But if you override queryset, user is not even be able to know if object exists or not thus more secure.
Are you using the django authentication system? Then you should be able to access request.user in the views and set the owner field accordingly.
EDIT: I think I misunderstood the question.
But this could help and Nafees Anwar looks good.
Nafees answer is pretty much the way to go.
I have been developing microservices with multitenancy to users and the rules for them(as per my projecs spec) are:
Users cannot create items on behalf of another company/user
Users cannot view/edit items belonging to another company.
The way I do this is simple.
To prohibit viewing/editing of someone else's stuff
def get_queryset(self):
return self.queryset.filter(user=request.user)
And to prohibit editing of someone else's stuff, this is done on a serializer
class SomeSerializer(...):
def validate(self, data):
data.pop('user', None)
data['user'] = self.context['request'].user
With the above, the get_queryset in the viewset will always return a 404 if user1 requests user2 info.
And the validate function will prevent assignment. Ie. If user1 creates something assigned to user2, it will rather assign user1, this behaviour is what I needed in my application, you can always raise serializers.ValidationError("You cannot assign stuff to user2") instead if thats what you need instead of reassigning the user as I do in my use case. With having this logic in the validate you can be sure that any writable function will always carry the same behaviour for that serializer.
Hope that this helps.

Django REST - Serialize User.get_all_permissions

I am building an application with a Django Rest backend, and a VueJS front end and am working through authorization and authentication. I have the authentication working well, but am a bit stuck on letting the front end (VueJS) know what the user has authorization to do in terms of Add/Change/View/Delete for a model. For example, if a user cannot add a customer, I don't want to show the 'Add Customer button'.
Working through the Django docs, and solutions on StackOverflow, I believe the simplest way is to send the user's permissions from Django to VueJS.
The 'best'/'simplest' way I can see to get the permissions is with the following:
userModel = User.objects.get(request.user)
return User.get_all_permissions(userModel)
Where I am stuck is exactly where to put this logic and how to serialize it. Does the above belong in the View, Serializer, other? Up until now, I have only been working with Models (ModelSerializers and ModelViews), but I don't believe this falls into this category.
Thanks in advance...
You should add this logic to views, because the views are used to implement these kinds of logic.
Actually, you don't want to use serializers here, because of the response of .get_all_permissions() method is already in serialized form
Apart from that, your provided code is not good (it's clearly bad). It should be as below,
return request.user.get_all_permissions()
because, you'll get current logged-in user's instance through request.user, to get his/her permissions, you all need to call the get_all_permissions() method
Example
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
#permission_classes(IsAuthenticated, )
#api_view()
def my_view(request):
logged_in_user = request.user
return Response(data=logged_in_user.get_all_permissions())

Use proxy class instead of User for request.user

I have a meta class for the Django User model that I use to add extra methods (overly simplified version):
# project.models.pie_lover.py
from django.contrib.auth.models import User
class PieLover(User):
class Meta:
app_label = "core"
proxy = True
def likes_pie(self):
return True
In my view, I wish to get the logged in PieLover and see if he likes pie (silly thing to do because a PieLover always loves pie, but in a real world situation this may not be the case). My problem lies in the way Django logs in the users, I use the built-in login(request) function for this and as a result the object stored in request.user is a User object, not a PieLover.
# project.views.like_pie.py
from ..models.pie_lover import PieLover
def index(request):
pie_lover = request.user
if pie_lover.likes_pie():
print "Try pie, try!"
else:
print "BLASPHEMER!"
If I try to do this Django tells me that the User object has no method likes_pie which is to be expected as request.user is not a PieLover instance.
As a quick workaround I just get the PieLover which has the same ID as the User but that means an extra DB hit.
How can I make Django use PieLover by default in the request?
I was thinking that instead of making another database query to get the proper PieLover object to create a new PieLover object and pass request.user to it at initialization but I don't know what the implications of this are.
After poking around I found, what seems to me, the easiest and non-hackish way to access the PieLover methods for a given User instance. I have added this to a custom middleware:
from models.pie_lover import PieLover
class PieLoverMiddleware(object):
def process_request(self, request):
request.pie_lover = PieLover(request.user)
How can I make Django use PieLover by default in the request?
You don't.
Read this before you do anything else: https://docs.djangoproject.com/en/1.3/topics/auth/#storing-additional-information-about-users
Your "extension" to user should be a separate model with all of your extension methods in that separate model.
You can then navigate from User (in the request) to your extension trivially using the get_profile() method already provided.
from django.contrib.auth import models as auth_models
def _my_func(self):
return True
auth_models.User.my_func = _my_func

django.contrib.auth: how to keep site and admin sessions separate?

I'm using a user profile model with a ForeignKey to the User, authenticated by django.contrib.auth.
The same auth module is of course used for the admin interface, so if a superuser/staff member is logged in to the admin interface, and enters the main site, the site will accept session cookie and authenticate him. This creates a problem because a superuser/admin doesn't need to have a UserProfile and shouldn't be recognized by the main site.
What's the easiest way to solve this, so that the sessions from admin don't carry on to the site?
I dont think there is a way to solve exactly this,
"What's the easiest way to solve this, so that the sessions from admin don't carry on to the site?"
But depending on what you wnat to do, you may try,
don't create UserProfile for superuser
if request.user.is_superuser():
UserProf.objects.create(...)
I always have the problem where I want to keep a logged in Admin user and a logged in normal user, simultaneously, when I am developing. To do this, I have two entries in /etc/hosts
127.0.0.1 uswaretech.tld
127.0.0.1 admin.uswaretech.tld
Now, normal user always logs in via uswaretech.tld and admin always via admin.uswaretech.tld so thy can both be logge din simultaneously.
From a design standpoint your idea seems like a bit of a hack, but if you really want to do this you may use a middleware.
class MyMiddleware(object):
def process_request(self, request):
if request.user.is_authenticated:
try:
UserProfile.objects.get(user=request.user)
except UserProfile.DoesNotExist:
from django.contrib.auth.models import AnonymousUser
request.user = request._cached_user = AnonymousUser()
return None
This should be at the top of the middleware list to prevent possible side-effects.
if request.session.get ('has_account', False):
return HttpResponse ("You have no account, sorry.")
Then make sure, every user of your front-end gets, if his session is initiated, the value has_account set properly.