Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I send 401 error codes instead of 403 with a custom permission class?

I'm using a custom authentication scheme and I cannot figure out how to get it to send 401 HTTP responses instead of 403. The guide at http://www.django-rest-framework.org/api-guide/authentication/#custom-authentication says to override the authenticate_header method, but this doesn't seem to do anything. The part that is sending the 403 is where the AuthenticationFailed exception is being raised. I assigned the OwnerCredentialsFound to a ModelViewSet via the permission_classes property.

from rest_framework import exceptions
from rest_framework import permissions
from django.contrib.auth.models import User

def authenticateUser(username, password):
    try:
        user = User.objects.get(username=username)
        return user.check_password(password)
    except:
        return False



class OwnerCredentialsFound(permissions.IsAuthenticated):

    def has_permission(self, request, view):
        #Check credentials
        #If the fields don't exist, the given default value is used
        username = request.POST.get('username', None)
        password = request.POST.get('password', None)
        authenticated = authenticateUser(username, password)
        if(not authenticated and username is not None and password is not None):
            raise exceptions.AuthenticationFailed('Username/password pair not found')
        elif(not authenticated):
            authenticated = permissions.IsAuthenticated.has_permission(self, request, view)
        else:
            #set the user
            view.request.user = User.objects.get(username=username)
        return authenticated

    def authenticate_header(self, request):
        return '{"username" : <username>, "password" : <password>}'

UPDATE: It seems that I've confused the authentication and permission classes. I'm using a permission class, but it is the authentication class that has a method called authenticate_header.

like image 687
Shaymin Gratitude Avatar asked Sep 25 '22 05:09

Shaymin Gratitude


2 Answers

Basically, I didn't really understand the difference between permissions and authentications, so that led to confusion. The permissions class has no authenticate_header method, but the authentication class does. Here is what I did to solve the problem:

from rest_framework import exceptions
from rest_framework import authentication
from django.contrib.auth.models import User

def authenticateUser(username, password):
    try:
        user = User.objects.get(username=username)
        return user.check_password(password)
    except:
        return False

class CustomAuthentication(authentication.BaseAuthentication):
    def authenticate(self, request):
        username = request.POST.get('username', None)
        password = request.POST.get('password', None)
        authenticated = authenticateUser(username, password)
        if(not authenticated and username is not None and password is not None):
            #authentication attempted and failed
            raise exceptions.AuthenticationFailed('Username/password pair not found')
        elif(not authenticated):
            #authentication not attempted (try other authentications)
            return None
        else:
            #authentication attempted and suceeded
            return (User.objects.get(username=username), None)


    def authenticate_header(self, request):
        return '{"username" : <username>, "password" : <password>}'

In my view:

permission_classes = (IsAuthenticated,)
authentication_classes = (CustomAuthentication, SessionAuthentication)

This confusion of permissions and authentication also explains why my attempt to combine multiple permission classes failed (you may note in my original code that I inherited from a permission class and called its has_permission method in order to get around this). I no longer need a custom permission class because I can just use two authentication classes.

like image 63
Shaymin Gratitude Avatar answered Oct 13 '22 11:10

Shaymin Gratitude


When You return False from has_permission, under the hood DRF raises PermissionDenied exception. Then the exception is caught and processed in a fuction named exception_hanlder like this:

elif isinstance(exc, PermissionDenied):
    msg = _('Permission denied.')
    data = {'detail': six.text_type(msg)}

    set_rollback()
    return Response(data, status=status.HTTP_403_FORBIDDEN)

It looks like You can define some custom Exception, throw it on error in OwnerCredentialsFound.has_permission and then use a custom exception handler to catch it Yourself. Please read more here: http://www.django-rest-framework.org/api-guide/exceptions/#custom-exception-handling

like image 34
Konstanty Karagiorgis Avatar answered Oct 13 '22 11:10

Konstanty Karagiorgis