如何根据URL中的单个页面ID限制权限?

时间:2012-04-22 09:07:34

标签: security authentication authorization pylons pyramid

我正在尝试在我的网站上实现金字塔的安全功能,但我在弄清楚如何使用它时遇到了一些麻烦。

我一直在阅读this tutorialthis example以及Pyramid文档,我无法弄清楚如何为单页ID实施授权策略。

例如,我有以下网址方案:

/pages
/pages/12

/pages显然列出了可用的页面,/pages/:id是您可以在页面上阅读/评论的位置。

我读过的文档/示例表明,您可以通过提供带有组列表的groupfinder回调来实现组级ACS。例如editoradmin

如何根据页面ID使用权限而不是权限?

在我上面的URL方案中,当用户浏览到/pages时,他们必须登录。当他们浏览到/pages/:id时,他们必须有权查看该特定ID。或者,他们必须是该页面的所有者。

与评论相同。在/page/:id页面上,他们可能有权查看该页面但不对其进行评论。

2 个答案:

答案 0 :(得分:6)

这里的基本原则是Pyramid的安全机制在当前上下文中检查ACL。在这种情况下,您的页面将是要使用的逻辑上下文。第一步是为页面设置上下文工厂。假设您正在使用SQLAlchemy和URL分派,这很容易做到。注册您的路线:

config.add_route('page', '/pages/{id:\d+}', factory=page_factory)

在路径的路径中有一个小技巧,使得金字塔检查页面ID必须是一个数字,因此您不必自己检查。请注意对* page_factory *方法的引用。让我们现在定义:

def page_factory(request):
    return DBSession.query(Page).get(int(request.matchdict['id']))

这将获取路径中的页面ID,并使用它来查找数据库中的页面。请注意,我们不会检查id是否可以在这里转换为整数:我们可以自行解决,因为路由已经直接检查了。

下一步是在页面上设置ACL。最简单的方法是向Page class:

添加 acl 属性
from pyramid import security

class Page(BaseObject):
    @property
    def __acl__(self):
        return [(security.Allow, self.userid, 'view')]

此ACL告诉金字塔,只允许具有存储在page.userid中的id的用户查看该页面。这里要认识到的重要一点是每个页面的ACL都是不同的:它是根据数据库中的信息分别为每个页面生成的;在这种情况下使用self.userid。

您现在可以在视图中使用视图权限:

@view_config(route_name='page', context=Page, permission='view')
def page_view(context, request):
    return 'I can see!'

此示例的页面ACL非常小,但您可以根据需要进行扩展。

还要注意view_config的context = Page参数:这告诉金字塔这个视图应该只用于上下文是一个Page。如果上下文工厂(此示例中为page_factory)未找到匹配的页面,则它将返回None而不是Page实例,因此金字塔不会使用此视图。因此,金字塔将自动产生未发现的错误。

答案 1 :(得分:3)

出于本讨论的目的,我将假设您正在使用SQLAlchemy与您的数据库进行交互。

如果config.add_route('pages', '/pages/{id}')中有__init__.py,则可以添加自定义工厂来替换/补充默认ACL。例如:

您当前的ACL可能如下所示:

class RootFactory(object):
    __acl__ = [
        (Allow, Everyone, 'view'),
        (Allow, Authenticated, 'auth'),
    ]

    def __init__(self, request):
        self.request = request

这将允许经过身份验证的用户访问具有“auth”权限的任何视图,以及访问您网站以访问具有“查看”权限的任何视图的任何人。

使用自定义工厂,您可以绕过RootFactory,也可以对其进行补充。

要绕过,请将原始config.add_route更改为 - > config.add_route('pages', '/pages/{id}', factory=PageFactory)并创建一个像这样的PageFactory类:

class PageFactory(object):
    __acl__ = [
        (Allow, Everyone, 'view'),
        (Allow, Authenticated, 'auth'),
    ]

    def __init__(self, request):
        self.request = request

    from pyramid.security import authenticated_userid
    user_id = authenticated_userid(self.request)

    thispage = DBSession.query(Page).filter(Page.id==self.request.matchdict['id']).first()

    if thispage.user_id == user_id:
        ## Pyramid allows Everyone, Authenticated, and authenticated_userid
        ## (each of these is known as a Principal) to be in the second
        ## position of the ACL tuple
        acl.append((Allow, user_id, 'edit'))

这假设您的观点有permission='edit'作为其参数之一。

现在,如果您想将RootFactory和补充自定义工厂一起使用,那么您不必重复自己,只需将RootFactory留作我在这篇文章的开头展示过,并继承自RootFactory类,如下:

class PageFactory(RootFactory):
    @property
    def __acl__(self):
        acl = super(PageFactory, self).__acl__[:] ##[:] creates a copy

        from pyramid.security import authenticated_userid
        user_id = authenticated_userid(self.request)

        thispage = DBSession.query(Page).filter(Page.id==self.request.matchdict['id']).first()

        if thispage.user_id == user_id:
            acl.append((Allow, user_id, 'edit'))

        return acl

顺便说一句, groupfinder 非常非常有用,因为这样您就可以简单地将用户置于群组中,例如“admin”,以及管理员组中的所有用户可以使用您可能需要的permission='whatever'permission='whateverelse'访问视图,并且不需要工厂,只能访问为当前用户返回组列表的组查找器。唉,我离题了,因为那不是你想要做的。希望这能回答你的问题。