Let's say I want to fetch a user profile. I want to allow it to the profile owners or users belonging to the administrator group. I know how to do these two things separately.
For owners:
class IsOwner(BasePermission):
def has_permission(self, request, view):
return True
def has_object_permission(self, request, view, object):
if request.user.is_authenticated:
return object.name == request.user.name
return False
And for the admin group:
from django.contrib.auth.mixins import PermissionRequiredMixin
class MyView(PermissionRequiredMixin, View):
permission_required = ('polls.can_open', 'polls.can_edit')
But how to allow either of the above? Is it possible to check user groups and permissions inside the has_object_permission method, without additional database calls?
Related
I am using Django Guardian to have object-level permission along with global permissions. Some of the users have a group with global permissions, and some have object-level permissions. With this, I seem to need to modify the PermissionRequiredMixin to check also for the object-level permission.
views.py
class MainPageView(PermissionRequiredMixin, TemplateView):
permission_required = "app.view_mainpage"
template_name = "web/mainpage.html"
This one works if the user has global permission but if the user is under a group with object-level permission, it won't. With guardian, to check for object-level permission, you also have to pass the object instance.
example:
self.request.user.has_perm('view_mainpage', obj)
And on PermissionRequiredMixin, the checking goes only like this, self.request.user.has_perms(perms)
So if the user has a group with permission of view_mainpage of a specific object, how can I check it too? By the way, all my permissions are of the same content_type. Is there any way I can execute this? Like I have to pass the object instance to PermissionRequiredMixin if the user is under an object-level group and None if the user is under a global group.
You can implement such mixin yourself with:
from django.core.exceptions import PermissionDenied
class ObjectPermissionRequiredMixin:
object_permission_required = None
object_permission_denied_message = None
def has_object_permission(self, obj):
return self.request.user.has_perm(self.object_permission_required, obj)
def get_object_permission_denied_message(self):
return self.object_permission_denied_message
def handle_no_object_permission(self):
raise PermissionDenied(self.get_object_permission_denied_message())
def get_object(self, *args, **kwargs):
obj = super().get_object(*args, **kwargs)
if not self.has_object_permission(obj)
return self.handle_no_object_permission()
return obj
Then you can mix this mixin into a view, for example:
class MyUpdateView(ObjectPermissionRequiredMixin, UpdateView):
model = MyModel
object_permission_required = 'app.update_my_model'
object_permission_denied_message = 'You can not edit this object'
This will work for all Views that usee the SingleObjectMixin [Django-doc] such as a DetailView, UpdateView, and the DeleteView.
I implemented DRF as per the document. At one point I figured out, once the user is authenticated, the user is allowed to fetch data of any user in the systems.
I have implemented filtering as per this document.
I read through the permission document and could not find a way to filter out queryset based on the owner. In my one of the views, I am checking if the owner is same as the user who requested.
My question is, Do I have to do the same in all viewsets? or There is a general way where I can check this condition?
Not sure, if it is the best way, but I do it by overriding get_queryset
def get_queryset(self):
queryset = YOUR_MODEL.objects.filter(user_id=self.request.user.id)
return queryset
Doing it, using permisson class
class IsInUserHierarchy(permissons.BasePermission):
def has_permission(self, request, view):
return bool(isinstance(request.user, UserClassHierarchy))
Some explanations. IsInUserHierarchy class is very similar to IsAdminUser. It checks, if request.user is in the required class (import UserClassHierarchy from models), using simple python isinstance() method
Just create a permissions file, and add something like this:
class IsOwner(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
# Instance must have an attribute named `owner`.
return obj.owner == request.user
Then, in your ViewSet, use this permission class:
class MyViewSet(viewsets.ViewSet):
permission_classes = (IsOwner,)
Now, just import your permissions file anywhere you want to use this logic and you don't have to duplicate any code
Old question but for anyone curious, you can still create follow the general procedure as outlined by Dalvtor and Django/DRF docs.
Your viewset makes a call to check the object through:
self.check_object_permissions(self.request, obj)
With your custom permission, you need to check if it is iterable and iterate and check each object in the queryset:
from rest_framework import permissions
from collections.abc import Iterable
class IsOwner(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
# For Get Queryset (List)
if isinstance(obj, Iterable):
for o in obj:
if o.user != request.user:
return False
# For Get Object (Single)
elif obj != request.user:
return False
return True
I would like to update a instance of a model only if the request author is same than instance author.
I guess can do it in the update method:
def update(self, request, *args, **kwargs):
if request.user == self.get_object().user
do_things()
How can I do it? Is it obligatory to write an update en every ModelViewSet or ListAPIView? or is there a method to write a custom permission to accomplish this.
You can implement a custom permission. The following example is from the docs, modified to fit your use case:
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Object-level permission to only allow owners of an object to edit it.
Assumes the model instance has an `user` attribute.
"""
def has_object_permission(self, request, view, obj):
# Read permissions are allowed to any request,
# so we'll always allow GET, HEAD or OPTIONS requests.
if request.method in permissions.SAFE_METHODS:
return True
# Instance must have an attribute named `user`.
return obj.user == request.user
I wont be needing to write codes much,I just need an idea.I am trying to make users in two different categories.That is a User can either have a Profile or Shop both not both.
class Profile(models.Model):
user=models.OneToOneField(User,on_delete=models.CASCADE,primary_key=True,editable=True)
class Shop(models.Model):
user=models.OneToOneField(User,on_delete=models.CASCADE,primary_key=True,editable=True)
I want all users to either have one of both,They will register with the same form without being able to switch from one to another once they have registered for it.As at now what I'm doing is If i dont want a shop-owned User to have the previlege of a profile i do
def post_product(request):
try:
if bool(request.user.profile)==False:
.....
except:
pass
But I have to do this for all views that require previledges. I want something site-wide that i can put in settings or put at the view like #login_required .Please how can I go about this.Thanks
As per answer given by Prabhakar, you should use django auth groups.
But, if you want to write your custom permission class, you could also do it.
1) Assuming file name -> self_defined_permission.py
from rest_framework import permissions
from django.contrib.auth.models import Group
class ShopPermissions(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
'''
check user group. Assuming you keep users having profile in group 1 and shop in group 2
'''
shop_gp = Group.objects.get(id=2)
if shop_gp in request.user.groups.all():
return True
else:
return False
'''
similarly you could write permission class for profile owners
'''
2) In your class based view
from self_defined_permission import ShopPermissions
class SomeView(generics.ListCreateAPIView):
...
permission_classes = (ShopPermissions, )
3) In case you are using function based view.
from rest_framework.decorators import permission_classes
#permission_classes((ShopPermissions, ))
def some_view(request):
....
Try the user_passes_test decorator:
from django.contrib.auth.decorators import user_passes_test
def is_shop(user):
return Shop.objects.filter(user=user).exists()
def is_normal_user(user):
return Profile.objects.filter(user=user).exists()
#user_passes_test(is_shop)
def only_for_shops(request):
return render(request, 'edit_price_list.html')
I am trying to implement TokenAuthentication using the Rest Framework, but it seems that I can't add my own custom decorators to my ViewSets because they are evaluated BEFORE the authentication. Consider this:
from django.utils.decorators import method_decorator
from django.http.response import HttpResponseForbidden
def require_staff(View):
def staffOnly(function):
def wrap(request, *args, **kwargs):
if request.user.is_active and request.user.is_staff:
return function(request, *args, **kwargs)
else:
return HttpResponseForbidden()
return wrap
View.dispatch = method_decorator(staffOnly)(View.dispatch)
return View
When I try to implement this, it seems the decorator code fires first, so the authentication is never run.
#require_staff
class CustomerViewSet(ModelViewSet):
model = Customer
filter_class = CustomerFilter
filter_backends = (DjangoFilterBackend,)
Since request.user is never set, introducing the decorator breaks authentication.
I think the issue is that Authentication is occuring the rest_frameworks dispatch() function and it is not clear to me how I could add additional (say) custom security if authentication is done that late in the game.
Am I missing something here, or what is the proper way to implement this customization?
Someone suggested using Permissions for this instead. I assume they mean custom DRF Permissions, right?
Everything you need to know is DRF permissions is here: http://www.django-rest-framework.org/api-guide/permissions
DRF provides a built in permission that is similar to yours, called IsAdminUser
The IsAdminUser permission class will deny permission to any user,
unless user.is_staff is True in which case permission will be allowed.
This permission is suitable is you want your API to only be accessible
to a subset of trusted administrators.
To use this permission in a Class Based View:
class ExampleView(APIView):
permission_classes = (IsAdminUser,)
Now you have two options to do an extra check for user.is_active.
The first is override the IsAdminUser permission, like so:
from rest_framework import permissions
class IsActiveAndAdminUser(permissions.IsAdminUser):
"""Only allow a user who is Admin and Active to view this endpoint. """
def has_permission(self, request, view):
is_admin = super(IsAdminAndActiveUser, self).has_permission(request, view)
return request.user.is_active and is_admin
The second is to create an IsActiveUser permission, and chain them in your view.
IsActiveUser Permission:
from rest_framework import permissions
class IsActiveUser(permissions.BasePermission):
""" Only Active Users have permission """
def has_permission(self, request, view):
return request.user.is_active
Your new permission list in your class based view:
class ExampleView(APIView):
permission_classes = (IsActiveUser, IsAdminUser,)