django rest framework - object permission does not trigger - django

I have the following setup:
permission.py
def get_permission_level(request, obj):
try:
level = PasswordListACL.objects.get(list=obj, user=request.user)
except PasswordListACL.DoesNotExist:
level = None
return level
class IsPasswordListOwner(permissions.DjangoObjectPermissions):
def has_permission(self, request, view):
return True
def has_object_permission(self, request, view, obj):
print(get_permission_level(request, obj))
if get_permission_level(request, obj) == None:
print('false')
return False
else:
level = AccessLevel.objects.get(pk=get_permission_level(request, obj).level_id).name
if level == 'Owner':
return True
else:
return False
I can confirm through my print('false') that the permission is being acted on and it is returning the correct value (I'm expecting False) however the view is still returning the data instead of a 403.
views.py
class PasswordListViewSet(viewsets.ModelViewSet):
queryset = PasswordList.objects.all()
serializer_class = PasswordListSerializer
permission_classes = (IsPasswordListOwner,)
def list(self, request):
self.permission_classes = [IsPasswordListOwner, ]
queryset = self.get_queryset().filter(passwordlistacl__user=request.user)
serializer = PasswordListSerializer(queryset, many=True, context={'request': request})
return Response(serializer.data)
def retrieve(self, request, pk=None):
self.permission_classes = [IsPasswordListOwner, ]
queryset = self.get_queryset()
if get_object_or_404(queryset, pk=pk):
password = get_object_or_404(queryset, pk=pk)
else:
pass
serializer = PasswordListSerializer(password, context={'request': request})
return Response(serializer.data)
edit - changing the return on has_permission confirms the permission there is overriding my has_object_permission?
Doing this gives me a 403 (correct):
def has_permission(self, request, view):
return False

The documentation states:
If you're writing your own views and want to enforce object level permissions, or if you override the get_object method on a generic view, then you'll need to explicitly call the .check_object_permissions(request, obj) method on the view at the point at which you've retrieved the object.
So since you're not calling get_object() you need to be calling self.check_object_permissions(self.request, obj) at some point.

Related

Django rest framework custom permission for ViewSet

this is my modelViewSet
class UserViewSet(viewsets.ModelViewSet):
def list(self, request):
users = User.objects.all()
serializer = UserSerializer(users, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def create(self, request):
serializer = UserSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
pass
def retrieve(self, request, pk):
user = get_object_or_404(User, pk=pk)
serializer = UserSerializer(user)
return Response(serializer.data, status=status.HTTP_200_OK)
def get_permissions(self):
if self.action == "list":
permission_classes = [
IsAdminUser,
]
elif self.action == "create":
permission_classes = [AllowAny]
else:
permission_classes = [AccountOwnerPermission]
return [permission() for permission in permission_classes]
and this is the custom permission class
class AccountOwnerPermission(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
print(object)
print(request.user)
return obj == request.user
i access this view from another user and it show me user retrieve, and that 2 prints on
AccountOwnerPermission won't run. can someone tell me what is wrong with what i did and why has_object_permission wont run.
i change has_object_permission to has_permission and it works, but i dont have access to obj on the other hand
From the docs:
If you're writing your own views and want to enforce object level permissions, or if you override the get_object method on a generic view, then you'll need to explicitly call the .check_object_permissions(request, obj) method on the view at the point at which you've retrieved the object.
So you'll need to call check_object_permissions in your retrieve to be able to trigger has_object_permission:
def retrieve(self, request, pk):
user = get_object_or_404(User, pk=pk)
self.check_object_permissions(request, user) # Add this line
serializer = UserSerializer(user)
return Response(serializer.data, status=status.HTTP_200_OK)

has_object_permission not working for detail action decorator?

I have an private action decorator for a User View. I want the action to be accessible only for the User in question.
# views.py
class UserViewSet(viewsets.ModelViewSet):
queryset = get_user_model().objects.all()
serializer_class = UserSerializer
#action(detail=True, permission_classes=[IsSelf])
def private(self, request, pk):
user = get_object_or_404(get_user_model(), pk=pk)
data = UserPrivateSerializer(user).data
return Response(data, status=status=HTTP_200_OK)
# permissions.py
class IsSelf(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return obj == request.user
However, it looks like anyone can go to my private action - even if I explicitly declare IsSelf to be False:
class IsSelf(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
# This has no effect
return False
What am I missing?
FYI:
The instance-level has_object_permission(...) method will only be called if the view-level has_permission(...) checks have already passed. Since it is inherited from BasePermission, the has_permission(...) is already returning the True value.
The has_object_permission(...) method is getting called when you call the .get_object() method of the GenericAPIView.
class UserViewSet(viewsets.ModelViewSet):
queryset = get_user_model().objects.all()
serializer_class = UserSerializer
#action(detail=True, permission_classes=[IsSelf])
def private(self, request, *args, **kwargs):
user = self.get_object()
data = UserPrivateSerializer(user).data
return Response(data, status=status.HTTP_200_OK)

DRF Custom Permission is not firing

I wrote a custom permission class for a drf project to protect my view:
views.py
class Employee(APIView):
permission_classes = [BelongsToClient]
serializer_class = EmployeeSerializer
def get(self, request, pk, format=None):
employee = EmployeeModel.objects.get(pk=pk)
serializer = EmployeeSerializer(employee, many=False)
return Response(serializer.data)
def delete(self, request, pk, format=None):
employee = EmployeeModel.objects.get(pk=pk)
employee.Employees_deleted = True
employee.save()
return Response(status=status.HTTP_200_OK)
My permission class:
permission.py
from rest_framework import permissions
class BelongsToClient(permissions.BasePermission):
message= "You are only authorized to view objects of your client"
"""
Object-level permission to only see objects of the authenticated users client
"""
def has_object_permission(self, request, view, obj):
if obj.Mandant == request.user.Mandant:
return True
else:
return False
Unfortunatly this permission class isn't blocking my view even when it should. I dont know why. Did I miss something?
You need to call check_object_permissions method before response for APIView
class Employee(APIView):
permission_classes = [BelongsToClient]
serializer_class = EmployeeSerializer
def get(self, request, pk, format=None):
employee = EmployeeModel.objects.get(pk=pk)
serializer = EmployeeSerializer(employee, many=False)
self.check_object_permissions(request, employee)
return Response(serializer.data)
has_object_permission only called when you use the DestroyAPIView or RetrieveAPIView or ViewSet.
Try to use a viewset just like below
from rest_framework import viewsets
class Employee(viewsets.ViewSet):
permission_classes = [BelongsToClient]
serializer_class = EmployeeSerializer
def delete(self, request, pk, format=None):
employee = EmployeeModel.objects.get(pk=pk)
self.check_object_permissions(request, employee)
employee.Employees_deleted = True
employee.save()
return Response(status=status.HTTP_200_OK)
Note: I didn't test it but it should work.

DestroyAPIView Django rest validation

class DeleteLedgerCategory(DestroyAPIView):
serializer_class = CategorySerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
queryset = Category.objects.filter(company = self.request.user.currently_activated_company, id=self.kwargs['pk'])
return queryset
def preform_destroy(self, instance):
if instance.is_default == True:
raise ValueError("Cannot delete default system category")
return instance.delete()
In above class based view. I need to add custom validation error message. ie. if instance.is_default == True: raise error... and only allow to delete the instance if no error encounters. If any unclear question. Do comment
Instead of just raise error you can customize response in destroy method:
from rest_framework.response import Response
class DeleteLedgerCategory(DestroyAPIView):
serializer_class = CategorySerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
queryset = Category.objects.filter(company = self.request.user.currently_activated_company, id=self.kwargs['pk'])
return queryset
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
if instance.is_default == True:
return Response("Cannot delete default system category", status=status.HTTP_400_BAD_REQUEST)
self.perform_destroy(instance)
You can use the destroy method here but you have to return a response whether it is successful or not
from rest_framework.response import Response
from rest_framework import status
class DeleteLedgerCategory(DestroyAPIView):
serializer_class = CategorySerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
queryset = Category.objects.filter(company = self.request.user.currently_activated_company, id=self.kwargs['pk'])
return queryset
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
if instance.is_default == True:
return Response("Cannot delete default system category", status=status.HTTP_403_FORBIDDEN)
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)

django rest framework - browsable api remove delete

I have the following view:
def retrieve(self, request, pk=None, **kwargs):
try:
instance = self.get_object()
self.check_object_permissions(self.request, instance)
serializer = PasswordFolderSerializer(instance, context={'request': request})
return Response(serializer.data)
except Http404:
return Response(status=status.HTTP_404_NOT_FOUND)
When not logged in I will get a 403 which is good however the "DELETE" button still shows in the browsable API. how do I get rid of this? Here is my permission:
class CanRetrievePasswordFolder(permissions.DjangoObjectPermissions):
def has_permission(self, request, view):
if request.user is None:
return False
else:
return True
def has_object_permission(self, request, view, obj):
access_levels = ['Owner', 'Admin', 'Read']
if get_permission_level(request, obj) is None:
return False
else:
level = AccessLevel.objects.get(pk=get_permission_level(request, obj).level_id).name
if request.method in permissions.SAFE_METHODS:
return True
else:
for access in access_levels:
if level == access:
return True
else:
return False
really stupid, I had to add IsAuthenticated to my permissions tuple on the view like so:
permission_classes_by_action = {'create': [CanCreatePasswordFolder, IsAuthenticated],
'list': [CanListPasswordFolder, IsAuthenticated],
'retrieve': [CanRetrievePasswordFolder, IsAuthenticated],
'partial_update': [CanUpdatePasswordFolder, IsAuthenticated],
'update': [CanUpdatePasswordFolder, IsAuthenticated],
'destroy': [CanDestroyPasswordFolder, IsAuthenticated]}