docs
  • Overview
  • 🐍 PYTHON
    • Type Hints
    • PEP8 Style Guide for Python Code
    • 🏡Pipenv
    • Pathlib
  • 🕸Django
    • 🗄models
      • 🎯Best Practices
      • 🚦Django Signals
    • ⚙️ settings
    • DRF
      • Serializer
      • Authentication
      • Permissions
      • Viewsets
    • Testing
      • Faker and Factory Boy
    • 🧪Test Coverage
    • 💦Python-Decouple
    • Django Tips:
    • 💾Django ORM Queryset
    • Custom Exceptions
    • Celery
    • Resources
  • Deploy
    • 🚀Django Deployment
    • 🔒Setup SSL Certificate
  • 💾Database
    • MongoDB
  • 🛠️DevOps
    • 🖥Scripting
      • A First Script
      • Loops
      • Test
      • Variables
      • External programs
      • Functions
    • Command Line Shortcuts
    • Basic Linux Commands
    • 🎛Microservices
    • 🐳Docker
      • Docker Commands
      • Docker Compose
      • Django project
    • Kubernates
  • 📝Software IDE
    • EditorConfig
    • Linters
    • VsCode
Powered by GitBook
On this page
  • Permissions In Django Rest Framework
  • Authentication and Authorization
  • Permissions Flow in DRF
  • View Permissions
  • Permission classed
  • Flow diagram
  • Default Permissions Classes provided by DRF
  • Configuring the Permissions Globally in "settings.py"
  • Writing Custom Permissions in DRF

Was this helpful?

  1. 🕸Django
  2. DRF

Permissions

Permissions in DRF

Permissions In Django Rest Framework

Permissions in Django Rest Framework are used to grant or deny access for different types of users to different parts of the API. Permissions are very useful when serving API resources/end-points with certain restrictions.

Authentication and Authorization

Authentication and authorization work hand in hand. Authentication is always executed before authorization. While authentication is the process of checking a user's Identity( the user the request came from, the token that it was signed with), authorization is a process of checking if the requesting user has the necessary permissions for executing the request are they a super user, are they the creators of the object. The authorization process in DEF is covered by permissions.

Permissions Flow in DRF

Whenever any request comes to DRF then it enters the "dispatch" method. "dispatch" method should route the request to appropriate request methods like "get", "post", "put", "delete", etc. But, before routing the request to one of these methods it will checks the authentication, "permissions" and then it checks throttles(i.e used to limit the number of requests per user or per IP). In the Django REST framework, we always define permissions as a list. In DRF we can setup global permission classes or we can specify the permission classes in Views and Viewsets. If all permission checks are valid then the request will be passed to the appropriate method (i.e "get", "post", "put", "delete", etc. ) based on the request type otherwise an exception will be raised.

The exception can be one of the exceptions.PermissionDenied or exceptions. NotAuthenticated . If authentication fails then it returns the status code of 401 and if any other permissions fail then it will return the status code of 403.

View Permissions

APIView has two methods that check for permissions:

  1. check_permissions check if the request should be permitted based on request data.

  2. check_object_permissions checks it the request should be permitted based on the combination of the request and object data.

# rest_framework/views.py

class APIView(View):
    # other methods
    def check_permissions(self, request):
        """
        Check if the request should be permitted. 
        Raises an appropriate exception if the request is not permitted. 
        """
        for permission in self.get_permissions():
            if not permission.has_permission(request, self):
                self.permission_denied(
                    request,
                    message=getattr(permission, 'message', None),
                    code=getattr(permission, 'code', None)
                )
                
    def check_object_permissions(self, request, obj):
        """
        Check if the request should be permitted for a given object. 
        Raises an appropriate exception if the request is not permitted. 
        """
        for permission in self.get_permissions():
            if not permission.has_object_permission(request, self, obj):
                self.permission_denied(
                    request,
                    message=getattr(permission, 'message', None),
                    code=getattr(permission, 'code', None)
                )

When the request comes in, the authentication is performed. if the authentication isn't successful, a NonAuthenticated error is raised. After that, the permissions get checked in a loop, and if any of them fail, a PermissionDenied error is raised. Finally, a throttling check is performed against the request.

check_permissions is called before the view handler is executed while check_object_permissions is not executed unless we explicitly call it.

class MessageSingleAPI(APIView):
    
    def get(self, request, pk):
        message = get_object_of_404(Message.objects.all(), pk=pk)
        self.check_object_permissions(request, message)  # explicitly called
        serializer = MessageSerializer(message)
        return Response(serializer.data)

With ViewSets and GenericViews , check_object_permissions is called after the object is retrieved from the database for all detail Views.

# rest_framework/generics.py

class GenericAPIView(views.APIView):
    # other methods
    def get_object(self):
        """
        Returns the object the view is displaying.
        We may want to override this if we need to provide non-standard queryset lookups.
        Eg. if objects are referenced using multiple keyword arguments in the url conf.
        """
        queryset = self.filter_queryset(self.get_queryset())
        
        # Perform the lookup filtering. 
        lookup_url_kwarg = self.lookup_url_kwarg of self.lookup_field
        
        assert lookup_url_kwarg in self.kwargs, (
            'Expected view %s to be called with a URL keyword argument '
            'named "%s". Fix your URL conf, or set the `.lookup_field` '
            'attribute on the view correctly.' %
            (self.__class__.__name__, lookup_url_kwarg)
        )
        
        filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
        obj = get_object_or_404(queryset, **filter_kwargs)
        # May raise permission denied
        self.check_object_permissions(self.request, obj)
        return obj

        

Permission is checked for all permissions and if any one of them return False , a PermissionDenied error is raised.

Permission classed

Permissions in DRF are defined as a list of permission classes. We can either create our own or use one of the seven built-in classes. All permission classes, either custom or built-in, extended from the BasePermission class:

