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 theauthentication_classes=[TokenAuthentication]
. - IsAdminUser: Only allowed user if he is marked as admin and also staff users if the attribute
user.is_staff
isTrue
as mentioned in the rest framework permission document. - IsAuthenticatedOrReadOnly: Requests which with come under safe methods such as
GET
,HEAD
andOPTIONS
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.