Returning custom message when a permission is denied in DRF
Asked Answered
J

8

30

Django REST Framework has an excellent piece of documentation about permissions. I've been able to use pre-made permission classes and also built my own.

However, there are some API methods in which a "Permission denied" generic message is not very informative for the user. For example, if the user is authenticated but the account has expired, it would be nice to let the user know that his account is expired and not just a permission denied error.

When building custom permission classes, you either return True or False - according to the documentation. But I would like, as said above, to show a more informative message to the user. How to accomplish this?

Januarius answered 29/4, 2015 at 6:52 Comment(0)
K
41

Since DRF 3.2.0, You only have to add a message attribute :

from rest_framework import permissions

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

    def has_permission(self, request, view): 

See from DRF documentation: http://www.django-rest-framework.org/api-guide/permissions/#custom-permissions

Kellum answered 20/10, 2015 at 15:58 Comment(3)
But how to localize the message?Nostrum
How you would do it regularly: docs.djangoproject.com/en/1.11/topics/i18n/translationKellum
is there any way to do it in version 3.1.1?Joslyn
W
17

From DRF

you can simply add message attribute.

from rest_framework import permissions

class IsSuperUserPermission(permissions.BasePermission):
    message = 'User is not superuser'

    def has_permission(self, request, view):
        return self.request.user.is_superuser

It will return a dict with key detail, something like this:

{
    'detail': 'User is not superuser'
}

But what if you want for example that the dict key not to be detail but errors for example, it will be the same how return errors DRF.

We can set message attribute not to string but to dict, something like this:

class IsSuperUserPermission(permissions.BasePermission):
    message = {'errors': ['User is not a superuser']}

    def has_permission(self, request, view):
        self.message['errors'].clear()
        return self.request.user.is_superuser

In this case the error will be:

{
    'errors': ['User is not a superuser']
}
Wiebmer answered 14/11, 2018 at 14:18 Comment(1)
I have been using the same for my authentication_classes but I just found out that boolean key values are converted into string. So I set {"success": False, "message": "Permission denied."} in the code and actually get the response as {"success": "False", "message": "Permission denied."}. Is there any way to tell DRF to not convert False to "False"?Biak
Z
10

when permission isn't granted, I will raise a exception which custom response. It works on djangorestframewor(3.10.1) and django(2.2.3).

from rest_framework.permissions import BasePermission
from rest_framework.exceptions import APIException
from rest_framework import status


class IsLogin(BasePermission):
    """
    Allows access only to authenticated users.
    """

    def has_permission(self, request, view):
        if request.email:
            return True
        raise NeedLogin()


class NeedLogin(APIException):
    status_code = status.HTTP_403_FORBIDDEN
    default_detail = {'error': True, 'message': 'need login'}
    default_code = 'not_authenticated'
Zaragoza answered 15/8, 2019 at 8:11 Comment(1)
How about composite permission?Fulfillment
M
3

Building on Aysennoussi’s answer:

from rest_framework import permissions

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

    def has_permission(self, request, view): 
        if request.user.has_expired:
            self.message = “Your account has expired.”
            return False
        elif request.user.has_access:
            return True
        else:
            return False

Mitochondrion answered 29/4, 2015 at 6:53 Comment(0)
A
1

By default, it is handled by default exception handler, and it is raising a standard message - https://github.com/tomchristie/django-rest-framework/blob/2eb9107b875972e442ed73eef0e653fd4480d873/rest_framework/views.py#L82

But, you can set own EXCEPTION_HANDLER in settings of DRF, and handle PermissionDenied exception to return message you want.

See description at http://www.django-rest-framework.org/api-guide/settings/

Anatomize answered 29/4, 2015 at 13:22 Comment(0)
C
0

I faced the same problem using DRF 3.9.4. As a workaround I defined just a simple message property in the custom permission class and it works. You can also use getattr with the same result I guess.

class IPWhitelistPermission(permissions.BasePermission):

    def __init__(self):
        super(IPWhitelistPermission, self).__init__()
        self._client_ip = None

    def has_permission(self, request, view):
        ip = get_client_ip(request)
        ret = IPWhitelist.is_whitelisted(ip)

        if not ret:
            logger = logging.getLogger('access')
            logger.warn("Unauthorized access from IP %s" % ip)
            self._client_ip = ip
        return ret

    @property
    def message(self):
        return "This IP is not whitelisted [{}]".format(self._client_ip)
Conversazione answered 6/6, 2019 at 19:56 Comment(0)
H
0

You can send more than a single customized message if you want to. You can do it using GenericAPIException.

Step 1: Create a permissions.py file and write this code.

class Check_user_permission(permissions.BasePermission):
def has_permission(self, request, view):
    if request.method in permissions.SAFE_METHODS:
        return True
    else:
        response ={
            "success": "false",
            'message': "Post request is not allowed for user from admin group",
            "status_code":403,
        }
        raise GenericAPIException(detail=response, status_code=403)

Here, response is the JSON response you want to send.

Step 2: Go to view.py file and add the class Check_user_permission in the permission_classes list this way:

class UserList(APIView):
    permission_classes = (IsAuthenticated, Check_user_permission)
    authentication_class = JSONWebTokenAuthentication
    ... 
    ...

Now if you go to the endpoint and send a POST request you'll get this response.

{
"success": "false",
"message": "Post request is not allowed!",
"status_code": 403
}
Hettie answered 18/12, 2020 at 15:36 Comment(0)
F
0

Simple method use PermissionDenied class, and raise exception:

from rest_framework.exceptions import PermissionDenied

...
if allowed:
   return True
else:
   # return False
   raise PermissionDenied('custom message')
...
Fauna answered 21/11, 2023 at 15:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.