Django实现Object级别的权限控制-django-guardian
在我的系列blog《Django中内置的权限控制》中明确提及到,Django默认并没有提供对Object级别的权限控制,而只是在架构上留了口子。在这篇blog中,我们探讨一个简单流行的Django组件django-guardian来实现Object level permission。
安装配置django-guardian
首先需要安装django-guardian,一般我们喜欢用virtualenv创建一个虚拟环境:
>>virtualenv - - distribute venv >>source venv / bin / activate >>pip install Django >>pip install django - guardian |
这样,我们需要的django-guardian 就安装好了。
接下来我们需要让Django知道它,在INSTALLED_APPS变量中加入guardian:
INSTALLED_APPS = ( 'guardian' , ) |
然后,如果仔细读过《Django中内置的权限控制》的第五篇文章的读者,应该猜到,我们需要添加一个backend到AUTHENTICATION_BACKENDS中,这样django才会具有对对象的权限控制:
AUTHENTICATION_BACKENDS = ( 'django.contrib.auth.backends.ModelBackend' , # django默认的backend 'guardian.backends.ObjectPermissionBackend' , ) |
在guardian中,还支持对匿名用户AnoymousUser的Object级别的权限控制,这种需求很常见,比方说允许匿名发言的论坛或者blog系统。要做到这一点需要在settings中加入:
ANONYMOUS_USER_ID = - 1 |
接下来我们执行python manage syncdb.执行完毕之后,系统将会创建一个User实例,叫做AnonymouseUser。
完成了以上几点,guardian就被安装好了,可以开始使用了。
设置和使用对象权限:
首先当然是设置和使用对象权限了,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 = 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' ) >>> assign( 'change_task' , group, task) >>> user.has_perm( 'change_task' , task) False >>> # user还不是employees组的成员,我们加入一下 >>> user.groups.add(group) >>> user.has_perm( 'change_task' , task) True |
接下来是删除某个用户对某个对象的某种许可,我们需要使用guardian.shortcuts模块中的remove_perm()函数。这个函数的签名和assign相同,都是三个:
guardian.shortcuts.remove_perm(perm,user_or_group=None, obj=None)
样例代码:
1 2 3 4 5 | >>> 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) False |
好上面就是guadian的安装配置和基本使用方法,下面介绍在Django的View中所能使用的一些helper函数。
Guardian在View中的使用
除了Django的user.has_perm方法之外,guardian提供了一些帮助函数能让我们生活的更轻松。
guardian.shortcuts.get_perms(user_or_group,obj)
该方法返回user对象对obj对象所有的权限。这个行数接受两个参数,一个是user对象或者组对象,一个是相关的对象。
比如我们可以用:
'permcodename' in get_perms(group,obj)来判断该组是否有这个权限,因为group没有has_perm方法。
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' ) >>所有可编辑的帖子 |
guadian.core.ObjectPermissionChecker
该方法是一个用来判断权限的包装器,针对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' ] |
使用view的decorator
我们可以使用decorator来减少我们的代码:
下面的代码,演示了通过decorator控制一个view函数的访问。我们要做到的是,只有拥有对name=foobars的Group对象拥有auth.change_group权限的用户,才能够执行这个view函数,否则返回的将是状态码为403的Response对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | >>> 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 >>> assign( 'auth.change_group' , joe, foobars) <UserObjectPermission: foobars | joe | change_group> >>> >>> edit_group(request, group_name = '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 % } |
下面探讨一下稍微复杂一些的情况,关于孤儿对象许可(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命令:
1 2 | $ python manage.py clean_orphan_obj_perms Removed 11 object permission entries with no targets |
还有一个是定期调用guardian.utils.clean_orphan_obj_perms()
该函数会返回删除的对象数目。在python的世界中,我们可以使用celery定期调度这个任务。
但是自定义命令和定期调度都不是合理的生产环境的解决办法。要想真正解决,还是需要手动编码实现,最优雅的方式还是加上post_delete signal给User或Object对象,关于对象的样例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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) |
这样就搞定了,不过需要写一些必要的代码。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET开发智能桌面机器人:用.NET IoT库编写驱动控制两个屏幕
· 用纯.NET开发并制作一个智能桌面机器人:从.NET IoT入门开始
· 一个超经典 WinForm,WPF 卡死问题的终极反思
· ASP.NET Core - 日志记录系统(二)
· .NET 依赖注入中的 Captive Dependency
· 互联网不景气了那就玩玩嵌入式吧,用纯.NET开发并制作一个智能桌面机器人(二):用.NET IoT库
· 几个自学项目的通病,别因为它们浪费了时间!
· 在外漂泊的这几年总结和感悟,展望未来
· .NET 数据拷贝方案选择
· .net工作流elsa-书签