概述
django-guardian
是為Django提供額外的基於對象權限的身份驗證后端。
特征
-
Django的對象全新啊
-
匿名用戶的支持
-
高級API
-
經過嚴密測試
-
Django admin的整合
-
裝飾器
安裝
要求Django1.7或更高版本
pip install django-guardian
或
easy_install django-guardian
配置
安裝完成后,我們可以將django-guardian
加入到我們的項目。首先在settings里將guardian加入到INSTALLED_APPS
INSTALLED_APPS = (
# ...
'guardian',
)
然后加入到身份驗證后端AUTHENTICATION_BACKENDS
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', # 這是Django默認的
'guardian.backends.ObjectPermissionBackend', # 這是guardian的
)
注意:一旦我們將
django-guardian
配置進我們的項目,當我們調用migrate命令將會創建一個匿名用戶的實例(名為AnonymousUser
)。guardian的匿名用戶與Django的匿名用戶不同。Django匿名用戶在數據庫中沒有條目,但是Guardian匿名用戶有。這意味着以下代碼將返回意外的結果。request.user.is_anonymous = True
額外設置
GUARDIAN_RAISE_403
如果GUARDIAN_RAISE_403
設置為True
,guardian將會拋出django.core.exceptions.PermissionDenied
異常,而不是返回一個空的django.http.HttpResponseForbidden
GUARDIAN_RENDER_403
和GUARDIAN_RAISE_403
不能同時設置為True
。否則將拋出django.core.exceptions.ImproperlyConfigured
異常
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
。
示例項目
准備模型和自定義權限
假設我們有以下模型
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_模型名
分配對象權限
我們可以使用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")
>>> assign_perm("change_task", group, task)
<GroupObjectPermission: GroupObjectPermission object>
>>> jack = User.objects.create(username="jack")
>>> jack.has_perm('change_task', task)
False
>>> jack.groups.add(group)
>>> jack.has_perm('change_task', task)
True
檢查對象權限
標准方式
之前的例子我們已經用到了,我們可以使用用戶實例的has_perm
來檢查是否有某種權限。
在視圖中使用
除了Django提供的has_perm
外,django-guardian
還提供了一些常用的方法幫助我們檢查對象權限
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(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
孤兒對象許可
所謂孤兒許可,就是沒用的許可。在大多數情況下,可能沒啥事兒,但是一旦發生,后果有可能非常嚴重。
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
還有一個是定期調用guardian.utils.clean_orphan_obj_perms()。該函數會返回刪除的對象數目。在python的世界中,我們可以使用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=StudyGrou