Django rest framework custom permission for ViewSet - django

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)

Related

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.

Allow different views for different types of users in django rest class based views

How do I write the following views using class based view?
#api_view(['GET', 'POST'])
def hotel_list(request):
# List all hotel or add new .
if request.method == 'GET':
if request.user.is_authenticated:
# Allow GET request for all authenticated users
hotels = models.Hotel.objects.all()
serializer = serializers.HotelSerializer(hotels, many=True)
return Response(serializer.data)
return Response({"message": "not authorized"}, status=status.HTTP_401_UNAUTHORIZED)
elif request.method == 'POST':
if request.user.is_superuser:
# Allow POST method for super users only
serializer = serializers.HotelSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
return Response({"message": "not authorized"}, status=status.HTTP_401_UNAUTHORIZED)
I want to allow different permissions for different groups of user.
First create custom-permissions
permissions.py
class CreateAdminOnly(permissions.BasePermission):
def has_permission(self, request, view):
if request.method == 'POST':
return request.user.is_superuser
return request.user.is_authenticated
def has_object_permission(self, request, view, obj):
if request.method == 'POST':
return request.user.is_superuser
return request.user.is_authenticated
use it with modelviewset in views.py
from rest_framework import mixins
from rest_framework.permissions import IsAuthenticated
from .permissions import CreateAdminOnly
class CreateListRetrieveViewSet(mixins.CreateModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet):
queryset = models.Hotel.objects.all()
serializer_class = HotelSerializer
permission_classes = [CreateAdminOnly]

Django Rest Framework restrict user data view to admins & the very own user

I am using Django and DRF, and I would like to check if a user (regular one), after it has been authenticated, is allowed to view it's own profile and only that (no other user's).
serializers.py
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('id', 'url', 'username', 'password', 'email', 'groups', 'is_staff')
def create(self, validated_data):
user = super().create(validated_data)
user.set_password(validated_data['password'])
user.save()
return user
Views.py
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all().order_by('-date_joined')
serializer_class = UserSerializer
permission_classes = (IsUser,)
permissions.py
class IsUser(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to edit it.
"""
def has_permission(self, request, view, obj):
# View or Write permissions are only allowed to the owner of the snippet.
return obj.owner == request.user
This, obviously is not working, because is wrong. But I can not figure out how to allow a user to view:
http://127.0.0.1:8000/api/users/7
ONLY if its an admin, or the very same user doing the request.
And:
http://127.0.0.1:8000/api/users/
Only if it's an admin.
Thanks!
class UserViewSet(ModelViewSet):
queryset = Message.objects.all()
serializer_class = UserSerializer
def get_permissions(self):
if self.action == 'list':
self.permission_classes = [IsSuperUser, ]
elif self.action == 'retrieve':
self.permission_classes = [IsOwner]
return super(self.__class__, self).get_permissions()
class IsSuperUser(BasePermission):
def has_permission(self, request, view):
return request.user and request.user.is_superuser
class IsOwner(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.user:
if request.user.is_superuser:
return True
else:
return obj.owner == request.user
else:
return False
override list and retrieve method for UserViewSet probably the easiest way.
My apologies in advanced as I don't have enough rep to add to Ykh's answer
views.py
class UserViewSet(ModelViewSet):
queryset = Message.objects.all()
serializer_class = UserSerializer
def get_permissions(self):
# Overrides to tightest security: Only superuser can create, update, partial update, destroy, list
self.permission_classes = [IsSuperUser]
# Allow only by explicit exception
if self.action == 'retrieve':
self.permission_classes = [IsOwner]
return super().get_permissions()
permissions.py
from rest_framework.permissions import BasePermission
class IsSuperUser(BasePermission):
def has_permission(self, request, view):
return request.user and request.user.is_superuser
class IsOwner(BasePermission):
def has_object_permission(self, request, view, obj):
if request.user:
if request.user.is_superuser:
return True
else:
return obj.owner == request.user
else:
return False
Note: obj.owner otherwise you will always be denied if you are not a superuser.
Thank you Ykh for the base answer.
Add a additional check in IsUser permission .
if request.method == permissions.SAFE_METHOD:
return True
like sample, list for default-auth, edit only for superuser
from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated, IsAdminUser
class CsrfExemptSessionAuthentication(SessionAuthentication):
def enforce_csrf(self, request):
return # To not perform the csrf check previously happening
class OnlyListAvaliableMixin:
authentication_classes = (CsrfExemptSessionAuthentication,)
def get_permissions(self):
permission_classes = [IsAuthenticated] if self.action == 'list' else [IsAdminUser] # noqa
return [permission() for permission in permission_classes]
# Example to use
class ListView(OnlyListAvaliableMixin, viewsets.ModelViewSet, ):
# premission_classes = (IsAdminUser,)
queryset = CarMark.objects.all()
serializer_class = ListViewSerializer

django rest framework - object permission does not trigger

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.