Chr*_*ris 5 python authentication django django-guardian
我希望能够使用创建每个对象的权限django-guardian.
但我想添加一层围绕这些权限的逻辑.例如,如果某人拥有edit_booka Book的权限,那么他们Pages在该书中编辑的权限应该是隐含的.该rules包装似乎理想.
Chr*_*ris 10
以下似乎有效:
import rules
import guardian
@rules.predicate
def is_page_book_editor(user, page):
return user.has_perm('books.edit_book', page.book)
@rules.predicate
def is_page_editor(user, page):
return user.has_perm('pages.edit_page', page)
rules.add_perm('pages.can_edit_page', is_page_book_editor | is_page_editor)
Run Code Online (Sandbox Code Playgroud)
然后检查:
joe.has_perm('pages.can_edit_page', page34)
Run Code Online (Sandbox Code Playgroud)
要么:
@permission_required('pages.can_edit_page', fn=objectgetter(Page, 'page_id'))
def post_update(request, page_id):
# ...
Run Code Online (Sandbox Code Playgroud)
使用身份验证后端定义:
AUTHENTICATION_BACKENDS = (
'rules.permissions.ObjectPermissionBackend',
'django.contrib.auth.backends.ModelBackend',
'guardian.backends.ObjectPermissionBackend',
)
Run Code Online (Sandbox Code Playgroud)
进口:
from django.contrib.auth.models import User
import rules
import guardian
from guardian.shortcuts import assign_perm
from myapp.models import Book, Page
Run Code Online (Sandbox Code Playgroud)
测试:
joe = User.objects.create(username='joe', email='joe@example.com')
page23 = Page.objects.filter(id=123)
assign_perm('edit_page', joe, page23)
joe.has_perm('edit_page', page23)
is_page_editor(joe, page23) # returns True
joe.has_perm('can_edit_page', i) # returns True
rules.remove_perm('can_edit_page')
rules.add_perm('can_edit_page', is_page_book_editor & is_page_editor)
joe.has_perm('can_edit_page', i) # returns False
Run Code Online (Sandbox Code Playgroud)
这样做的一个问题是每次检查规则时,每个谓词都会调用数据库.以下添加了缓存,以便每个规则检查只有一个查询:
@rules.predicate
def is_page_book_viewer(user, instance):
if is_page_book_viewer.context.get('user_perms') is None:
is_page_book_viewer.context['user_perms'] = guardian.shortcuts.get_perms(user, page.book)
return 'view_book' in is_page_book_viewer.context.get('user_perms')
@rules.predicate(bind=True)
def is_page_viewer(self, user, instance):
if self.context.get('user_perms') is None:
self.context['user_perms'] = guardian.shortcuts.get_perms(user, instance)
return 'view_page' in self.context.get('user_perms')
Run Code Online (Sandbox Code Playgroud)
(我在第二个例子中绑定并使用self,但这与使用谓词名称相同.)
当您执行复杂的复合权限时,将django-guardian的通用外键替换为可以由数据库优化和索引的实际外键可能是明智的,如下所示:
class PageUserObjectPermission(UserObjectPermissionBase):
content_object = models.ForeignKey(Page)
class PageGroupObjectPermission(GroupObjectPermissionBase):
content_object = models.ForeignKey(Page)
class BookUserObjectPermission(UserObjectPermissionBase):
content_object = models.ForeignKey(Book)
class BookGroupObjectPermission(GroupObjectPermissionBase):
content_object = models.ForeignKey(Book)
Run Code Online (Sandbox Code Playgroud)
有一个错误.我们对缓存的权限Page,并Book在同一个地方-我们需要区分并单独缓存这些.另外,让我们将重复的代码封装到自己的方法中.最后,让我们给出get()一个默认值,以确保我们不会在用户拥有时重新查询用户的权限None.
def cache_permissions(predicate, user, instance):
"""
Cache all permissions this user has on this instance, for potential reuse by other predicates in this rule check.
"""
key = 'user_%s_perms_%s_%s' % (user.pk, type(instance).__name__, instance.pk)
if predicate.context.get(key, -1) == -1:
predicate.context[key] = guardian.shortcuts.get_perms(user, instance)
return predicate.context[key]
Run Code Online (Sandbox Code Playgroud)
这样,对象权限将单独缓存.(包括用户ID key是不必要的,因为任何规则只会检查一个用户,但是更具有前瞻性.)
然后我们可以定义我们的谓词如下:
@rules.predicate(bind=True)
def is_page_book_viewer(self, user, instance: Page):
return 'view_book' in cache_permissions(self, user, instance.book)
Run Code Online (Sandbox Code Playgroud)
一个限制rules是权限检查必须基于用户单独完成,但我们经常必须获取用户具有给定权限的所有对象.例如,要获取用户具有编辑权限的所有页面的列表,我需要重复调用[p for p in Pages.objects.all() if usr.has_perm('can_edit_page', p)],而不是usr.has_perm('can_edit_page')在一个查询中返回所有允许的对象.
我们不能完全解决这个限制,但是如果我们不需要检查列表中的每个对象,我们可以减少使用next和延迟生成器基于协同程序的查询集的查询数量.在上面的例子中,我们可以使用(...)而不是[...]我们可能不会到列表的末尾,next(...)如果我们只需要检查列表中的任何对象是否具有权限.break或者return是正常循环代码中的等价物,如下所示.
我有一种模型具有自连接层次结构的情况,我只需要知道模型的任何后代是否具有权限.代码必须以连续节点的后代递归查询表.但是一旦我们找到具有权限的对象,我们就不需要进一步查询.我这样做了如下.(注意我对是否有人拥有对象的权限感兴趣,并且我已经指定了非通用键.如果您正在检查特定用户的权限,则可以user.has_perm('perm_name', obj)使用您的规则.)
class Foo(models.Model):
parent = models.ForeignKey('Foo', blank=True, null=True)
def descendants(self):
"""
When callers don't need the complete list (eg, checking if any dependent is
viewable by any user), we run fewer queries by only going into the dependent
hierarchy as much as necessary.
"""
immediate_descendants = Foo.objects.filter(parent=self)
for x in immediate_descendants:
yield x
for x in immediate_descendants:
for y in x.descendants():
yield y
def obj_or_descendant_has_perm(self, perm_code):
perm_id = Permission.objects.get(codename=perm_code).id
if FooUserObjectPermission.objects.filter(permission_id=perm_id,
content_object=self).exists()
return True
if FooGroupObjectPermission.objects.filter(permission_id=perm_id,
content_object=self).exists()
return True
for o in self.descendants():
if FooUserObjectPermission.objects.filter(permission_id=perm_id,
content_object=self).exists()
return True
if FooGroupObjectPermission.objects.filter(permission_id=perm_id,
content_object=self).exists()
return True
return False
Run Code Online (Sandbox Code Playgroud)
如果您有一个这样简单的自连接,请treebeard查看更有效的层次结构建模方法(物化路径,嵌套集或邻接列表).在我的情况下,自联接是通过其他表,所以这是不可能的.
我更进了一步,通过从后代返回查询集来允许组选:
class Foo(models.Model):
parent = models.ForeignKey('Foo', blank=True, null=True)
def descendants(self):
"""
When callers don't need the complete list (eg, checking if any dependent is
viewable by any user), we run fewer queries by only going into the dependent
hierarchy as much as necessary. Returns a generator of querysets of Foo objects.
"""
immediate_descendants = Foo.objects.filter(parent=self)
yield immediate_descendants
for x in immediate_descendants:
for y in x.descendants():
yield y
def obj_or_descendant_has_perm(self, perm_code):
perm_id = Permission.objects.get(codename=perm_code).id
if FooUserObjectPermission.objects.filter(permission_id=perm_id,
content_object=self).exists()
return True
if FooGroupObjectPermission.objects.filter(permission_id=perm_id,
content_object=self).exists()
return True
for gen in self.descendants():
if FooUserObjectPermission.objects.filter(permission_id=perm_id,
content_object__in=gen).exists()
return True
if FooGroupObjectPermission.objects.filter(permission_id=perm_id,
content_object__in=gen).exists()
return True
return False
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1121 次 |
| 最近记录: |