Custom permissions on viewset - django

Im trying to find a way to create custom permissions on a viewset.
Im want to implement somthing like this :
class ActivityViewSet(viewsets.ModelViewSet):
queryset = Activity.objects.all()
serializer_class = ActivitySerializer
if request.method == 'PUT':
permission_classes = (permissions.IsOwner)
elif request.method == 'LIST':
permission_classes = (permissions.IsAdmin)
else :
permission_classes = (permissions.AllowAny)
IE : sorting permissions by method. (the above code doesnt work cause "request" isnt recognized)
Couldn't find any use in the "Custom permissions" section of the documentation, but maybe Im just blind. (overriding BasePermission? how? who? where?)
Thanks :)

Viewsets use action to determine HTTP a verb and permissions invoked from get_permissions method, so you can override it:
class ActivityViewSet(viewsets.ModelViewSet):
queryset = Activity.objects.all()
serializer_class = ActivitySerializer
def get_permissions(self):
if self.action == 'update':
return IsOwner()
elif self.action == 'list':
return IsAdminUser()
else :
return AllowAny()

Spent some amount on the same problem. Here is the solution for DRF 3+
class ActivityViewSet(viewsets.ModelViewSet):
"""ViewSet for viewing and editing client's accounts."""
queryset = Activity.objects.all()
serializer_class = ActivitySerializer
def get_permissions(self):
"""Set custom permissions for each action."""
if self.action in ['update', 'partial_update', 'destroy', 'list']:
self.permission_classes = [IsAuthenticated, ]
elif self.action in ['create']:
self.permission_classes = [AllowAny, ]
return super().get_permissions()
Make sure that permission_classes is a list.

Related

ViewSet's action decorator is ignoring its permission_classes

Why is my action decorator ignoring its permission_class?
I have a ViewSet that has "IsAuthenticated" for both a generic post/create and for posting to a custom action decorator.
However, when I use the custom action decorator for a non-logged in user, the code for the action decorator still runs (and causes an error).
Why is this? Shouldn't a non-logged in user receive a 401_Unauthorized when posting to the action?
A non-logged in user does receive a 401_Unauthorized when doing a generic post.
Here is the ViewSet:
class ItemViewSet(viewsets.ModelViewSet):
queryset = Item.objects.all()
lookup_field = "pk"
serializer_class = ItemSerializer
def get_permissions(self):
if self.action == "create":
permission_classes = [IsAuthenticated]
else:
permission_classes = [AllowAny]
return [permission() for permission in permission_classes]
#action(
methods=["post"],
detail=True,
permission_classes=[IsAuthenticated],
url_name="actioned",
)
def actioned(self, request, pk=None):
try:
item = Item.objects.get(user=request.user)
item.status = "actioned"
item.save()
return Response(status=status.HTTP_200_OK)
except Item.DoesNotExist:
Item.objects.create(user=request.user, status="actioned")
return Response(status=status.HTTP_201_CREATED)
I'm using DefaultRouter() for my urls:
router = DefaultRouter
router.register(r"items", ItemViewSet, basename="item")
urlpatterns = [ path("", include(router.urls)), ]
Here are the tests:
# GENERIC POST BY ANONYMOUSUSER DOESN'T RUN AND GIVES 401
def test_public_generic_post(self):
payload = {"status": None}
response = APIClient.post(
reverse("app:item-list"),
payload,
format="json"
)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) # True
# ACTION POST BY ANONYMOUSUSER RUNS EVEN THOUGH IT HAS THE SAME IsAuthenticated permission
def test_public_action_post(self):
item = Item.objects.create()
response = APIClient.post(
reverse("app:item-actioned", args=[item.pk]),
format="json"
)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) # False
For the action post, I receive the following error:
TypeError: Cannot cast AnonymousUser to int. Are you trying to use it in place of User?
Because it's running my try: item = Item.objects.get(user=request.user) code and there is no request.user.
But why would it run the action decorator in the first place if the action decorator explicitly has permission_classes=[IsAuthenticated]?
I can confirm that request.user.is_authenticated is False.
As per #KutayAslan's comment, removing the get_permissions method resolves this error.
It looks like in my original code, the action decorators were being assigning [AllowAny] due to its "self.action" falling under the "else" clause.
Setting up the permission_classes to explicitly equal the action decorator resolves this issue:
def get_permissions(self):
if self.action == "create":
permission_classes = [IsAuthenticated]
elif self.action == "actioned":
permission_classes = [IsAuthenticated]
else:
permission_classes = [AllowAny]
return [permission() for permission in permission_classes]
Another possibility for anyone looking at this thread is that you mis-spelled the BasePermission functions as has_permissions and has_object_permissions (plural) instead of has_permission and has_object_permission (singular)!

