django guardian 对象级别权限设计
转自并修改: https://blog.csdn.net/bbwangj/article/details/89159727
https://django-guardian.readthedocs.io/en/latest/userguide/assign/
安装
pip install django-guardian
配置 setting.py
1、setting.py 将 guardian 加入到 INSTALLED_APPS
INSTALLED_APPS = (
)

2、setting.py 加入到身份验证后端 AUTHENTICATION_BACKENDS
AUTHENTICATION_BACKENDS = (
)

注意:将 django-guardian 配置进项目,当使用 migrate 命令时 auth_user 表里面会创建一个匿名用户的实例(名为 AnonymousUser )。guardian的匿名用户与Django的匿名用户不同。Django匿名用户在数据库中没有条目,但是Guardian 匿名用户有。

额外设置
GUARDIAN_RAISE_403:如果GUARDIAN_RAISE_403设置为True,guardian将会抛出 django.core.exceptions.PermissionDenied 异常,而不是返回一个空的 django.http.HttpResponseForbidden
GUARDIAN_RENDER_403:如果GUARDIAN_RENDER_403设置为True,将会尝试渲染403响应,而不是返回空的django.http.HttpResponseForbidden。模板文件将通过GUARDIAN_TEMPLATE_403来设置。
ANONYMOUS_USER_NAME用来设置匿名用户的用户名,默认为 AnonymousUser。
GUARDIAN_GET_INIT_ANONYMOUS_USER:Guardian支持匿名用户的对象级权限,但是在我们的项目中,我们使用自定义用户模型,默认功能可能会失败。这可能导致guardian每次migrate之后尝试创建匿名用户的问题。将使用此设置指向的功能来获取要创建的对象。一旦获取,save方法将在该实例上被调用。默认值为guardian.ctypes.get_default_content_type
GUARDIAN_GET_CONTENT_TYPE:Guardian允许应用程序提供自定义函数以从对象和模型中检索内容类型。当类或类层次结构以ContentType非标准方式使用框架时,这是有用的。大多数应用程序不必更改此设置。
例如,当使用django-polymorphic适用于所有子模型的基本模型上的权限时,这是有用的。在这种情况下,自定义函数将返回ContentType多态模型的基类和ContentType非多态类的常规模型。
默认为guardian.ctypes.get_default_content_type。
与Django admin集成
Django配有优秀和广泛使用的admin应用程序。它为Django应用程序提供基本的内容管理。具有访问管理面板的用户可以管理系统提供的用户,组,权限和其他数据。django-guardian 为Django的admin提供简单的对象许可管理集成。
设置和使用对象权限:
首先当然是设置和使用对象权限了,guardian 提供了一个简单的方法:
guardian.shortcuts.assign(perm, user_or_group, obj=None),这个方法接受3个参数:
- perm,这个参数是一个字符串,代表一个许可,格式必须为<app>.<perm_codename>或者<perm_codename>。但是如果第三个参数是None,则必须为<app>.<perm_codename>格式。因此建议还是统一使用<app>.<perm_codename>格式。注意app并不是app的全路径,而是最后一级的模块名。这一点和INSTALL_APP中的app全路径不同,如果你的app module不只一级的话,这地方一定要注意。
- user_or_group,这个参数是一个User或者Group类型的对象。
- obj,这个参数就是相关的对象了。该参数是可省略的,如果省略则赋予Model权限。
通过这个方法我们可以很方便通过传入一个 <app>.<perm_codename> 格式的字符串来给用户User或组Group赋予权限了。如果不传入第三个参数,则可以当作 User.user_permissions.add(permissioninstance) 的快捷方式。
下面是赋予模型级别的权限:
from guardian.shortcuts import assign user = User.objects.create(username='liuyong') assign('app.view_task', user) # 赋予模型级别的权限 user.has_perm('app.view_task') >>True
注意,一旦赋予模型级的权限,那么所有该模型的对象级别的权限就都有了,所以应该先从对象级别进行设置,清空刚刚分配的权限然后再设置对象权限:
user = User.objects.get(username='liuyong') user.user_permissions.clear() # 清空刚刚分配的权限 task = Task.objects.create(summary='Some job', content='') # 创建一个对象 assign('app.view_task', user, task) # 为用户user设置对象权限(task是一个对象,对应Task表格中的一行) user = User.objects.get(username='liuyong')#刷新缓存 user.has_perm('app.view_task',task) >>True user.has_perm('app.view_task')#模型级别的权限还没有 >>False
我们也可以通过设置 group 来使用户具有相应的权限:
>>> group = Group.objects.create(name='employees') # 创建group >>> assign('change_task', group, task) # group组有task的权限 >>> user.has_perm('change_task', task) False >>> # user还不是employees组的成员,我们加入一下 >>> user.groups.add(group) ## user还不是employees组的成员,我们加入一下 >>> user.has_perm('change_task', task) True
接下来是删除某个用户对某个对象的某种许可,我们需要使用 guardian.shortcuts 模块中的 remove_perm() 函数。这个函数的签名和 assign 相同,都是三个:
guardian.shortcuts.remove_perm(perm,user_or_group=None, obj=None)
样例代码:
>>> from guardian.shortcuts import remove_perm >>> remove_perm('change_site', user, site) # >>> user = User.objects.get(username='joe') #刷新user对象缓存 >>> joe.has_perm('change_site', site) # user.has_perm('change_site', site) False
上面就是guadian的安装配置和基本使用方法,下面介绍在 Django 的 View 中所能使用的一些 helper 函数。
Guardian在View中的使用
除了 Django 的 user.has_perm 方法之外,guardian 提供了一些函数。
1、guardian.shortcuts.get_perms(user_or_group,obj)
该方法返回“user对象或者group对象”对obj对象所有的权限。这个行数接受两个参数,一个是user对象或者group对象,一个是相关的对象。
比如我们可以用:
'permcodename' in get_perms(group,obj) # 判断该组是否有这个权限,因为group没有has_perm方法。
2、guardian.shortcuts.get_objects_for_user(user, perms, klass=None, use_groups=True, any_perm=False)
该函数获得该用户下指定perm列表中的所有对象。
比如我要获得某一个用户,拥有编辑权限的所有帖子。
get_objects_for_user(user,'app.change_post')
>>所有可编辑的帖子
3、guadian.core.ObjectPermissionChecker(user_or_group)
该方法是一个用来判断权限的包装器,针对user和group提供权限相关的访问方法,主要有has_perm(perm,obj)和get_perms(obj)两个方法。并且提供缓存机制,在多次查找权限的时候,可以使用它。
>>> epser = User.objects.get(username='esper') >>> site = Site.objects.get_current() >>> from guardian.core import ObjectPermissionChecker >>> checker = ObjectPermissionChecker(esper) # 我们也可以传入组group对象 >>> checker.has_perm('change_site', site) True >>> checker.has_perm('add_site', site) # 这次将不会产生数据库查询 False >>> checker.get_perms(site) [u'change_site']
4、使用view的decorator来减少代码
下面的代码,演示了通过decorator控制一个view函数的访问。我们要做到的是,只有拥有对name=foobars的Group对象拥有auth.change_group权限的用户,才能够执行这个view函数,否则返回的将是状态码为403的Response对象。
>>> user_joe = User.objects.get(username='user_joe') >>> group_foobars = Group.objects.create(name='group_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 = user_joe >>> edit_group(request, group_name='group_foobars') <django.http.HttpResponseForbidden object at 0x102b43dd0> >>> user_joe.groups.add(group_foobars) >>> edit_group(request, group_name='group_foobars') <django.http.HttpResponseForbidden object at 0x102b43e50> >>> from guardian.shortcuts import assign >>> assign('auth.change_group', user_joe, group_foobars) <UserObjectPermission: group_foobars | user_joe | change_group> >>> edit_group(request, group_name='group_foobars') <django.http.HttpResponse object at 0x102b8c8d0> # 这时,我们已经分配了权限,因此我们的view方法得以顺利访问了。
Guardian 在模版中的使用
和Django一样,我们也需要在界面上进行权限控制以显示不同的界面。
Guardian提供了标签:
get_obj_perms
需要加载guardian_tags标签库,在需要使用guardian标签的模版上面,将其引用近来:
{% load guardian_tags %}
标签格式为:
{% get_obj_perms user/group for obj as "context_var" %}
例子代码如下:
{% get_obj_perms request.user for flatpage as "flatpage_perms" %}
{% if "delete_flatpage" in flatpage_perms %}
<a href="/pages/delete?target={{ flatpage.url }}">Remove page</a>
{% endif %}
实例项目
准备模型和自定义权限
from django.db import models from django.contrib.auth.models import User # Create your models here. class Task(models.Model): summary = models.CharField(max_length=32) content = models.TextField() reported_by = models.ForeignKey(User) created_at = models.DateTimeField(auto_now_add=True) class Meta: permissions = ( ('view_task', 'View task'), )
说明:
permissions是我们自定义的权限,当我们调用migrate命令的时候,view_task将会被添加到默认的权限集合中。
默认情况下Django为每个模型注册3个权限
* add_模型名
* change_模型名
* delete_模型名
By default, Django adds 4 permissions for each registered model:
- add_modelname
- change_modelname
- delete_modelname
- view_modelname
分配对象权限:
使用 guardian.shortcuts.assign_perm() 方法可以为用户/组分配对象权限
为用户分配权限
>>> from django.contrib.auth.models import User >>> from todo.models import Task >>> from guardian.shortcuts import assign_perm >>> boss = User.objects.create(username="Big Boss") # 创建用户boss >>> joe = User.objects.create(username="joe") # 创建用户joe >>> task = Task.objects.create(summary="Some job", content="", reported_by=boss) # 创建Task对象 >>> joe.has_perm('view_task', task) # 默认用户对这个对象没有权限 False >>> assign_perm('view_task', joe, task) # 为用户joe分配权限 <UserObjectPermission: UserObjectPermission object> >>> joe.has_perm('view_task', task) True
为用户组分配权限
>>> from django.contrib.auth.models import Group >>> group = Group.objects.create(name="employees") # 创建group employees >>> assign_perm("change_task", group, task) <GroupObjectPermission: GroupObjectPermission object> >>> jack = User.objects.create(username="jack") # 创建用户jack >>> jack.has_perm('change_task', task) False >>> jack.groups.add(group) # jack user还不是employees组的成员,我们加入一下 >>> jack.has_perm('change_task', task) True
检查对象权限
标准方式:使用用户实例的 has_perm 来检查是否有某种权限。
在视图中使用
get_perms
>>> from guardian.shortcuts import get_perms >>> 'change_task' in get_perms(joe, task) False >>> 'change_task' in get_perms(jack, task) True
建议尽量使用标准 has_perm 方法。但是对于Group实例,它不是那么容易,get_perms 解决这个问题很方便,因为它接受 User和Group实例。如果我们需要做更多的工作,我们可以使用ObjectPermissionChecker 这个低级类。
也可以使用 get_user_perms 获得直接分配权限给用户(而不是从它的超级用户权限或组成员资格继承的权限)。同样的,get_group_perms仅返回其是通过用户组的权限。
get_objects_for_user
有时候我们需要根据特定的用户,对象的类型和提供的权限来获取对象列表,例如
>>> from guardian.shortcuts import get_objects_for_user >>> get_objects_for_user('view_task', joe) <QuerySet [<Task: Task object>] >>> get_objects_for_user(jack, 'todo.change_task') <QuerySet [<Task: Task object>]> >>> get_objects_for_user(jack, 'todo.view_task') <QuerySet []>
ObjectPermissionChecker
guardian.core.ObjectPermissionChecker 用于检查特定对象的用户/组的权限。因为他缓存结果,因此我们可以在多次检查权限的代码的一部分中使用
>>> from guardian.core import ObjectPermissionChecker >>> cheker = ObjectPermissionChecker(joe) >>> checker = ObjectPermissionChecker(joe) >>> checker.has_perm('view_task', task) True >>> checker.has_perm('change_task', task) False
使用装饰器
标准permission_required装饰器不允许检查对象权限。django-guardian随附两个装饰器,这可能有助于简单的对象权限检查,但请记住,在装饰视图被调用之前,这些装饰器会触发数据库——这意味着如果在视图中进行类似的查找,那么最可能的一个(或更多,取决于查找)会发生额外的数据库查询。
在模板中使用
django-guardian附带特殊模板标签guardian.templatetags.guardian_tags.get_obj_perms(),可以存储给定用户/组和实例对的对象权限。为了使用它,我们需要在模板中放置以下内容:
{% load guardian_tags %}
guardian.templatetags.guardian_tags.get_obj_perms(parser, token)返回给定用户或者组和对象(Model实例)的权限列表。
调用格式为
{% get_obj_perms user/group for obj as "context_var" %}
移除对象权限
删除对象权限和分配一样简单,我们使用 guardian.shortcuts.remove_perm() 来移除权限
>>> remove_perm("veiw_task",joe, task) (0, {'guardian.UserObjectPermission': 0}) >>> joe.has_perm("view_task", task) True >>> remove_perm("view_task",joe, task) (1, {'guardian.UserObjectPermission': 1}) >>> joe.has_perm("view_task", task) False
孤儿对象许可(Orphaned object permissions)
所谓孤儿许可,就是没用的许可,大多数情况下,可能没事,但一旦发生,后果有可能非常严重。
Guardian用来纪录某用户对某个模型对象有某个权限的纪录时是使用UserObjectPermission和GroupObjectPermission对象纪录的。其中对于object的引用是contenttype对象(标示是那个模型类)和pk主键,对于用户则是对User表的外键引用。
比方说,有一个对象A。我们通过权限设置,设定joe用户对该对象有着编辑权限。忽然有一天,用户joe被删除了。可想而知,我们分配而产生的UserObjectPermission对象仍然在数据库里面,记录着:joe
有对A的编辑权限。又有一天,一个用户注册了一个用户,用户username为joe。因为之前的那个纪录,joe用户拥有对A的编辑权限。而此joe非彼joe,我们犯了一个大错误!
再比如说,当我们删除了某一个对象的时候,而这个对象的某种权限已经被赋予给某个用户,那么这个权限的纪录也就失效了。如果什么时候和曾经删除过的对象是同一个模型类,而且主键和以前的那个相同,那么用户也就有可能对其本不应该拥有权限的对象有了权限。呵呵,说起来有点绕,但是应该很容易理解。
因此,当删除User和相关Object时,一定要删除其相关的所有UserObjectPermission和GroupObjectPermission对象。要解决这个问题有三种,一是显式编码,二是通过其提供的自定义django命令:
$ python manage.py clean_orphan_obj_perms
Removed 11 object permission entries with no targets
还有一个是定期调用guardian.utils.clean_orphan_obj_perms(),该函数会返回删除的对象数目。也可使用celery定期调度任务。
但是自定义命令和定期调度都不是生产环境的解决办法。还是需要手动编码实现,最优雅的方式还是加上post_delete signal给User或Object对象。如下:
from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType from django.db.models import Q from django.db.models.signals import pre_delete from guardian.models import UserObjectPermission from guardian.models import GroupObjectPermission from school.models import StudyGroup def remove_obj_perms_connected_with_user(sender, instance, **kwargs): filters = Q(content_type=ContentType.objects.get_for_model(instance), object_pk=instance.pk) UserObjectPermission.objects.filter(filters).delete() GroupObjectPermission.objects.filter(filters).delete() pre_delete.connect(remove_obj_perms_connected_with_user, sender=StudyGroup)
https://django-guardian.readthedocs.io/en/latest/
========================================
========================================
========================================
REF
https://blog.51cto.com/u_6186189/6950636
https://blog.csdn.net/bbwangj/article/details/89159727
https://blog.csdn.net/ldq_sd/article/details/131249269

浙公网安备 33010602011771号