没有遍历的Pyramid ACL
我对ACL(访问控制列表)是怎么工作的了解不多。我知道它很酷,能帮我省很多时间和麻烦。但现在我有点迷茫。所有关于金字塔框架的例子都使用了遍历(traversal)。而我只用URL调度(URL Dispatch)。我不太明白怎么构建一个资源树结构。
这里有一段代码示例:
class QuestionFactory(object):
def __init__(self, request):
self.__acl__ = default[:]
self.uid = authenticated_userid(request)
self.qid = request.matchdict.get('id')
if self.qid:
self.question = request.db.questions.find_one({'_id': ObjectId(self.qid)})
if str(self.question.get('owner')) == self.uid:
self.__acl__.append((Allow, userid, 'view'))
问题是,这段代码能正常工作。但我必须为每种资源定义一个新的工厂(factory)。我不确定我该如何知道通过URL调度和工厂我想访问的是哪种资源。我想我应该看到这样的东西:
/accounts/{account} //Owners only but viewable by anyone
/messages/{message} //Owners only
/configs/{config} //Admin only
/pages/{page} //Admins only but viewable by anyone
也就是说,我会有这样的结构:
Root -\
+-- account
+-- message
+-- config
+-- page
这些工厂每个都有自己特殊的ACL。还有一点是,/accounts是主页面。它没有ID或其他东西。此外,/accounts/new也是一个特殊情况。它不是一个ID,而是创建新项目的视图。
我使用的是REST风格,包含GET/PUT/DELETE/POST请求。我不太确定我该如何自动将URL与资源和正确的ACL匹配。如果我在根目录定义一个像上面那样的特殊工厂,就没有问题。
编辑
我已经让它工作了,除了某些事情。我终于觉得我明白了遍历的目的。例如,我们有这个URL:/comments/9494f0eda/new,/comments/{comment}/new
我们可能在资源树中有两个节点,甚至三个节点。
RootFactory会首先被检查,然后根据我们的遍历,它会获取RootFactory的comments属性,然后是Comment工厂的“comment”,再然后是CommentFactory或对象本身的“new”。
我没有像Michael的例子那样使用工厂作为字典。
它看起来大致是这样的:
class RessourceFactory(object):
def __init__(self, parent, name):
self.__acl__ = []
self.__name__ = name
self.__parent__ = parent
self.uid = parent.uid
self.locale = parent.locale
self.db = parent.db
self.req = parent.req
这是我的基本资源对象。在每一步,它会从父级复制信息到新的子级。我当然可以将我的属性向上冒泡... context.parent._parent_.uid,但这样做并不是很好。
我不使用字典属性的原因是,我需要让它与
/comments
一起工作。出于某种原因,它确实创建了我的CommentFactory,但没有返回它,因为不需要一个键。
所以我的根工厂大致是这样的:
class RootFactory(object):
def __init__(self, request):
self.__acl__ = default[:]
self.req = request
self.db = request.db
self.uid = authenticated_userid(request)
self.locale = request.params.get('locale', 'en')
def __getitem__(self, key):
if key == 'questions':
return QuestionFactory(self, 'questions')
elif key == 'pages':
return PageFactory(self, 'pages')
elif key == 'configs':
return ConfigFactory(self, 'configs')
elif key == 'accounts':
return AccountFactory(self, 'accounts')
return self
如果没有找到项目,RootFactory会返回自身;如果找到了,就返回一个新的工厂。因为我的代码是基于Michael的代码,所以工厂构造函数有第二个参数。我不确定是否要保留它,因为QuestionFactory已经知道如何处理“问题”,所以这里不需要命名工厂。它应该已经知道自己的名字。
class QuestionFactory(RessourceFactory):
def __init__(self, parent, name):
RessourceFactory.__init__(self, parent, name)
self.__acl__.append((Allow, 'g:admin', 'view'))
self.__acl__.append((Allow, 'g:admin', 'edit'))
self.__acl__.append((Allow, 'g:admin', 'create'))
self.__acl__.append((Allow, 'g:admin', 'delete'))
self.__acl__.append((Allow, Everyone, 'create'))
def __getitem__(self, key):
if key=='read':
return self
self.qid = key
self.question = self.db.questions.find_one({'_id': ObjectId(self.qid)})
if str(self.question.get('owner')) == self.uid:
log.info('Allowd user %s' % self.uid)
self.__acl__.append((Allow, self.uid, 'view'))
self.__acl__.append((Allow, self.uid, 'edit'))
self.__acl__.append((Allow, self.uid, 'delete'))
return self
所以几乎所有的逻辑都会在这里进行。在init中,我设置了适用于/questions的ACL,在getitem中,它会适用于/questions/{id}/*
因为我返回自身,任何经过这个资源工厂的getitem都会指向自身,除非我为某些特殊情况返回一个新的工厂。这样做的原因是,我的上下文不仅仅是数据库中的一个对象或一个对象。
我的上下文处理多个事情,比如用户ID、地区等... 当ACL设置完成后,我就有了一个新的上下文对象可以使用。这减少了视图中的大部分逻辑。
我可能可以设置事件来查询地区和用户ID,但在这里并不合适。如果我需要任何新东西,我只需编辑我的RootFactory和资源工厂,将它们复制到子工厂。
这样,如果某些东西需要在所有视图中更改,就没有任何冗余。
1 个回答
看起来你对一些对象/行级安全功能感兴趣,这样只有账户的拥有者才能查看他们的数据。我建议你看看我之前在StackOverflow上关于这个话题的回答,还有我正在做的一个关于URL调度的认证教程,这个教程是围绕那个回答展开的。特别是你可能想看看链接的GitHub项目中的2.object_security
演示,以及我网站上渲染的HTML中解释资源树的文档。
https://github.com/mmerickel/pyramid_auth_demo
http://michael.merickel.org/projects/pyramid_auth_demo/
如果你对理解这些资源有任何问题,我很乐意在这里进一步解释。