Django Rest Framework Custom Permission does not recognize user

I am trying to enforce a custom permission in DRF. Specifically, I am trying to see whether a user is authorized to access an object. I adapted this solution from another post here but it does not quite work for me. The permission class always assumes that request.user is an Anonymous user. What am I missing?
permissions.py
class CanSeeWishlist(permissions.BasePermission):
def has_permission(self, request, view):
try:
wishlist = Wishlist.objects.get(
pk=view.kwargs['pk'])
except:
return False
if wishlist.private:
print(request.user) # Prints Anonymous User
if request.user.id == wishlist.owner.id or request.user.id in wishlist.members.all():
return True
return False
return True
api.py
class WishlistViewset(viewsets.ModelViewSet):
serializer_class = WishlistSerializer
queryset = Wishlist.objects.all()
authentication_classes = (TokenAuthentication,)
permission_classes = [
permissions.IsAuthenticatedOrReadOnly
]
def get_permissions(self):
if self.request.method == 'GET':
self.permission_classes = (CanSeeWishlist,)
return super(WishlistViewset, self).get_permissions()
I suspect that doing super(WishlistViewset, self).get_permissions() didn't pass request to the class so it resulted in Anonymous User or as you dropped IsAuthenticatedOrReadOnly something went wrong.
Anyway, I think it's better to override get_permissions() with the same way it was implemented, it will fix the problem I hope:
What get_permissions() actually do:
def get_permissions(self):
"""
Instantiates and returns the list of permissions that this view requires.
"""
return [permission() for permission in self.permission_classes]
So It should be:
def get_permissions(self):
permissions_list = [permissions.IsAuthenticatedOrReadOnly] #the base one that should be there for all request actions
if self.request.method == 'GET': # this will add the permission for all GET requests, If you want it to only apply on retrive you can do self.action == 'retrive':
permissions_list.append(CanSeeWishlist)
return [permission() for permission in permissions_list]

check permissions before perform_create() method applied

