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.
Related
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)
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)
I am a beginner learning django rest framework and I just encounter this error and I can't seem to find a way around it. Here is the permissions.py sample code
from rest_framework import permissions
class UpdateOwnProfile(permissions, BaseException):
"""Allow user to edit their own profile"""
def has_object_permission(self, request, view, obj):
"""Check if user is trying to update their own profile"""
if request.method in permissions.SAFE_METHODS:
return True
return obj.id == request.user.id
Here is also a sample of the views.py sample codes
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework import viewsets
from rest_framework.authentication import TokenAuthentication
from profiles_api import serializers
from profiles_api import models from profiles_api import permissions
class HelloApiView(APIView): """Test Api view""" serializer_class = serializers.HelloSerializer
def get(self, request, format=None):
"""Returns a list of Api features"""
an_apiview = [
'Uses HTTP methods as function (get, post, patch, put, delete)',
'Is similar to a traditional Django view',
'Gives you the most control over your application logic',
'Is mapped manually to URLs',
]
return Response({'message': 'Hello', 'an_apiview': an_apiview})
def post(self, request):
"""Create a hello message with our name"""
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
name = serializer.validated_data.get('name')
message = f'Hello {name}'
return Response({'message': message})
else:
return Response(
serializer.errors,
status = status.HTTP_400_BAD_REQUEST
)
def put(self, request, pk=None):
"""Handling updates of objects"""
return Response({'method': 'PUT'})
def patch(self, request, pk=None):
"""Handle a partial update of an object"""
return Response({'method': 'PATCH'})
def delete(self, request, pk=None):
"""Delete an object"""
return Response({'method': 'DELETE'})
class HelloViewset(viewsets.ViewSet): """Test API Viewset""" serializer_class = serializers.HelloSerializer
def list(self, request):
"""Return a hello message"""
a_viewset = [
'Uses actions (list, create, retrieve, update, partial update'
'Automatically maps to URLs using router'
'provides more functionality with less code'
]
return Response({'message': 'Hello', 'a_viewset': a_viewset})
def create(self, request):
"""Create a new hello message"""
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
name = serializer.validated_data.get('name')
message = f'Hello {name}!'
return Response({'message': message})
else:
return Response(
serializer.errors,
status=status.HTTP_400_BAD_REQUEST
)
def retrieve(self, request, pk=None):
"""Handle getting an object by its ID"""
return Response({'http_method': 'GET'})
def update(self, request, pk=None):
"""Handle updating an object"""
return Response({'http_method': 'PUT'})
def partial_update(self, request, pk=None):
"""Handle updating of an object"""
return Response({'http_method': 'PATCH'})
def destroy(self, request, pk=None):
"""Handle removing an object"""
return Response({'http_method': 'DELETE'})
class UserProfileViewSet(viewsets.ModelViewSet): """Handle creating and updating profiles""" serializer_class = serializers.UserProfileSerializer queryset = models.UserProfile.objects.all() authentication_classes = (TokenAuthentication,) permission_classes = (permissions.UpdateOwnProfile,)
And while running the development server I get this error:
class UpdateOwnProfile(permissions, BaseException): TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
permissions (rest_framework.permissions) is of type module (whose type is type FWIW), but the type of BaseException is type (like all regular classes). So you have a metaclass conflict as expected.
Presumably, you meant to use permissions.BasePermission class from the module:
class UpdateOwnProfile(permissions.BasePermission, BaseException):
...
...
You can also import and refer the class directly:
from rest_framework.permissions import BasePermission
class UpdateOwnProfile(BasePermission, BaseException):
...
...
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)
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