Search Here

Adding Custom Permissions in Django Rest Framework

Adding Custom Permissions in Django Rest Framework

The Permission Module is the best security measure and UnAuthorized access of system resources and data. Hence the Django Rest Framework comes with its built-in Permissions Module despite Django Web Framework having one.
Using these classes we can easily check for authentic users for performing the operation.

Configuration of Default Permissions

Before you begin editing files, first install the rest framework using the command pip install djangorestframework. Then open the settings.py file and add rest_framework, rest_framework.authtoken within INSTALLED_APPS.

The next step is to specify the DEFAULT_AUTHENTICATION_CLASSES ( optional step ) in the settings.py file.

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ]
}

Note

If no authentication is applied to view then by default DEFAULT_AUTHENTICATION_CLASSES will be applied. The DEFAULT_AUTHENTICATION_CLASSES will not be applied if you have specified any authentication classes in your APIView.

Basic Example of Permissions in APIView

First, import permissions from from rest_framework.permissions import IsAuthenticated and If you are using permission in APIView you must specify the permission_classes. If there are any authentication then specify them within authentication_classes list.

class MyAPIView(APIView):
    authentication_classes = [ ... ]
    permission_classes = [...]

    def get( self, request, *args, **kwargs ):
        return Response({ })

Out of box available permissions in Django Rest Framework

By default, the rest_framework provides us with sufficient toolsets to implement the basic and intermediate level of permission cases.
Some of them are mentioned below:

  • AllowAny: This permission class allows any user to access the resource whether he is authenticated or not.
  • IsAuthenticated: Only allows the user who is authenticated. If you are sending tokens from headers with isauthenticated the class also mention the authentication_classes=[TokenAuthentication].
  • IsAdminUser: Only allowed user if he is marked as admin and also staff users if the attribute user.is_staff is True as mentioned in the rest framework permission document.
  • IsAuthenticatedOrReadOnly: Requests which with come under safe methods such as GET, HEAD and OPTIONS are only accessible to the unauthenticated user. An authenticated user can use all the available methods.

View Level Permissions

A View Level Permissions are given to a particular APIView class. Such as  IsAuthenticated which checks for the user to authenticate if they want to access any of the methods within the APIView class.
Let’s try to demonstrate using an Product API example and here we try to create a product through API where the user must be authorized to perform that operation.

We’ll start by creating a URL for API in root app src\urls.py

from django.urls import path
from django.urls.conf import include

urlpatterns = [
    path('api/products/', include('products.api_urls') ),
]

Then serializers to validate in products\serializers.py

from rest_framework import serializers
from products.models import ProductModel

class ProductSerializer(serializers.ModelSerializer):
    class Meta:
        model = ProductModel
        fields = '__all__'
    

All the login will be in products\api_views.py

from products.serializers import ProductSerializer
from products.models import ProductModel

from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.authentication import TokenAuthentication

class CreateView( APIView ):
    authentication_classes = [ TokenAuthentication ]
    permission_classes = [IsAuthenticated, IsProductOwner]

    def post(self, request, format=None):
        info = {
            'success' : False,
            'msg' : "Something went wrong",
        }
        
        product = ProductModel.objects.filter(product_id=request.POST['product_id']).first()
        
        if product:
            self.check_object_permissions(request, product)

        query = {
            'user' : request.user.id,
            'name' : request.POST['name'],
            'price' : request.POST['price'],
            'content' : request.POST['content']
        }
        serializer = ProductSerializer( product, data=query )

        if serializer.is_valid( ):
            serializer.save()
            info['obj'] = serializer.validated_data
        else:
            info['validation_errors'] = serializer.errors

        return Response(info)

In products\api_urls.py

from django.urls import path
from products import api_views as views
app_name = "api_products"

urlpatterns = [
    path( 'create', views.CreateView.as_view(), name="create" ),
]

Output

{
    "success": false,
    "msg": "Something went wrong",
    "obj": {
        "product_id": 12,
        "name": "Oil",
        "price": 150,
        "content": "sample content",
        "featured_image": null,
        "user": 1
    }
}

Object Level Permissions and Creating Custom Permission Classes

The Object Level Permissions can be done on a specific object itself. Let’s say if a user is allowed to create a new or edit his own product but not allowed to edit products created by other users. And for this kind of permission, we have to create our own custom permission class and specify it in permission_classes the list.

Let’s take the above example and make certain changes to the code. Firstly create a new file in src\permissions.py and declare a permission class by inheriting from BasePermission from from rest_framework.permissions import BasePermission.

from rest_framework.permissions import BasePermission

class IsProductOwner( BasePermission ):

    def has_permission( self, request, view ):
        # check if has permission view, by default we'll return True
        return True

    def has_object_permission( self, request, view, obj ):
        return obj.user == request.user
    

Whenever you create a permission class using BasePermission class always override the has_permission and has_object_permission method. And the has_object_permission is used for object-level permission checking and is can be invoked manually inside a view method by simply calling self.check_object_permissions(request, object).

Note

You must only call the method self.check_object_permissions(request, object) and don’t try to declare it as it is already declared in the APIView.

And inside the method, has_object_permission you can do the permission checking for that particular object you have passed and return a boolean value.

Custom Permission Denied Messages

If you have denied permission then you will be displayed a default message as { "detail": "You do not have permission to perform this action." }. You can change this message by specifying a message property in your custom permission class.

In src\permissions.py

from rest_framework.permissions import BasePermission

class IsProductOwner( BasePermission ):
    message = "You are not product owner"

    def has_permission( self, request, view ):
        self.message = "No Permission"
        return True

    def has_object_permission( self, request, view, obj ):
        self.message = "You must be product owner to update"
        return obj.user == request.user
    

The permission IsProductOwner checks whether the product is edited by the user who has created it and returns a boolean.

After this, we also need to refactor products\api_views.py.

class CreateView( APIView ):
    authentication_classes = [ TokenAuthentication ]
    permission_classes = [IsAuthenticated, IsProductOwner]

    def post(self, request, format=None):
        info = {
            'success' : False,
            'msg' : "Something went wrong",
        }

        product = ProductModel.objects.filter(product_id=request.POST.get('product_id')).first()
        
        if product:
            self.check_object_permissions(request, product) #checks for object level permission

        query = {
            'user' : request.user.id,
            'name' : request.POST['name'],
            'price' : request.POST['price'],
            'content' : request.POST['content']
        }
        serializer = ProductSerializer( product, data=query )

        if serializer.is_valid( ):
            serializer.save()
            info['obj'] = serializer.data
        else:
            info['validation_errors'] = serializer.errors

        return Response(info)

Watch Video

Conclusion

The Django Rest Framework Permission can also interact with the DjangoModelPermissions and this topic will be covered in upcoming posts.