I need to check different types of permissions for different types of actions from request user. For example get permission only need [IsAuthenticated] but when user request perform_create method. I want to implement another permission that is CanCreateProject
permissions.py
class CanCreateProject(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.user.is_superuser:
return True
else:
return request.user.profile_limitation.can_create_project
views.py
class ProjectView(ModelViewSet):
serializer_class = ProjectSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
queryset = Project.objects.all()
organization = self.request.user.organization
query_set = queryset.filter(organization=organization)
return query_set
def perform_create(self, serializer):
self.permission_classes = [CanCreateProject] ## here
project = self.request.data["project_name"]
path = self.request.data["project_name"]
organization = self.request.data["organization"]
serializer.save(project_name=project, project_path=path, organization=organization)
How can I run the CanCreateProject method only for perform_create method is requested.
Override the get_permissions(...) method
class ProjectView(ModelViewSet):
serializer_class = ProjectSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
queryset = Project.objects.all()
organization = self.request.user.organization
query_set = queryset.filter(organization=organization)
return query_set
def get_permissions(self):
if self.action == 'create':
composed_perm = IsAuthenticated & CanCreateProject
return [composed_perm()]
return super().get_permissions()
# def perform_create(self, serializer):
# self.permission_classes = [CanCreateProject] ## here
#
# project = self.request.data["project_name"]
# path = self.request.data["project_name"]
# organization = self.request.data["organization"]
# serializer.save(project_name=project, project_path=path,
# organization=organization)
Notes:
You really don't need to use perform_create(...) method here
a possible dup: DRF Viewset remove permission for detail route
Update-1
You should implement the has_permission(..) method of the Permission class, not has_object_permission(...) method
from rest_framework import permissions
class CanCreateProject(permissions.BasePermission):
def has_permission(self, request, view):
if request.user.is_superuser:
return True
else:
return request.user.profile_limitation.can_create_project

Setting different permission classes for POST and GET while using djangorestframework's default router

I was just watching some online tutorial on how to use Django Rest Framework to create a basic REST API using their default router. Link to Docs
but then because he used a model viewset he had to add permission_classes to them which means all different types of requests whether its post or get or others it'll all take the same permission.
I was wondering if there's a way to give them different permission_classes depending on the type of request.
Yes, you can write your own permissions.
Just create some python file. I named it permissions.py:
from rest_framework.generics import get_object_or_404
from rest_framework.permissions import BasePermission
from company.models import Company
class IsGetOrIsAuthenticated(BasePermission):
def has_permission(self, request, view):
if request.method == 'GET':
return True
return request.user and request.user.is_authenticated
class IsGetOrIsCompanyOwner(BasePermission):
def has_permission(self, request, view):
if request.method == 'GET' or 'OPTIONS':
return True
elif request.method == 'DELETE':
company = get_object_or_404(Company, id=view.kwargs['pk'])
return request.user.user_type == 'moder' or company.owner == request.user
else:
company = get_object_or_404(Company, id=view.kwargs['pk'])
return company.owner == request.user or request.user.user_type == 'moder'
class IsModer(BasePermission):
def has_permission(self, request, view):
return request.user.user_type == 'moder'
After that you can use them in views.py
from company.permissions import IsGetOrIsAuthenticated, IsGetOrIsCompanyOwner, IsModer
class ActiveCompanyShortView(ModelViewSet):
authentication_classes = (TokenAuthentication,)
permission_classes = (IsGetOrIsAuthenticated,)
you can read more there
To achieve what you need, one possible solution is to override the get_permissions() of ViewSet.
def get_permissions(self):
"""
Instantiates and returns the list of permissions that this view requires.
"""
if self.action == 'list':
return [objects of permissions_u_need_in_list_view]
elif self.action == 'create':
return [objects of permissions_u_need_in_create_view]
This is what DRF provides the definition of get_permissions().
def get_permissions(self):
"""
Instantiates and returns the list of permissions that this view requires.
"""
return [permission() for permission in self.permission_classes]

Different Permissions using decorators for each methods inside ModelViewset methods for "list", "create" , "retrieve", "update"

I want to add different permissions for different methods of ModelViewset class using decorator.
I tried:
class abcd(viewsets.ModelViewSet):
#permission_classes(IsAuthenticated,))
def list(self, request, format=None):
try:
#permission_classes(CustomPermission,))
def create(self, request, format=None):
try:
But it isn't working.
I also tried using #method_decorator. That didn't worked either.
I know we can do in the following way:
def get_permissions(self):
if self.action == 'create':
return [IsAuthenticated(), ]
return super(abcd, self).get_permissions()
But I was wondering if we can achieve this using decorators for Django Rest Framework.
ModelViewSet inherits Mixin classes and GenericAPIView. The methods list and create are from Mixins hence decorating with permission_classes won't work. Instead, you can try overriding get_permissions in APIView.
def get_permissions(self):
if self.request.method == "GET":
return [IsAuthenticated()]
elif self.request.method == "POST":
return [CustomPermission()]
return [permission() for permission in self.permission_classes]
Note: I am not sure whether above code works or not
a sample code is shown in official page.
so it might be better not using self.request.method but self.aciton property.
def get_permissions(self):
"""
Instantiates and returns the list of permissions that this view requires.
"""
if self.action == 'list':
permission_classes = [IsAuthenticated]
else:
permission_classes = [IsAdmin]
return [permission() for permission in permission_classes]
https://www.django-rest-framework.org/api-guide/viewsets/#introspecting-viewset-actions