简体   繁体   中英

DRF and Token authentication with safe-deleted users?

I'm using a Django package named django-safedelete that allows to delete users without removing them from the database.

Basically, it adds a delete attribute to the model, and the queries like User.objects.all() won't return the deleted models.

You can still query all objects using a special manager. For example User.objects.all_with_deleted() will return all users , including the deleted ones. User.objects.deleted_only() will return the deleted ones.

This works as expected, except in one case. I'm using Token Authentication for my users with Django Rest Framework 3.9, and in my DRF views, I'm using the built-in permission IsAuthenticated .

Code of a basic CBV I'm using:

class MyView(APIView):

    permission_classes = (IsAuthenticated,)

    def get(self, request):
        return Response(status=HTTP_200_OK)

Code of the DRF implementation of IsAuthenticated permission:

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

    def has_permission(self, request, view):
        return bool(request.user and request.user.is_authenticated)

The problem

When a user is soft deleted, he's still able to authenticate using its token.

I'm expecting the user to have a 401 Unauthorized error when he's soft deleted. What's wrong?

Why?

If we look into the authenticate_credentials() method of DRF TokenAuthentication [source-code] , we could see that,

 def authenticate_credentials(self, key): model = self.get_model() try: token = model.objects.select_related('user').get(key=key) except model.DoesNotExist: raise exceptions.AuthenticationFailed(_('Invalid token.')) if not token.user.is_active: raise exceptions.AuthenticationFailed(_('User inactive or deleted.')) return (token.user, token) 

Which indicates that it's not filtering out the soft deleted User instances

Solution?

Create a Custom Authentication class and wire-up in the corresponding view

# authentication.py
from rest_framework.authentication import TokenAuthentication, exceptions, _


class CustomTokenAuthentication(TokenAuthentication):
    def authenticate_credentials(self, key):
        model = self.get_model()
        try:
            token = model.objects.select_related('user').get(key=key)
        except model.DoesNotExist:
            raise exceptions.AuthenticationFailed(_('Invalid token.'))

        
            raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))

        return (token.user, token)

and wire-up in views

# views.py
from rest_framework.views import APIView


class MyView(APIView):
    
    permission_classes = (IsAuthenticated,)

    def get(self, request):
        return Response(status=HTTP_200_OK)

The DRF already uses the is_active property to decide if the user is able to authenticate. Whenever you delete a user, just be sure to set is_active to False at the same time.

For django-safedelete:

Since you're using django-safedelete , you'll have to override the delete() method to de-activate and then use super() to do the original behavior, something like:

class MyUserModel(SafeDeleteModel):
    _safedelete_policy = SOFT_DELETE
    my_field = models.TextField()

    def delete(self, *args, **kwargs):
        self.is_active = False
        super().delete(*args, **kwargs)

    def undelete(self, *args, **kwargs):
        self.is_active = True
        super().undelete(*args, **kwargs)

Note that this works with QuerySets too because the manager for SafeDeleteModel overrides the QuerySet delete() method. (See: https://github.com/makinacorpus/django-safedelete/blob/master/safedelete/queryset.py )

The benefit to this solution is that you do not have to change the auth class on every APIView, and any apps that rely on the is_active property of the User model will behave sanely. Plus, if you don't do this then you'll have deleted objects that are also active, so that doesn't make much sense.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM