How do I restrict permissions based on the single page ID in the URL?

authentication, authorization, pylons, pyramid, security

Solution

The basic principle here is that Pyramid's security machinery checks the ACL on the current context. In this case your page would be the logical context to use. The first step is to setup a context factory for a page. Assuming you are using SQLAlchemy and URL dispatch this is simple to do. Register your route like this:

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

There is a little trick in the path for the route that makes pyramid check the page id must be a number so you do not have to check that yourself. Note the reference to a *page_factory* method. Lets define that now:

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

This takes the page id from the route and uses that to lookup the page in your database. Notice that we do not check if the id can be converted to an integer here: we can get away with that since the route already checks that directly.

The next step is to setup the ACL on the page. The simplest way is to add a acl property to you Page class:

from pyramid import security

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

This ACL tells pyramid that only the user with the id stored in page.userid is allowed to view that page. What is important to realise here is that the ACL is different for every page: it is generated for every page separately based on the information in your database; in this case using self.userid.

You can now use the view permission on your view:

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

This example has a very minimal ACL for a page, but you can extend that to fit your needs.

Also note the context=Page parameter for view_config: this tells pyramid that this view should only be used of the context is a Page. If the context factory (page_factory in this example) did not find a matching page it will return None instead of a Page instance, so this view will not be used by pyramid. As a result pyramid will automatically produce a not-found error.

Problem

I'm trying to implement Pyramid's Security features in my website but I'm having some trouble figuring out how to use it. I've been reading over this tutorial and this example, as well as the Pyramid docs, and I can't figure out how to implement an authorization policy for single page IDs. For example, I have the following URL scheme: ``` /pages /pages/12 ``` `/pages` obviously lists the available pages and `/pages/:id` is where you can read/comment on the page. The documentation/examples I've read have shown that you can implement group level ACS's by providing a `groupfinder` callback with a list of groups. Such as `editor`, `admin`, etc. How can I not use a group for permissions and instead rights based on the page id? In my URL scheme above, when the user browses to `/pages` they must be logged in. When they browse to `/pages/:id`, they must have been given access to view that particular id. Or, they must be the owner of that page. Same as comments. On the `/page/:id` page, they may have been given access to view the page but not comment on it.

Original source