class BasePermission(metaclass=BasePermissionMetaClass):
    
    def has_permission(self, request, view):
        return True
    
    def has_object_permission(self, request, view, obj):
        return True

check_permissions and check_object_permissions method calls has_permissions and has_object_permissions for each of the permissions respectively.

has_permission is used to decide whether a request and a user are allowed to access a specific view. For example: - is the quest method allowed? - is the user authenticated? - is the user an admin or super user? has_permission possesses knowledge about the request, but not about the object of the request. has_object_permission is used to decide whether a specific user is allowed to interact with a specific object For example: - Who created the object? - When was it created? - In which group does the object belong to? Besides the knowledge of the request, has_object_permission also possesses data about the object of the request. the method executes after the object is retrieved from the databases. Unlike has_permission , has_object_permission isn't always executed by default: - With an APIView, we must explicitly call check_object_permission to execute has_object_permission for all permission classes. - With ViewSets (like ModelViewSet ) or generic Views (like RetriveAPIView), has_object_permission is executed via check_object_permission inside a get_object method out of the box. - has_object_permission is never executed for list views (regardless of the view we're extending from) or when the request method is POST (since the object doesn't exist yet). - When any has_permission returns False , the has_object_permission doesn't get checked. The request is immediately rejected.

Flow diagram

Note:

  • List views, only has_permission is executed and the request is either granted or refused access. if access is refused, the objects never get retrieved.

  • Detail views, has_permission is executed, and then only if permission is granted, has_object_permission is executed after the object is retrieved.

Default Permissions Classes provided by DRF

Django rest framework provides the following permission classes which are used frequently.

  1. AllowAny

    • This permission class does not restrict the user to access the API resource.

  2. IsAuthenticated

    • This permission class only permits authenticated or logged-in users to access the API resource.

  3. IsAdminUser

    • This permission class only allows Django Admin Users(i.e user.is_staff = True) to access the API resource and all other users will not be able to access the API resource.

  4. IsAuthenticatedOrReadOnly

    • This permission class allows authenticated users to perform read and write operations on the API resource.

    • All users which are not authenticated will have read access only. If they try to update the API resource then it will raise permission denied error.

  5. DjangoModelPermissions

    • It is based on model permissions provided by django.contrib.auth.

    • It only is applied to views that have a queryset property.

    • It only applied to authenticated users who have the relevant model permissions assigned.

    • POST requests require the user to have the add permission on the model.

    • PUT and PATCH requests require the user to have the change permission on the model.

    • DELETE requests require the user to have delete permission on the model.

    6. DjangoModelPermissionsOrAnonReadOnly

    • Same as DjangoModelPermissions, but it also allows unauthenticated users to have read-only access to the API resource.

    7. DjangoObjectPermissions by extending DjangoModelPermissions. While DjangoModelPermissions limit the user's permission for interacting with a model (all the instances), DjangoObjectPermissions limits the interaction to a single instance of the model (an object). To use DjangoObjectPermissions we'll need a permission backend that supports object-level permissions. like django-guardian .

Configuring the Permissions Globally in "settings.py"

If we configure the global permission then these permissions will apply on all API Views and ViewSets unless these permissions are overridden in Views and ViewSets.

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    )
}

DEFAULT_PERMISSION_CLASSES takes data type of list or tuple or any iterable types. We can configure multiple permissions globally.

Writing Custom Permissions in DRF

In Django REST Framework we can use custom permission classes also. We can write two types of permission classes "view level" and "object-level".

  1. View level permissions We use these permissions when we want to check the user permission/access level before passing the request to process it further.

  2. Object level permissions we use it when we want to check the user permission on the object level. does the user has permission to edit or delete the object.

Custom view level permission to block some IP addresses to access the API.

from rest_framework import permissions
from .models import Blacklist

class BlacklistPermission(permissions.BasePermission):
    """
    Permission check for blacklisted IPs.
    """

    def has_permission(self, request, view):
        ip_addr = request.META['REMOTE_ADDR']
        blacklisted = Blacklist.objects.filter(ip_addr=ip_addr).exists()
        return not blacklisted

Custom object-level permission

from rest_framework import permissions
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import RetrieveAPIView
from .permissions import IsBookOwner, BlacklistPermission

from .models import Blacklist, Writer

class IsBookOwner(permissions.BasePermission):
    """
    Check if user is Book owner or not.
    """
    def has_object_permission(self, request, view, obj):
        return obj.owner == request.user


class BookAPIView(RetrieveAPIView):
    permission_classes = [BlacklistPermission, IsAuthenticated, IsBookOwner]
    serializer_class = BookSerializ
from rest_framework import permissions


class AuthorAllStaffAllButEditOrReadOnly(permissions.VasePermission):
    
    edit_methods = ("PUT", "PATCH")
    
    def has_permission(self, request, view):
        if request.user.is_authenticated:
            return True
            
    def has_object_permission(self, request, view, obj):
    
        # grants access to the superuser.
        if request.user.is_superuser:
            return True
            
        # SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
        # These methods have no impact on the object; they can only read it.
        if request.method in permissions.SAFE_METHODS:
            return True
            
        if obj.author == request.user:
            return True

        if request.user.is_staff and request.method not in self.edit_methods:
            return True
        return False
            
            
            
class ExpiredObjectSuperuserOnly(permissions.BasePermission):

    message = "This object is expired."  # Custome error message
    
    def object_expired(self, obj):
        expired_on = timezone.make_aware(datetime.now() - timedelta(minutes=10))
        return obj.created < expired_on
        
    if self.object_expired(obj) and not request.use.is_superuser:
            return False
    
PreviousAuthenticationNextViewsets

Last updated 2 years ago

Was this helpful?