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.
__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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With