【问题标题】:DRF and Token authentication with safe-deleted users?安全删除用户的 DRF 和 Token 身份验证?
【发布时间】:2019-10-07 19:27:44
【问题描述】:

我正在使用一个名为 django-safedelete 的 Django 包,它允许删除用户而不将其从数据库中删除。

基本上,它会为模型添加一个delete 属性,而User.objects.all() 之类的查询不会返回已删除的模型。

您仍然可以使用特殊的管理器查询所有对象。例如 User.objects.all_with_deleted() 将返回所有用户,包括已删除的用户。 User.objects.deleted_only() 将返回已删除的。

这按预期工作,但在一种情况下除外。 我使用 Django Rest Framework 3.9 为我的用户使用令牌身份验证,在我的 DRF 视图中,我使用的是内置权限 IsAuthenticated

我正在使用的基本 CBV 代码:

class MyView(APIView):

    permission_classes = (IsAuthenticated,)

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

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)

问题

当用户被软删除时,他仍然能够使用其令牌进行身份验证。

我预计用户在软删除时会出现 401 Unauthorized 错误。 怎么了?

【问题讨论】:

  • request.user and not request.user.delete and ...?
  • 您使用SafeDeleteModel 创建了自己的User 模型并用它替换了默认模型?
  • @BearBrown 我认为这不会起作用,因为 Permission 阶段是在 Authentication 阶段和身份验证是在默认的用户查询集上执行的。 (如果我错了,请纠正我......)
  • @David D. 你能添加一个简单的例子来说明如何使用包检索已删除的对象/Quryset?
  • @JPG 我添加了例子

标签: python django django-rest-framework soft-delete


【解决方案1】:

为什么?

如果我们查看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)

这表明它没有过滤掉软删除的用户实例

解决方案?

创建一个自定义身份验证类并在相应的视图中连接

# 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)

在视图中连接

# 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)

【讨论】:

  • 删除用户时确保将is_active设置为False不是更容易吗?
  • 另外,那您不是必须更改每个 APIView 吗?
  • 是的...但是,设置is_active=False 而用户删除将是另一个令人头疼的问题。我没有找到一种简单的方法来以编程方式在用户删除时执行该操作,对吗?
  • 是的,尽管由于他使用另一个包来执行此操作,因此他必须在那里覆盖某些内容。好处是这将是一个单一的变化。您的解决方案需要更改每个 APIView。
  • 是的,他只需要为User 模型覆盖delete()
【解决方案2】:

DRF 已经使用is_active 属性来决定用户是否能够进行身份验证。每当您删除用户时,请务必同时将is_active 设置为False

对于 django-safedelete:

由于您使用的是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)

请注意,这也适用于 QuerySet,因为 SafeDeleteModel 的管理器会覆盖 QuerySet delete() 方法。 (见:https://github.com/makinacorpus/django-safedelete/blob/master/safedelete/queryset.py

此解决方案的好处是您不必更改每个 APIView 上的 auth 类,并且任何依赖于 User 模型的 is_active 属性的应用程序都会正常运行。另外,如果您不这样做,那么您将删除同样处于活动状态的对象,因此这没有多大意义。

【讨论】:

  • 是的,我在最后添加了一个注释; django-safedelete 已经以一种智能的方式覆盖了 QuerySet 删除。
  • 如果是这样,那看起来很棒!来自我的 +1 :)
  • @Greg Schmit 不错的答案! 2个想法:1)我认为我们也应该重写查询集方法,因为评论:# TODO: Replace this by bulk update if we can(意味着它可能会随着时间的推移而改变并导致意外行为)。 2) 我们不要忘记undelete() 重新激活用户的方法。
  • 顺便说一句,我认为用户is_active 字段可以(或不能)具有使用此方法会丢失的功能意义。换句话说,也许我们希望在取消删除用户时保持用户处于非活动状态。
  • @DavidD。这实际上是我在另一个项目中遇到的问题的产物,我想在 save 方法中更改对象的属性,但有时 Django 的某些部分会用 commit=False 调用 save,所以我所做的更改made 会迷路。所以现在我倾向于保存方法来提交我想要的更改,然后调用super 并传递任何参数。在这种情况下,这可能不适用,因此删除这些行是安全的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-21
  • 1970-01-01
  • 2012-04-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多