胖蔡说技术
随便扯扯

Django-guardian提供额外的身份验证后端

Django-guardianDjango实现Django对象权限,提供身份验证后端,其扩展特性如下:

  • 匿名用户支持
  • 高级API
  • Django对象权限
  • 较全面的测试覆盖
  • 装饰器支持
  • Django的管理集成:Django-grappelli

安装

此应用程序需要 Django 2.2 或更高版本,这是使用 django-guardian 之前的唯一先决条件。

$ pip install django-guardian

配置

安装后,我们可以为对象权限处理准备我们的项目。在设置模块中,我们需要将监护人添加到 INSTALLED_APPS

INSTALLED_APPS = (
    # ...
    'guardian',
)

并在guardian权限后端添加钩子:

AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend', # this is default
    'guardian.backends.ObjectPermissionBackend',
)

一旦项目被配置为与 django-guardian 一起工作,调用 migrate 管理命令将为匿名用户支持创建用户实例(名称为 AnonymousUser)。

Guardian 匿名用户不同于 Django 匿名用户。 Django 匿名用户在数据库中没有条目,但是 Guardian 匿名用户有。这意味着以下代码将返回意外结果:

from django.contrib.auth import get_user_model
User = get_user_model()
anon = User.get_anonymous()
anon.is_anonymous  # returns False

我们可以将 id 更改为我们喜欢的任何内容。项目现在应该可以使用对象权限了。

可选设置

Guardian 具有以下可选配置变量:

  • GUARDIAN_RAISE_403:新版本 1.0.4。如果设置为 Trueguardian 将引发 django.core.exceptions.PermissionDenied 错误,而不是返回空的 django.http.HttpResponseForbidden
  • GUARDIAN_RENDER_403:新版本 1.0.4。如果设置为true,监护人将尝试呈现403响应,而不是返回无完整的django.http.httpresponsessonsefordforbidden。将使用guardian_template_403指向的模板来执行此操作。默认值为false
  • GUARDIAN_TEMPLATE_403:使用状态代码403(即cermission_required)使用哪种模板用于响应。默认为403.html
  • ANONYMOUS_USER_NAME:这是匿名用户的用户名。用于创建匿名用户并随后根据需要获取匿名用户。如果 ANONYMOUS_USER_NAME 设置为 None,则禁用匿名用户对象权限。如果创建用户对象来表示匿名用户在您的环境中会出现问题,您可能需要选择此选项。
  • GUARDIAN_GET_INIT_ANONYMOUS_USERGuardian 支持匿名用户的对象级权限,但是在我们的项目中使用自定义用户模型时,默认功能可能会失败。这可能会导致问题,因为监护人会在每次迁移调用后尝试创建匿名用户。使用此设置指向的函数检索将要创建的对象。一旦检索到,将在该实例上调用 save 方法。
  • GUARDIAN_GET_CONTENT_TYPE:Guardian 允许应用程序提供自定义函数来从对象和模型中检索内容类型。当类或类层次结构以非标准方式使用 ContentType 框架时,这很有用。大多数应用程序不必更改此设置。
  • GUARDIAN_AUTO_PREFETCH:默认False,对于使用标准 ContentType 接口和默认 UserObjectPermission GroupObjectPermission 模型的原始部署,Guardian 可以自动预取所有对象类型的所有用户权限。当由于大量模型类型导致 O(n) 查询而导致手动预取不可行时,这可能很有用。此设置可能与非标准部署不兼容,并且仅应在非预取调用会导致大量查询或延迟特别重要时使用。
  • GUARDIAN_USER_OBJ_PERMS_MODEL:默认'guardian.UserObjectPermission',允许自定义模型覆盖默认的 UserObjectPermission 模型。自定义模型需要最低限度地继承自 UserObjectPermissionAbstract。这仅在项目开始时设置时自动支持。这在项目开始后不受支持。如果依赖库没有为模型调用UserObjectPermission = get_user_obj_perms_model(),那么依赖库不支持该特性。

定义自定义用户对象权限模型:

from guardian.models import UserObjectPermissionAbstract
class BigUserObjectPermission(UserObjectPermissionAbstract):
   id = models.BigAutoField(editable=False, unique=True, primary_key=True)
   class Meta(UserObjectPermissionAbstract.Meta):
      abstract = False
      indexes = [
         *UserObjectPermissionAbstract.Meta.indexes,
         models.Index(fields=['content_type', 'object_pk', 'user']),
      ]

配置guardian以在settings.py中使用自定义模型:

GUARDIAN_USER_OBJ_PERMS_MODEL = 'myapp.BigUserObjectPermission'


要访问模型,请使用不带参数的 get_user_obj_perms_model()

