Instance level authorization in Pyramid

2011-09-05 Thread Brian
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

2011-09-05 Thread Chris McDonough
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

2011-09-05 Thread Brian
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

2011-09-05 Thread Chris McDonough
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

2011-09-05 Thread Michael Merickel
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

2011-09-05 Thread Michael Merickel
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.