I have used ListCreateAPIView and RetrieveUpdateDestroyAPIView for a model. Now I want to add JWT authentication to only the Update and Destroy part in the RetrieveUpdateDestroyAPIView. How can I do that?
Let me make my question a bit more clear. I have a model named Post. Now All users are allowed to view the post but update, delete is only available to the user who created it. And I want to use JWT Authentication.
You can write custom permission class for this:
from rest_framework import permissions
class CustomPermission(permissions.BasePermission):
def has_permission(self, request, view):
if view.action in ('update', 'destroy'):
return request.user.is_authenticated
return True
And use in in your view:
class ExampleView(RetrieveUpdateDestroyAPIView):
permission_classes = (CustomPermission,)
we can override the method get_authenticators and don't forget to add authentication_classes to api view.
def get_authenticators(self):
if self.request.method in ['PUT', 'DELETE']:
return [auth() for auth in self.authentication_classes]
else:
return []
For your question update we need to add object level permissions like below
class OwnerRequiredPermission(object):
def has_object_permission(self, request, obj):
return obj.created_by == request.user
add above permission class to permission_classes
Related
I trying to understand permissions in Django. In the points below I wrote what I was able to find on this issue and how I understand it. Maybe someone will be able to tell me if I am thinking correctly or if it works in a different way. I will be grateful for additional articles on this topic. Thanks!
1. APIView with built-in permissions
Django has some built-in permissions that we can use in APIView through permission_classes.
class OrderView(APIView):
permission_classes = [permissions.IsAuthenticated]
We can add the following built-in permissions to permission_classes, because they don’t have has_object_permission:
AllowAny, IsAuthenticated, IsAuthenticatedOrReadOnly, IsAdminUser, DjangoModelPermissions, DjangoModelPermissionsOrAnonReadOnly
2. APIView with custom permissions
Let’s say that we want to use the custom permission from the point 1 in APIView.
Can we use permission_classes in that case?
class OrderView(APIView):
permission_classes = [permissions.IsAuthenticatedAndOwner]
I don’t know which object will be sent to has_object_permission and how to do it. Is it a good way?
def has_object_permission(self, request, view, obj): # <-- obj
return obj.user == request.user
3. APIView with check_permissions and check_object_permissions.
I am a little confused about the methods. If I understood correctly these methods are used to call has_permission and has_object_permission from APIView? Is there a different reason to use it? Do we use it only with APIView?
4. ViewSet with custom permissions
Let’s say that we want to use the custom permission from the point 1 but with ViewSet
class MessageViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticatedAndOwner]
queryset = Message.objects.all()
serializer_class = MessageSerializer
def perform_create(self, serializer):
serializer.save(author=self.request.user)
In that case what will be sent as obj to has_object_permission?
def has_object_permission(self, request, view, obj): # <-- obj
return obj.user == request.user
Will it be data from queryset?
I have one model which has user as its ForeignKey attribute which is auto fill ie. logged in user is filled there. I have made token authentication. Only Authenticated // i mean authorized users can visit that view. But i am planning to make such that only the user which had created that model object can only update the content of that object.
For example:
class Something(models.Model):
sth_name = models.CharField(max_length=18)
sth_qty = models.IntegerField()
user = models.ForeignKey(User)
on my View:
I override perform_create() to associate to above model automaticall.
def perform_create(self, serializer):
return serializer.save(user=self.request.user)
What do i exactly need to do? I have to write some permissions method, But I am really stuck.
Yes, you need to create an object level permission. The DRF tutorial covers this nicely here: http://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/#object-level-permissions
Specifically, create a file permissions.py in your app, and add this permission there:
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to edit it.
"""
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.user == request.user
Then, in your view class which has the update resource for the Something model (probably SomethingDetail), add the permission_classes field:
class SomethingDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Something.objects.all()
serializer_class = SomethingSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
Just add the user when retrieving the object
obj = get_object_or_404(Something, pk=pk, user=request.user)
Note that this will throw 404. If you want 403 error, use custom condition to check the user and raise PermissionDenied. If you want to do this for multiple views, put the condition logic in a decorator.
I am confused with the BasePermission in Django-rest-framework.
Here I defined a class: IsAuthenticatedAndOwner.
class IsAuthenticatedAndOwner(BasePermission):
message = 'You must be the owner of this object.'
def has_permission(self, request, view):
print('called')
return False
def has_object_permission(self, request, view, obj):
# return obj.user == request.user
return False
Using in views.py
class StudentUpdateAPIView(RetrieveUpdateAPIView):
serializer_class = StudentCreateUpdateSerializer
queryset = Student.objects.all()
lookup_field = 'pk'
permissions_classes = [IsAuthenticatedAndOwner]
But it doesn't work at all. Everyone can pass the permission and update the data.
The called wasn't printed.
And I used to define this class: IsNotAuthenticated
class IsNotAuthenticated(BasePermission):
message = 'You are already logged in.'
def has_permission(self, request, view):
return not request.user.is_authenticated()
It works well in the function
class UserCreateAPIView(CreateAPIView):
serializer_class = UserCreateSerializer
queryset = User.objects.all()
permission_classes = [IsNotAuthenticated]
So, what are the differences between the examples above, and function has_object_permission & has_permission?
We have following two permission methods on BasePermission class:
def has_permission(self, request, view)
def has_object_permission(self, request, view, obj)
Those two different methods are called for restricting unauthorized users for data insertion and manipulation.
has_permission is called on all HTTP requests whereas, has_object_permission is called from DRF's method def get_object(self). Hence, has_object_permission method is available for GET, PUT, DELETE, not for POST request.
In summary:
permission_classes are looped over the defined list.
has_object_permission method is called after has_permission method returns value True except in POST method (in POST method only has_permission is executed).
When a False value is returned from the permission_classes method, the request gets no permission and will not loop more, otherwise, it checks all permissions on looping.
has_permission method will be called on all (GET, POST, PUT, DELETE) HTTP request.
has_object_permission method will not be called on HTTP POST request, hence we need to restrict it from has_permission method.
Basically, the first code denies everything because has_permission return False.
has_permission is a check made before calling the has_object_permission. That means that you need to be allowed by has_permission before you get any chance to check the ownership test.
What you want is:
class IsAuthenticatedAndOwner(BasePermission):
message = 'You must be the owner of this object.'
def has_permission(self, request, view):
return request.user and request.user.is_authenticated
def has_object_permission(self, request, view, obj):
return obj.user == request.user
This will also allow authenticated users to create new items or list them.
I think this can help:
class IsAuthorOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
# Read-only permissions are allowed for any request
if request.method in permissions.SAFE_METHODS:
return True
# Write permissions are only allowed to the author of a post
return obj.user == request.user
has_permission() is a method on the BasePermission class that is used to check if the user has permission to perform a certain action on the entire model. For example, you might use it to check if a user has permission to view a list of all objects of a certain model.
has_object_permission() is a method on the BasePermission class that is used to check if the user has permission to perform a certain action on a specific instance of the model. For example, you might use it to check if a user has permission to view, update or delete a specific object of a certain model.
For example, you might have a Book model and a User model in your application. You could use has_permission() to check if a user has permission to view a list of all books, while you use has_object_permission() to check if a user has permission to view, update or delete a specific book.
class IsBookOwnerOrAdmin(permissions.BasePermission):
def has_permission(self, request, view):
# Check if the user is authenticated
if not request.user.is_authenticated:
return False
# Allow access for superusers
if request.user.is_superuser:
return True
# Allow access if the user is the owner of the book
if request.method in permissions.SAFE_METHODS:
return True
return False
def has_object_permission(self, request, view, obj):
# Allow access for superusers
if request.user.is_superuser:
return True
# Allow access if the user is the owner of the book
return obj.owner == request.user
As far as I can see, you are not adding your custom permission to the class as an argument.
This is your code:
class StudentUpdateAPIView(RetrieveUpdateAPIView):
serializer_class = StudentCreateUpdateSerializer
queryset = Student.objects.all()
lookup_field = 'pk'
permissions_classes = [IsAuthenticatedAndOwner]
But it should be:
class StudentUpdateAPIView(RetrieveUpdateAPIView, IsAuthenticatedAndOwner):
serializer_class = StudentCreateUpdateSerializer
queryset = Student.objects.all()
lookup_field = 'pk'
permissions_classes = [IsAuthenticatedAndOwner]
Note the custom permission IsAuthenticatedAndOwner as an argument in the class header.
PS: I hope this helps, I am a beginner in DRF but this is one of the things I just learned.
I'm building a Django API view by extending the rest_framework.views.APIView class.
I have successfully built many APIs that are only callable by an authenticated user. I have done this by adding: permission_classes = [permissions.IsAuthenticated,]
There are some APIs that I only want unauthenticated users to call. Such as "ForgotPassword". Basically, I want to ensure that the API caller doesn't send in the JWT Token in the request header. How can I enforce that? There is no permissions.IsUnAuthenticated.
you can easily create your own IsNotAuthenticated class
something like this:
from rest_framework.permissions import BasePermission
class IsNotAuthenticated(BasePermission):
"""
Allows access only to non authenticated users.
"""
def has_permission(self, request, view):
return not request.user.is_authenticated()
then: permission_classes = (myapp.permissions.IsNotAuthenticated,)
regards.
In case you are using function based view then it would be good if you use the following.
from django.contrib.auth.decorators import user_passes_test
#user_passes_test(lambda u: not u.is_authenticated())
Or you can do it in permissions.py like this (For who were getting bool object error)
from rest_framework import permissions
class IsNotAuthenticated(permissions.BasePermission):
def has_permission(self, request, view):
return not request.user.is_authenticated
And in the main view
from .permissions import IsNotAuthenticated
permission_classes = [IsNotAuthenticated]
The following answer is for Django not Django REST Framework
For a Class-Based View, make a custom mixin like this
class IsNotAuthenticatedMixin(UserPassesTestMixin):
"""
Allows access only to non authenticated users.
"""
def test_func(self):
return not self.request.user.is_authenticated
def handle_no_permission(self):
return redirect('home')
You can inherit any Class based view from IsNotAuthenticatedMixin
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,)