from guardian.utils import get_user_obj_perms_model
UserObjectPermission = get_user_obj_perms_model()
  • GUARDIAN_GROUP_OBJ_PERMS_MODEL:允许自定义模型覆盖默认的 GroupObjectPermission 模型。自定义模型需要最低限度地继承自 GroupObjectPermissionAbstract。这仅在项目开始时设置时自动支持。这在项目开始后不受支持。如果依赖库没有为模型调用GroupObjectPermission = get_user_obj_perms_model(),那么依赖库不支持该特性。
from guardian.models import GroupObjectPermissionAbstract
class BigGroupObjectPermission(GroupObjectPermissionAbstract):
   id = models.BigAutoField(editable=False, unique=True, primary_key=True)
   class Meta(GroupObjectPermissionAbstract.Meta):
      abstract = False
      indexes = [
         *GroupObjectPermissionAbstract.Meta.indexes,
         models.Index(fields=['content_type', 'object_pk', 'group']),
      ]

示例

分配对象权限

为模型创建权限后,分配对象权限应该非常简单。

class Task(models.Model):
    summary = models.CharField(max_length=32)
    content = models.TextField()
    reported_by = models.ForeignKey(User, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        permissions = (
            ('assign_task', 'Assign task'),
        )

在信号中分配权限

请注意,匿名用户是在创建权限之前创建的。这可能会导致 Django 信号,例如在创建权限之前发送 post_save。在处理信号时,您需要考虑到这一点。

@receiver(post_save, sender=User)
def user_post_save(sender, **kwargs):
    """
    Create a Profile instance for all newly created User instances. We only
    run on user creation to avoid having to check for existence on each call
    to User.save.
    """
    user, created = kwargs["instance"], kwargs["created"]
    if created and user.username != settings.ANONYMOUS_USER_NAME:
        from profiles.models import Profile
        profile = Profile.objects.create(pk=user.pk, user=user, creator=user)
        assign_perm("change_user", user, user)
        assign_perm("change_profile", user, profile)

检查对象权限

一旦我们分配了一些权限,我们就可以详细了解如何验证用户或组的权限。

标准方式

通常为了检查 Joe 是否被允许更改 Site 对象,我们在 User 实例上调用 has_perm 方法:

>>> from guardian.shortcuts import assign_perm
>>> assign_perm('sites.change_site', joe, site)
<UserObjectPermission: example.com | joe | change_site>
>>> joe = User.objects.get(username='joe')
>>> joe.has_perm('sites.change_site', site)
True

内部视图

除了标准的 has_perm 方法外,django-guardian 还提供了一些有用的对象权限检查助手。

1、get_perms

>>> from guardian.shortcuts import get_perms
>>>
>>> joe = User.objects.get(username='joe')
>>> site = Site.objects.get_current()
>>>
>>> 'change_site' in get_perms(joe, site)
True

使用标准的 has_perm 方法可能更好。但对于 Group 实例来说,它并不那么容易,get_perms 在这里可能很方便,因为它接受 User Group 实例。如果我们需要做更多的工作,我们可以使用下一节中描述的较低级别的 ObjectPermissionChecker 类。

2、get_objects_for_user

有时需要根据特定用户、对象类型和提供的权限提取对象列表。例如,假设在具有自定义 view_project 权限的项目应用程序中有一个项目模型。我们想向我们的用户展示他们实际可以查看的项目。这可以使用 get_objects_for_user 轻松实现:

from django.shortcuts import render
from django.template import RequestContext
from projects.models import Project
from guardian.shortcuts import get_objects_for_user

def user_dashboard(request, template_name='projects/dashboard.html'):
    projects = get_objects_for_user(request.user, 'projects.view_project')
    return render(request, template_name, {'projects': projects},
        RequestContext(request))

ObjectPermissionChecker

django-guardian 的核心模块中,有一个 guardian.core.ObjectPermissionChecker 检查用户/组对特定对象的权限。它缓存结果,因此它可以在我们多次检查权限的部分代码中使用。

>>> joe = User.objects.get(username='joe')
>>> site = Site.objects.get_current()
>>> from guardian.core import ObjectPermissionChecker
>>> checker = ObjectPermissionChecker(joe) # we can pass user or group
>>> checker.has_perm('change_site', site)
True
>>> checker.has_perm('add_site', site) # no additional query made
False
>>> checker.get_perms(site)
[u'change_site']

使用装饰器

标准的 permission_required 装饰器不允许检查对象权限。 django-guardian 附带了两个装饰器,这可能有助于简单的对象权限检查,但请记住,这些装饰器会在调用装饰视图之前访问数据库——这意味着如果在一个视图中进行了类似的查找,那么很可能是一个(或多个,取决于查找)额外的数据库查询会发生。

假设我们将“group_name”参数传递给我们的视图函数,该函数返回表单以编辑组。此外,如果检查失败,我们希望返回 403 代码。这可以使用 permission_required_or_403 装饰器简单地实现:

>>> joe = User.objects.get(username='joe')
>>> foobars = Group.objects.create(name='foobars')
>>>
>>> from guardian.decorators import permission_required_or_403
>>> from django.http import HttpResponse
>>>
>>> @permission_required_or_403('auth.change_group',
>>>     (Group, 'name', 'group_name'))
>>> def edit_group(request, group_name):
>>>     return HttpResponse('some form')
>>>
>>> from django.http import HttpRequest
>>> request = HttpRequest()
>>> request.user = joe
>>> edit_group(request, group_name='foobars')
<django.http.HttpResponseForbidden object at 0x102b43dd0>
>>>
>>> joe.groups.add(foobars)
>>> edit_group(request, group_name='foobars')
<django.http.HttpResponseForbidden object at 0x102b43e50>
>>>
>>> from guardian.shortcuts import assign_perm
>>> assign_perm('auth.change_group', joe, foobars)
<UserObjectPermission: foobars | joe | change_group>
>>>
>>> edit_group(request, group_name='foobars')
<django.http.HttpResponse object at 0x102b8c8d0>
>>> # Note that we now get normal HttpResponse, not forbidden

内部模板

django-guardian 带有特殊的模板标签 guardian.templatetags.guardian_tags.get_obj_perms() 可以存储给定用户/组和实例对的对象权限。为了使用它,我们需要将以下内容放入模板中:

{% load guardian_tags %}

删除对象权限

删除对象权限与分配权限一样简单。我们将使用 guardian.shortcuts.remove_perm() 函数代替 guardian.shortcuts.assign_perm() (它接受相同的参数)。

>>> from guardian.shortcuts import remove_perm
>>> remove_perm('change_site', joe, site)
>>> joe = User.objects.get(username='joe')
>>> joe.has_perm('change_site', site)
False

Admin整合

Django 带有出色且广泛使用的管理应用程序。基本上,它为 Django 应用程序提供内容管理。有权访问管理面板的用户可以管理系统提供的用户、组、权限和其他数据。

django-guardian Django 的管理应用程序提供了简单的对象权限管理集成。

from django.db import models


class Post(models.Model):
    title = models.CharField('title', max_length=64)
    slug = models.SlugField(max_length=64)
    content = models.TextField('content')
    created_at = models.DateTimeField(auto_now_add=True, db_index=True)

    class Meta:
        permissions = (
            ('hide_post', 'Can hide post'),
        )
        get_latest_by = 'created_at'

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return {'post_slug': self.slug}

我们想在管理应用程序中注册 Post 模型。通常,我们会在应用程序的 admin.py 文件中按如下方式执行此操作:

from django.contrib import admin

from posts.models import Post


class PostAdmin(admin.ModelAdmin):
    prepopulated_fields = {"slug": ("title",)}
    list_display = ('title', 'slug', 'created_at')
    search_fields = ('title', 'content')
    ordering = ('-created_at',)
    date_hierarchy = 'created_at'

admin.site.register(Post, PostAdmin)

如果我们想为 Post 模型添加对象权限管理,我们需要将 PostAdmin 基类更改为 GuardedModelAdmin。我们的代码可能如下所示:

from django.contrib import admin

from posts.models import Post

from guardian.admin import GuardedModelAdmin


class PostAdmin(GuardedModelAdmin):
    prepopulated_fields = {"slug": ("title",)}
    list_display = ('title', 'slug', 'created_at')
    search_fields = ('title', 'content')
    ordering = ('-created_at',)
    date_hierarchy = 'created_at'

admin.site.register(Post, PostAdmin)

自定义用户模型

Django具有自定义默认auth.user模型的功能 – 通过摘要抽象器或定义自己的类。这可能非常强大,但是必须谨慎完成。基本上,如果我们为Auther.group(并给出反向关联姓名组),我们应该很好。

默认情况下,Django-Guardian Monkey修补了用户模型以添加一些必要的功能。如果将Guardian导入到模型中,则可能会导致错误。自定义用户模型所处的同一应用程序的py

为了解决此问题,建议您在您的自定义用户模型中添加设置guardian_monkey_patch = false

匿名用户创建

也可以覆盖创建匿名用户实例的默认行为。例如,让我们想象我们的用户模型如下:

from django.contrib.auth.models import AbstractUser
from django.db import models


class CustomUser(AbstractUser):
    real_username = models.CharField(max_length=120, unique=True)
    birth_date = models.DateField()  # field without default value

    USERNAME_FIELD = 'real_username'

为了覆盖创建匿名实例的方式,我们需要使Guardian_get_init_anonymous_user指向我们的自定义实现。例如,让我们定义我们的初始功能:

import datetime

def get_anonymous_user_instance(User):
    return User(real_username='Anonymous', birth_date=datetime.date(1970, 1, 1))

# myapp/models.py
GUARDIAN_GET_INIT_ANONYMOUS_USER = 'myapp.models.get_anonymous_user_instance'
赞(0) 打赏
转载请附上原文出处链接:胖蔡说技术 » Django-guardian提供额外的身份验证后端
分享到: 更多 (0)

请小编喝杯咖啡~

支付宝扫一扫打赏

微信扫一扫打赏