【问题标题】:Django restrict access to user objectsDjango 限制对用户对象的访问
【发布时间】:2019-06-09 03:28:06
【问题描述】:

我有节点和用户模型,它们都属于一个组织。我想确保用户只会看到属于其组织的节点实例。

为此,我想用一个返回用户拥有的过滤结果的 query_set 的节点对象管理器来覆盖节点对象管理器。

基于https://docs.djangoproject.com/en/2.1/topics/db/managers/#modifying-a-manager-s-initial-queryset 我拥有的相关 models.py 代码如下:

class Organisation(models.Model):
    users = models.ManyToManyField(User, related_name='organisation')
    ...

class UserNodeManager(models.Manager):
    def get_queryset(self, request):
        return super().get_queryset().filter(organisation=self.request.user.organisation.first())


class Node(models.Model):
    organisation = models.ForeignKey(
        Organisation, related_name='nodes', on_delete=models.CASCADE)

    uuid = models.UUIDField(primary_key=True, verbose_name="UUID")
    ...

    objects = UserNodeManager

views.py

class NodeListView(LoginRequiredMixin, generic.ListView):
    model = Node

编辑 我可以将自定义 query_set 添加到单个视图中,这确实如下所示:

views.py

class NodeListView(LoginRequiredMixin, generic.ListView):
    model = Node

    def get_queryset(self):
        return Node.objects.filter(organisation__users__id=self.request.user.pk)

但是,我的意图是保持 DRY 并在单个点覆盖“主”query_set 方法,以便任何视图(例如表单下拉列表、API 端点)都将执行用户受限查询而无需额外代码。

例如,我使用 django 的通用列表视图有一个表单用于添加 Scan 对象,这需要用户选择 Scan 所属的节点。该表单当前显示来自其他组织的节点,这违反了我需要的权限逻辑。

不幸的是,被覆盖的 Node.objects 属性似乎没有任何效果,任何用户都可以看到所有节点。我是否采取了正确的方法?

【问题讨论】:

  • 你试试这个return super().get_queryset().filter(organisation__users_id=self.request.user.pk)
  • 这会导致错误:Related Field got invalid lookup: users_id
  • 尝试将两个下划线改为organisation__users__id
  • @SergeyPugach:是的,它适用于在视图中定义的 query_set,但作为上面的 UserNodeManager 类的一部分仍然没有效果。
  • 如何调用get_queryset 方法?您是否覆盖了所有管理器方法(即更新、获取、删除)?

标签: django user-permissions


【解决方案1】:

我认为问题出在这里:

objects = UserNodeManager

您需要像这样启动UserNodeManager 实例:

objects = UserNodeManager()

另外,当你调用YourModel.objects.all() 方法时它应该会抛出错误(从视图中的get_queryset 方法调用),因为当它调用get_queryset() 方法时,它不会通过request。所以我认为这将是一个更好的方法:

class UserNodeManager(models.Manager):
    def all(self, request=None):
       qs = super(UserNodeManager, self).all()
       if request:
          return qs.filter(...)
       return qs

或者您可以像这样创建一个新的管理器方法(可选):

class UserNodeManager(models.Manager):
    def user_specific_nodes(self, request):
       return self.get_queryset().filter(...)

也在视图中更新:

class NodeListView(LoginRequiredMixin, generic.ListView):
    model = Node

    def get_queryset(self):
        return Node.objects.all(self.request)  # where you can obviously use filter(...) or Model.objects.user_specific_nodes(self.request)

更新

来自 cmets

问题是,您需要将requestfilter()all() 一起传递。在通用视图中,get_queryset 方法不会将该信息传递给all()。所以你需要通过任何一种方式。还有另一种方法,使用像 django-crequest 这样的中间件。你可以这样使用它:

from crequest.middleware import CrequestMiddleware

class UserNodeManager(models.Manager):
    def all(self):
       qs = super(UserNodeManager, self).all()
       request = CrequestMiddleware.get_request()
       return qs.filter(...)

【讨论】:

  • 谢谢 ruddra,我可以将自定义 query_set 添加到每个视图,但我正在寻找一个我可以更改的点,这将限制应用程序范围内的节点查询。我更新了问题以澄清。
  • 在您的回答中,您可以选择添加UserNodeManager 并声明NodeListViewget_queryset 将被覆盖。但是我理解这一点,因为所有视图都需要重复相同的get_queryset,这不是 DRY。我的意图是更改信号 UserNodeManager 以便任何查询(来自任何 HTML 视图、API 端点等)都使用这个“主”查询集。这可能吗?
  • @user1330734 请参阅答案的更新部分。我希望它有所帮助。
【解决方案2】:

实现此目的的最佳方法是使用组和自定义权限。您可以为每个组织添加一个组,并为这些组在您的节点上设置正确的权限。

看看这篇文章,它可能会有所帮助:User Groups with Custom Permissions in Django

【讨论】:

  • 感谢 Raydel 的建议,但如果我走在正确的轨道上并且可以在模型级别定义 query_set,那么与定义其他组相比,它的工作/维护应该更少。目的是防止不安全的直接对象引用。
  • @user1330 我明白了
【解决方案3】:

@ruddra 再次感谢您的指导。

虽然您的中间件示例对我没有影响(因为用户仍然可以看到其他人的对象),但我能够将其与 django 文档一起使用,最终实现类似于以下内容的管理器:

class UserDeviceManager(models.Manager):
    def get_queryset(self):
        request = CrequestMiddleware.get_request()
        return super().get_queryset().filter(organisation=request.user.organisation)

【讨论】:

    猜你喜欢
    • 2019-07-11
    • 2016-04-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-11
    相关资源
    最近更新 更多