如何设置 Django 权限以特定于某个模型的实例?

问题描述 投票:0回答:3

请考虑一个简单的 Django 应用程序,其中包含一个名为

Project
的中心模型。此应用程序的其他资源始终与特定的
Project
相关联。

示例代码:

class Project(models.Model):
    pass

class Page(models.Model):
    project = models.ForeignKey(Project)

我想利用 Django 的权限系统来设置每个现有项目的细粒度权限。在示例的情况下,用户应该能够对某些项目实例拥有

view_page
权限,而对其他项目实例则不具备该权限。

最后,我希望有一个像

has_perm
这样的函数,它将权限代号和项目作为输入,如果当前用户在给定项目中拥有权限,则返回 True。

有没有办法扩展或替换Django的授权系统来实现这样的功能?

我可以扩展用户的

Group
模型以包含指向
Project
的链接并检查组的项目及其权限。但这并不优雅,并且不允许向单个用户分配权限。


Django 论坛上的一些相关问题可以在这里找到:

相关 StackOverflow 问题:

python django permissions
3个回答
3
投票

我对(谢天谢地!)提出的答案不太满意,因为它们似乎会带来复杂性或维护方面的开销。特别是对于

django-guardian
,我需要一种方法来使这些对象级权限保持最新,同时可能会遭受(轻微)性能损失。动态创建权限也是如此;我需要一种方法来使这些内容保持最新状态,并且会偏离(仅)在模型中定义权限的标准方法。

但这两个答案实际上都鼓励我更详细地了解 Django 的身份验证和授权系统。就在那时,我意识到将其扩展到我的需求是非常可行的(就像 Django 中经常发生的那样)。


我通过引入新模型

ProjectPermission
解决了这个问题,该模型将
Permission
链接到项目,并且可以分配给用户和组。该模型表示用户或组拥有特定项目的权限。

为了利用此模型,我扩展了

ModelBackend
并引入了并行权限检查
has_project_perm
,用于检查用户是否拥有特定项目的权限。该代码主要类似于
has_perm
中定义的
ModelBackend
的默认路径。

通过利用默认权限检查,如果用户具有特定于项目的权限或以老式方式(我称之为“全局”)具有权限,

has_project_perm
将返回 True。这样做允许我分配对所有项目都有效的权限,而无需明确说明。

最后,我通过引入新方法

has_project_perm
来扩展自定义用户模型以访问新的权限检查。


# models.py

from django.contrib import auth
from django.contrib.auth.models import AbstractUser, Group, Permission
from django.core.exceptions import PermissionDenied
from django.db import models

from showbase.users.models import User


class ProjectPermission(models.Model):
    """A permission that is valid for a specific project."""

    project = models.ForeignKey(Project, on_delete=models.CASCADE)
    base_permission = models.ForeignKey(
        Permission, on_delete=models.CASCADE, related_name="project_permission"
    )
    users = models.ManyToManyField(User, related_name="user_project_permissions")
    groups = models.ManyToManyField(Group, related_name="project_permissions")

    class Meta:
        indexes = [models.Index(fields=["project", "base_permission"])]
        unique_together = ["project", "base_permission"]


def _user_has_project_perm(user, perm, project):
    """
    A backend can raise `PermissionDenied` to short-circuit permission checking.
    """
    for backend in auth.get_backends():
        if not hasattr(backend, "has_project_perm"):
            continue
        try:
            if backend.has_project_perm(user, perm, project):
                return True
        except PermissionDenied:
            return False
    return False


class User(AbstractUser):
    def has_project_perm(self, perm, project):
        """Return True if the user has the specified permission in a project."""
        # Active superusers have all permissions.
        if self.is_active and self.is_superuser:
            return True

        # Otherwise we need to check the backends.
        return _user_has_project_perm(self, perm, project)
# auth_backends.py

from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import Permission


class ProjectBackend(ModelBackend):
    """A backend that understands project-specific authorization."""

    def _get_user_project_permissions(self, user_obj, project):
        return Permission.objects.filter(
            project_permission__users=user_obj, project_permission__project=project
        )

    def _get_group_project_permissions(self, user_obj, project):
        user_groups_field = get_user_model()._meta.get_field("groups")
        user_groups_query = (
            "project_permission__groups__%s" % user_groups_field.related_query_name()
        )
        return Permission.objects.filter(
            **{user_groups_query: user_obj}, project_permission__project=project
        )

    def _get_project_permissions(self, user_obj, project, from_name):
        if not user_obj.is_active or user_obj.is_anonymous:
            return set()

        perm_cache_name = f"_{from_name}_project_{project.pk}_perm_cache"
        if not hasattr(user_obj, perm_cache_name):
            if user_obj.is_superuser:
                perms = Permission.objects.all()
            else:
                perms = getattr(self, "_get_%s_project_permissions" % from_name)(
                    user_obj, project
                )
            perms = perms.values_list("content_type__app_label", "codename").order_by()
            setattr(
                user_obj, perm_cache_name, {"%s.%s" % (ct, name) for ct, name in perms}
            )
        return getattr(user_obj, perm_cache_name)

    def get_user_project_permissions(self, user_obj, project):
        return self._get_project_permissions(user_obj, project, "user")

    def get_group_project_permissions(self, user_obj, project):
        return self._get_project_permissions(user_obj, project, "group")

    def get_all_project_permissions(self, user_obj, project):
        return {
            *self.get_user_project_permissions(user_obj, project),
            *self.get_group_project_permissions(user_obj, project),
            *self.get_user_permissions(user_obj),
            *self.get_group_permissions(user_obj),
        }

    def has_project_perm(self, user_obj, perm, project):
        return perm in self.get_all_project_permissions(user_obj, project)
# settings.py

AUTHENTICATION_BACKENDS = ["django_project.projects.auth_backends.ProjectBackend"]

2
投票

原始答案:使用django-guardian

编辑。 正如评论中所讨论的,我认为 django-guardian 提供了实现这一目标的最简单、最干净的方法。但是,另一种解决方案是创建自定义用户。

  1. 创建自定义用户模型。 操作方法
  2. 重写新用户模型中的
    has_perm
    方法。
from django.db import models
from my_app import Project

class CustomUser(...)
    projects = models.ManyToManyField(Project)

    def has_perm(self, perm, obj=None):
        if isinstance(obj, Project):
            if not obj in self.projects.objects.all():
                return False
        return super().has_perm(perm, obj)

1
投票

我的答案是基于“用户应该能够对一个项目实例拥有 view_page 权限,而对另一个项目实例则没有该权限。”

所以基本上你必须捕获“第一个用户访问==第一个模型实例”,你可以创建“FirstVisit”模型,它将使用

url
user.id
page.id
捕获并保存每个第一个实例,然后你检查如果存在的话。

# model

class Project(models.Model):
   pass

class Page(models.Model):
    project = models.ForeignKey(Project)

class FirstVisit(models.Model):
    url = models.URLField()
    user = models.ForeignKey(User)
    page = models.ForeignKey(Page)


#views.py

def my_view(request):
   if not FisrtVisit.objects.filter(user=request.user.id, url=request.path, page=request.page.id).exists():
      # first time visit == first instance
      #your code...
      FisrtVisit(user=request.user, url=request.path, page=request.page.id).save()

基于此解决方案

我建议使用设备(计算机或智能手机)Mac 地址而不是使用 getmac 的 url,以进行最大程度的首次访问检查

© www.soinside.com 2019 - 2024. All rights reserved.