Django REST Framework (DRF) - permissions

DoriDoro - Oct 28 - - Dev Community

Introduction

In Django REST Framework, permissions are a crucial part of the authentication and authorization mechanism. They determine the level of access that users have to various resources in your API, ensuring that sensitive information is protected and only accessible to authorized users.

Permissions can be applied at the view level, allowing you to specify who can list, retrieve, create, update, or delete resources. This fine-grained control is essential for building secure applications, as it restricts actions based on user roles, authentication status, or other custom rules.

DRF provides a set of built-in permission classes that cover common use cases, such as granting access to authenticated users or allowing only admin users to perform certain actions. You can apply these permission classes to individual views or viewsets using the permission_classes attribute.

For more complex scenarios, DRF's flexibility allows you to define custom permission classes by extending the BasePermission class. By leveraging permissions effectively, you can build robust APIs that cater to different user roles while maintaining data integrity and security.


Setting the permission policy

You can choose not to set a general restriction for your project. By doing so, the default settings of the Djnago REST framework will allow unrestricted access.

The DEFAULT_PERMISSION_CLASSES is set to the permission AllowAny.

'DEFAULT_PERMISSION_CLASSES': [
   'rest_framework.permissions.AllowAny',
]
Enter fullscreen mode Exit fullscreen mode

In your settings.py file you can set a general restriction:

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ]
}
Enter fullscreen mode Exit fullscreen mode

The restriction allows only authenticated users to access your entire project.

You can also set permissions in your view. You can set a general permission in your settings.py and override the permission_classes in your view. Or you can set your permissions in your view only.

How to apply a permission to a view?

class AccountViewSet(viewsets.ModelViewSet):
    queryset = Account.objects.all()
    serializer_class = AccountSerializer
    permission_classes = [IsAccountAdminOrReadOnly]
Enter fullscreen mode Exit fullscreen mode

You add the permission into the permission_classed list in your view.

How does the system work?

There is a difference between the APIView and the ViewSet and GenericViewSet. The APIView provides a very basic view that you can extend. It doesn't come with automatic permission behavior, so you need to call check_object_permission manually. On the other hand, ViewSets and Generic Views are built on top of APIView and come with more built-in functionality. They come pre-equipped with mechanisms to handle permissions for detail routes. Essentially, inside ViewSets and Generic Views, there's logic that automatically handles permission checks when it accesses an object. This is done through the use of mixins and generic classes, making it unnecessary for you to manually invoke check_object_permission.

ViewSet and GenericView:
You don't have to worry about the permissions system, you just have to implement the permissions.

APIView:
If you are using an APIView within your method where you get the object, call self.check_object_permissions(self.request, obj) immediately after you get the object and before you perform any further actions. This will ensure that the permissions checks are properly enforced before any further operations are performed on the object.

Summary:

  • With an APIView, you must explicitly call check_object_permission to execute has_object_permission for all permission classes.
  • With ViewSets (like ModelViewSet) or Generic Views (like RetrieveAPIView), 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 you'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.

1. Built-in permissions:
Django REST Framework are several built-in permissions that you can use for controlling access to your API views.

Here are the main ones:

  • AllowAny: This permission class allows unrestricted access, i. e., any user can perform any operation.
  • IsAuthenticated: This permission only allows access to authenticated users. Anonymous users will be denied access.
  • IsAdminUser: Grants access only to users with the is_staff attribute set to True.
  • IsAuthenticatedOrReadOnly: Allow read-only access to anonymous users and full access to authenticated users.
  • DjangoModelPermissions: This permission ties the access level to the model-level permissions set in Django admin.
  • DjangoModelPermissionsOrAnonReadOnly: Similar to DjangoModelPermissions, but allows read-only access to anonymous users.

These permissions can be applied to DRF's views and viewsets by setting the permission_classes attribute. For custom behaviour, you can also create your own permission classes by extending BasePermission.

2. Custom permissions:

If you need a custom permission, you will need to override one of the two BasePermission methods:

  • .has_permission(self, request, view)
  • .has_object_permission(self, request, view, obj)

The has_permission() method checks whether a user has the permission to perform a certain action on the API resource (view) or endpoint.

And the has_object_permission() method checks the permissions on a specific instance or object level. It is important to note that the has_object_permission() method is only called if the has_permission() method has granted access.

The method should return True to give access. By returning False a PermissionDenied exception will be raised.

To customise the error message that is raised when the permission is denied, you can implement a message attribute in your permission class.

from rest_framework import permissions

class CustomerAccessPermission(permissions.BasePermission):
    message = 'Adding customers not allowed.'

    def has_permission(self, request, view):
         ...
Enter fullscreen mode Exit fullscreen mode

In general, you will want to create permissions for read or write operations.

Read operations are actions that retrieve data without modifying it. Methods such as GET, OPTIONS and HEAD are considered safe or read operations. These read operations (GET, OPTIONS and HEAD) can be checked using the SAFE_METHODS constant.

On the other hand, write operations are actions that change the state on the server by creating, updating or deleting data. Methods such as POST, PUT, PATCH and DELETE fall into this category.

if request.method in permissions.SAFE_METHODS:
    # Check permissions for read-only request
else:
    # Check permissions for write request
Enter fullscreen mode Exit fullscreen mode

Here are some examples:

First example:

class IsAuthor(BasePermission):
    message = "You have to be the author to update or delete."

    def has_object_permission(self, request, view, obj):
        if request.method in SAFE_METHODS:
            return True

        return obj.author == request.user
Enter fullscreen mode Exit fullscreen mode

In this example, I have not overridden the has_permission method. So the general permission or the view implemented permission is evaluated for access to the view. On the other hand, I want to make sure that only the author of the object (a project, issue or comment) has the permission to update or delete this object. Read operations, for this object, are allowed for other users. If permission is denied, the message attribute is set to deliver a customised message to the user.

Second example:

class UserPermission(BasePermission):
    def has_object_permission(self, request, view, obj):
        if not request.user.is_authenticated:
            return False

        if view.action in ["retrieve", "update", "partial_update"]:
            return (
                obj == request.user
            )  
        else:
            return False  
Enter fullscreen mode Exit fullscreen mode

In this example, the permission customisation is again at the object level. The first check is to see if the user is authenticated. If the user is an AnonymousUser (not authenticated), the permission is denied. The next check allows the user to retrieve, update or partial update their own data by verifying that specific view actions are met. All other view actions are denied.


- Django REST Framework Links:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .