简体   繁体   English

安全删除用户的DRF和令牌认证?

[英]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. 我正在使用名为django-safedelete的Django包,它允许删除用户而不从数据库中删除它们。

Basically, it adds a delete attribute to the model, and the queries like User.objects.all() won't return the deleted models. 基本上,它会向模型添加一个delete属性,而像User.objects.all()这样的查询将不会返回已删除的模型。

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.all_with_deleted()将返回所有用户,包括已删除的用户。 User.objects.deleted_only() will return the deleted ones. User.objects.deleted_only()将返回已删除的。

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 . 我正在为Django Rest Framework 3.9的用户使用令牌身份验证,在我的DRF视图中,我使用的是内置权限IsAuthenticated

Code of a basic CBV I'm using: 我正在使用的基本CBV代码:

class MyView(APIView):

    permission_classes = (IsAuthenticated,)

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

Code of the DRF implementation of IsAuthenticated permission: IsAuthenticated权限的DRF实现代码:

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. 我希望用户在软删除时有401 Unauthorized错误。 What's wrong? 怎么了?

Why? 为什么?

If we look into the authenticate_credentials() method of DRF TokenAuthentication [source-code] , we could see that, 如果我们查看DRF TokenAuthentication [source-code]authenticate_credentials()方法,我们可以看到,

 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.'))

        if not token.user.is_active or not token.user.deleted: # Here I added something new !!
            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):
    authentication_classes = (CustomTokenAuthentication,)
    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. DRF已使用is_active属性来确定用户是否能够进行身份验证。 Whenever you delete a user, just be sure to set is_active to False at the same time. 每当您删除用户时,只需确保同时将is_activeFalse

For django-safedelete: 对于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: 由于您使用的是django-safedelete ,因此您必须覆盖delete()方法以取消激活,然后使用super()来执行原始行为,例如:

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. 请注意,这也适用于QuerySets,因为SafeDeleteModel的管理器会覆盖QuerySet delete()方法。 (See: https://github.com/makinacorpus/django-safedelete/blob/master/safedelete/queryset.py ) (参见: 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. 此解决方案的好处是您不必在每个APIView上更改auth类,并且任何依赖User模型的is_active属性的应用程序都将表现得非常好。 Plus, if you don't do this then you'll have deleted objects that are also active, so that doesn't make much sense. 另外,如果你不这样做,那么你将删除同样活跃的对象,因此没有多大意义。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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