Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pyramid: multiple resource factories -- how to

Tags:

python

pyramid

I have a simple root resource factory:

class Root:
    __acl__ = [
        (Allow, Authenticated, 'edit')
    ]

Now for some "special" routes, I need to create another resource factory

config.add_route('special', '/special/test', factory=SpecialFactory)

class SpecialFactory:
    __acl__ = [
        (Allow, Authenticated, 'special_edit')
    ]

Now, I want to make Root the parent of SpecialFactory -- how should I do it?

Is this the right way...

class SpecialFactory:
    def __init__(self, request):
        self.request = request
        self.__parent__ = Root(request)
        self.__name__ = 'special'

    __acl__ = [
        (Allow, Authenticated, 'special_edit')
    ]

I also don't understand the purpose of __name__ fully and what it should be set to.

Also, when will Pyramid traverse the __parent__ chain and when not? For a view config like this:

@view_config(route_name='special', permission='special_edit')
def something(req):
    pass

will Pyramid 'collect' both permissions (special_edit and edit) or just one (special_edit)?

Please explain the "flow" of calculating the permissions extensively.

like image 612
treecoder Avatar asked Apr 23 '13 12:04

treecoder


1 Answers

__name__ only comes into account when generating urls via traversal, so don't worry about it.

First off, the factory argument is a factory. Meaning, it's "some object" that accepts a request object, and expects to receive back an object that is actually the root of the tree.

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

def resource_factory(request):
    return Root(request)

add_route(..., factory=resource_factory)

Notice how, here, the factory is obvious. A shortcut that is commonly used is to use the fact that constructing an instance of an object actually returns itself. So Root(request) looks exactly the same from the outside and returns the same object as resource_factory(request).

Great, so now we have a "root" object from which we can begin traversal. Of course this doesn't have to be the actual root of the tree, it's just where traversal should begin from.

You haven't added a traverse argument to your add_route, so traversal won't go anywhere, it will just return the root object as the context. Finding the context object is the whole goal of the traversal exercise.

So, now we have a context. Yay.

Pyramid's authorization works by combining the "effective principals" of the user, with the "context" and a "permission". These 3 things are what your authorization policy will use to determine if an operation is allowed or denied.

The "effective principals" come from the authentication policy, and are representative of the user behind the request.

The "context" and "permission" are whatever you want. In most scenarios they are request.context and the view's permission, but pyramid.security.has_permission() can accept any context object and any permission and return you an allow or deny result.

So, we've got the 3 required things for authorization. Now, how to authorize? Well that is up to the authorization policy. By default, the ACLAuthorizationPolicy. So how does it work?

The ACLAuthorizationPolicy starts at the context and goes backward through the "lineage" that object. The "lineage" is defined as the list created by following each object's __parent__ back to the end, where there is no more __parent__ to follow. So in your example, the context would be an instance of SpecialFactory, and the "lineage" of the context is the list [ SpecialFactory(request), Root(request) ].

The way ACL matching works (in the ACLAuthorizationPolicy) is that it goes through each object in the lineage from the context back to the root, searching each object's __acl__ in order. The first match it finds is the winner. An entry in the ACL is defined by "(Allow or Deny, principal, permission)" and match is an entry in the ACL that contains the same permission we are looking for, as the the principal matches one of the principals in our list of effective principals for the current user. Once a match is found, searching stops and the result is returned.

If this algorithm doesn't work for you, replace the authorization policy. It's highly pluggable and the default implementation is easy to understand (totaling only a few lines of code). You could even make your own policy that doesn't care about the context at all, at which point you can ignore all this traversal nonsense. It's up to you.

like image 65
Michael Merickel Avatar answered Oct 05 '22 11:10

Michael Merickel