如何设置 Django 权限以特定于某个模型的实例?
Posted
技术标签:
【中文标题】如何设置 Django 权限以特定于某个模型的实例?【英文标题】:How to setup Django permissions to be specific to a certain model's instances? 【发布时间】:2021-12-02 19:54:46 【问题描述】:请考虑一个简单的 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 论坛上的一些相关问题可以在这里找到:
Authorization on sets of resources How are you handling user permissions in more complex projects?相关 *** 问题:
Django permissions via related objects permissions【问题讨论】:
无法通过 Django 授权系统完成对象级权限。对于您的用例,您需要维护一个数据库表,其中包含用户有权查看的项目列表。 感谢您的建议!我并不是真的在寻找对象级别的权限(我猜),只是为了将项目包含到授权过程中。在我看来,这远没有对象级权限那么精细,对吧?我想您的建议不会解决我的问题,因为我还需要拥有特定于项目的其他权限(例如,创建或更改页面的权限也应该特定于项目)。 "Class Project" 将存储每个项目的详细信息,因此每个项目都是您数据库中的一个对象,其次您不需要设置创建或更改页面的权限。每当用户尝试对项目执行任何操作(例如添加新页面)时,您只需检查一个表,用户是否有权访问该特定项目。 我当时没有很好地解释我的情况,抱歉。我确实需要权限来创建、更改(和其他自定义操作)特定于单个项目的权限。这是一个现有的软件项目,它使用 Django 的权限系统进行粒度(即单个操作)授权。这在管理单个项目时效果很好,但现在我想介绍在保持细粒度权限的同时管理多个项目的可能性。 您能说明您希望如何使用这些权限吗? 【参考方案1】:原始答案:使用django-guardian
编辑。 正如 cmets 中所讨论的,我认为 django-guardian 提供了实现这一目标的最简单和最干净的方法。但是,另一种解决方案是创建自定义用户。
-
创建自定义用户模型。 how-to
在您的新用户模型中覆盖
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)
【讨论】:
非常感谢您的回答,菲利克斯!我认为我的主要问题是:如何创建特定于项目的权限并使其保持最新。据我所知,我在DjangoObjectPermissions
和django-guardian
中也需要它们,对吧?感谢您的指点和其他想法!
我不明白你的意思。你能举个例子,也许我可以进一步帮助你?了解您计划如何创建和访问页面和其他对象会很有帮助。
当然,我将尝试给出一个具体示例来说明我想要实现的目标:我在管理界面中创建了两个项目。我想将查看第一个项目的页面的权限分配给现有用户,并阻止他们查看另一个项目的页面。我如何/在哪里存储有关页面权限及其所指项目的信息?稍微改变你的第一个例子:我如何才能问request.user.has_perm('view_page', project)
? (项目将在管理界面中创建,页面将由用户在自定义视图中创建。)
我认为你只需要安装 django-guardian,然后在我链接的 admin 中查看如何启用它的文档。然后您就可以将用户分配给项目。
我已对您应该尝试的步骤进行了编辑。我没有经常使用 django-guardian,所以我不确定它在管理面板中的外观。但我知道您将能够为对象分配权限。【参考方案2】:
我的回答是基于a user should be able to have a view_page permission for one project instance, and don't have it for another instance.
所以基本上你必须捕获first user visit == first model instance
,你可以创建FirstVisit model
,它将使用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()
基于此solution
我建议使用设备(计算机或智能手机)Mac 地址而不是使用 getmac 的 url 来进行最大的首次访问检查
【讨论】:
感谢您的回答!不幸的是,这并不能解决我的问题,我想我的问题在您引用的部分中是模棱两可的。我的意思是:用户应该有可能在某些项目中拥有view_page
权限,而在其他项目中则没有。我的主要问题是了解如何使用/扩展Django's permission system 以适应我的用例。
我认为当前案例的最佳方法是创建User Groups
并为每个组设置权限。更多细节在这里botreetechnologies.com/blog/django-user-groups-and-permission
这是解决我主要问题的有趣方式,谢谢!本质上,他们在创建新项目时动态创建权限。权限代号包含特定于它们的项目 ID。这当然是一种可能的解决方案,并且不需要扩展权限系统。我有点担心保持权限是最新的,但这似乎是可行的。随意调整您的答案!不过,我想再等几天,看看是否有更清洁的解决方案。【参考方案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"]
【讨论】:
以上是关于如何设置 Django 权限以特定于某个模型的实例?的主要内容,如果未能解决你的问题,请参考以下文章