Instance level authorization in Pyramid
I'm in the early stages of designing a my first Pyramid app and I was hoping for some verification on my approach to instance level authorization. Most of the stock documentation discusses global ACLs which apply to an entire class, not individual instances of that class. Consider a simple CMS which lets users create pages then only edit the pages they create. My thought goes something like this: Configure each route with a factory, to generate a context object: config.add_route('edit_page', 'edit_page/{page}', factory='myproject.resources.PageFactory') In this case the PageFactory would return the {page} instance of the Page model. Then configure a view for the route with some permission requirement. config.add_view('myproject.views.edit_page', route_name='edit_page', permission='edit') ??? I'm not clear what the difference is between passing a factory to the add_route vs. passing a context to add_view. I believe the factory can create a specific instance of a Page and pass it to the view. I don't know what the context would be if I passed context=myproject.resources.Page to add_view. Then, within the constructor for a Page, created by the PageFactory, an acl decorator would be set to that instance based on the owner. Something like... def __init__(self,...): self.__acl__ = [ (Allow, Everyone, 'view'), (Allow, 'user:owners_name_from_db', 'edit'), ] Then, only a person who is authenticated as owners_name_from_db would be allowed to view the edit_page view. The value of owners_name_from_db would be loaded as part of the object when it was instantiated by the PageFactory. I'm really sketchy on how this all fits together. The documentation is great, if you're making a site where a group of editors can edit everything, but not so much when you want individuals to be able to edit specific instances. I appreciate any advice, Brian -- You received this message because you are subscribed to the Google Groups pylons-devel group. To post to this group, send email to pylons-devel@googlegroups.com. To unsubscribe from this group, send email to pylons-devel+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/pylons-devel?hl=en.
Re: Instance level authorization in Pyramid
On Thu, 2011-09-01 at 06:30 -0700, Brian wrote: I'm in the early stages of designing a my first Pyramid app and I was hoping for some verification on my approach to instance level authorization. Most of the stock documentation discusses global ACLs which apply to an entire class, not individual instances of that class. Consider a simple CMS which lets users create pages then only edit the pages they create. My thought goes something like this: Configure each route with a factory, to generate a context object: config.add_route('edit_page', 'edit_page/{page}', factory='myproject.resources.PageFactory') In this case the PageFactory would return the {page} instance of the Page model. Then configure a view for the route with some permission requirement. config.add_view('myproject.views.edit_page', route_name='edit_page', permission='edit') ??? I'm not clear what the difference is between passing a factory to the add_route vs. passing a context to add_view. I believe the factory can create a specific instance of a Page and pass it to the view. I don't know what the context would be if I passed context=myproject.resources.Page to add_view. When you use url dispatch, you typically don't really care about associating the view with a particular context, because the view is already associated with the route (and by extension, its factory, and by further extension the context produced by the factory). So saying: config.add_route('edit_page', 'edit_page/{page}', factory='myproject.resources.page_factory') config.add_view('myproject.views.edit_page', route_name='edit_page', permission='edit', context='project.resources.Page') Doesn't really buy you anything over: config.add_route('edit_page', 'edit_page/{page}', factory='myproject.resources.factory') config.add_view('myproject.views.edit_page', route_name='edit_page', permission='edit') .. because the route_name passed to add_view was enough to associate the view with the route all alone. Note that in the above examples I'm assuming that page_factory is something like this: def page_factory(request): ... figure out which Page to show from the request variables ... page = Page(thosevariables) page.__acl__ = someacl return page The magic here would need to happen in ... figure out which Page to show from the request variables ... and, if you need per-instance ACLs, what you do in page.__acl__ = someacl. It really doesn't matter whether page_factory sets the __acl__ on the Page instance, or whether the Page class' constructor accepts enough arguments to know which __acl__ to construct and sets it itself. But the result must be such that page.__acl__ is the ACL you want. The result of the factory returns the context, and Pyramid's security machinery compares the ACL of the context against the permission assigned to the view (in this case, edit). Then, within the constructor for a Page, created by the PageFactory, an acl decorator would be set to that instance based on the owner. Something like... def __init__(self,...): self.__acl__ = [ (Allow, Everyone, 'view'), (Allow, 'user:owners_name_from_db', 'edit'), ] Then, only a person who is authenticated as owners_name_from_db would be allowed to view the edit_page view. The value of owners_name_from_db would be loaded as part of the object when it was instantiated by the PageFactory. That works. I'm really sketchy on how this all fits together. The documentation is great, if you're making a site where a group of editors can edit everything, but not so much when you want individuals to be able to edit specific instances. I appreciate any advice, Seems like you have it mostly right, at least as right as you can get without using something like traversal. - C -- You received this message because you are subscribed to the Google Groups pylons-devel group. To post to this group, send email to pylons-devel@googlegroups.com. To unsubscribe from this group, send email to pylons-devel+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/pylons-devel?hl=en.
Re: Instance level authorization in Pyramid
Chris, Thanks for the reply. One more question... Is it acceptable for __acl__ to be a callable associated with an instance? def __acl__(self): return [ (Allow, 'user:%s' % self.owner, 'edit'), ] Thanks, Brian -- You received this message because you are subscribed to the Google Groups pylons-devel group. To post to this group, send email to pylons-devel@googlegroups.com. To unsubscribe from this group, send email to pylons-devel+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/pylons-devel?hl=en.
Re: Instance level authorization in Pyramid
On Mon, 2011-09-05 at 12:44 -0700, Brian wrote: Chris, Thanks for the reply. One more question... Is it acceptable for __acl__ to be a callable associated with an instance? def __acl__(self): return [ (Allow, 'user:%s' % self.owner, 'edit'), ] No, it must be an attribute, but you can use a property to turn it into a callable, e.g.: @property def __acl__(self): return [ (Allow, 'user:%s' % self.owner, 'edit'), ] - C -- You received this message because you are subscribed to the Google Groups pylons-devel group. To post to this group, send email to pylons-devel@googlegroups.com. To unsubscribe from this group, send email to pylons-devel+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/pylons-devel?hl=en.
Re: Instance level authorization in Pyramid
Brian, I just want to clarify some points from your original email. Specifying the ``factory`` on the route is telling the traversal system how to get the root of your resource tree for that specific route. Thus in your example you might do: def PageFactory(request): pagename = request.matchdict.get('page') if pagename: # page = query db for a Page object named 'pagename' return page Specifying the ``context`` parameter on the view is saying for this view to match (be invoked), the context object must be an instance of this class (or interface). There is a subtle caveat here when using url dispatch. Traversal stops when the path being traversed is exhausted *OR* when a KeyError is raised. If you are attempting in your ``PageFactory`` to load a ``Page`` and that fails, raising a KeyError, then the ``PageFactory`` instance will now be the context. ACLs will then be checked against the ``PageFactory`` object instead of your intended ``Page``. In this case you would probably prefer a 404 instead of having your view be invoked without a valid ``Page``. Thus, if your view specifies ``route_name='foo'`` *and* ``context=Page``, then you can be sure that view will only be called if the context is a ``Page``. On top of that if you specify a permission, then the view is only invoked if that permission was in the ACL as well. -- Michael -- You received this message because you are subscribed to the Google Groups pylons-devel group. To post to this group, send email to pylons-devel@googlegroups.com. To unsubscribe from this group, send email to pylons-devel+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/pylons-devel?hl=en.
Re: Instance level authorization in Pyramid
It's not incorrect, I just merged two thoughts which probably made it unclear. If he doesn't specify a ``traverse`` parameter then traversal will not happen, which will use a similar RootFactory to what I showed except that he might want to raise a HTTPNotFound if the ``page`` is None. The rest of the stuff was about traversal, which I probably should've kept my mouth shut about. ;-) -- Michael -- You received this message because you are subscribed to the Google Groups pylons-devel group. To post to this group, send email to pylons-devel@googlegroups.com. To unsubscribe from this group, send email to pylons-devel+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/pylons-devel?hl=en.