happy weekend, and welcome to my RTFM question about view_config and permissions

154 views
Skip to first unread message

Michael Bayer

unread,
May 28, 2012, 12:26:52 PM5/28/12
to pylons-...@googlegroups.com
I've made a 40% effort to figure this one out but at least I've figured many other things out without bugging the list .... (the irc channel is another story ;) )

Here's a route in application.py:

config.add_route('some_admin_thing', '/admin_something', factory=AdminUserACL)

Here's the general idea of AdminUserACL:

class AdminUserACL(object):
@property
def __acl__(self):
# this is programmatic based on who is logged in,
# but the end result might be:
return [
(Allow, Authenticated, "access"),
(Allow, Authenticated, "useradmin")
]

def __init__(self, request):
# pull out the admin user from request, do things

So a view that wants to require the "useradmin" permission looks like:

@view_config(route_name='some_admin_thing', renderer='json',
request_method='GET', permission='useradmin')
def some_admin_thing(request):
# ...

But the thing is, all views of this route should require "useradmin" permission. I don't like that I have to split the declaration of authorization in two places (factory on add_route(), permission on view_config()). If I try to put "permission" or "view_permission" on the add_route(), it wants to know the view at that point, implying I wouldn't be able to use view_config() in the first place. Plus it appears "view_permission" on add_route() is deprecated.

Since what I want to do seems natural here, yet it's all explicitly disallowed/discouraged, it suggests my understanding of things is incorrect ? The goal here is "declare all authorization in one place". To me, "factory" and "permission" are both dealing with authorization and it isn't clear why add_route() can't have some default notion of "permission", agnostic of individual views which is applied to those views.




Carlos de la Guardia

unread,
May 29, 2012, 12:25:42 AM5/29/12
to pylons-...@googlegroups.com
On Mon, May 28, 2012 at 11:26 AM, Michael Bayer
<mik...@zzzcomputing.com> wrote:
>
> If I try to put "permission" or "view_permission" on the add_route(), it wants to know the view at that point, implying I wouldn't be able to use view_config() in the first place.   Plus it appears "view_permission" on add_route() is deprecated.
>

The 'permission' or 'view_permission' configuration argument is only
used when a view is specified and I guess the confusion this causes is
part of why it was deprecated.

> Since what I want to do seems natural here, yet it's all explicitly disallowed/discouraged, it suggests my understanding of things is incorrect ?   The goal here is "declare all authorization in one place".   To me, "factory" and "permission" are both dealing with authorization and it isn't clear why add_route() can't have some default notion of "permission", agnostic of individual views which is applied to those views.
>

In Pyramid, routes (URL dispatch) are just one of two different
mechanisms to find a "view". There's also traversal, which many of us
use. Since traversal can use views without ever defining any routes,
it makes sense to have permissions at that level, so that both
approaches use the same kind of view configuration. Also, it's
possible to combine routes and traversal in an application, so that
the first part of a url uses a route to find a matching 'resource'
(think factory) and then traversal is used to get to the appropriate
object and view. In this case, having a permission on the route
doesn't even make sense, because the object that was 'traversed' to in
the end could have a different factory and views with varying
permissions.

Even so, the good news is that there's an easy way to get *more or
less* what you want. You don't even need separate 'useradmin' and
'access' permissions, since the __acl__ is dynamic. Just do the
following:

1. To avoid having to specify a permission in all view declarations,
in your config:

config.set_default_permission('access')

This also allows you to have the permission set right below the
route declarations, so you will have all authorization declared in
one place, like you want.

2. Now all the views will require the 'access' permission. For normal,
non-admin views, make sure the default root factory has an __acl__
like:

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

3. For your protected admin views, keep using your AdminUserACL
factory, but you don't need another permission. If the user is an
admin, return:

[(Allow, Authenticated, 'access')]

If the user is not an admin:

[(Deny, Authenticated, 'access')]

4. Finally, the login view must have a special permission declared in
the view configuration: pyramid.security.NO_PERMISSION_REQUIRED:

@view_config(
route_name='login',
permission=NO_PERMISSION_REQUIRED,
renderer='login.mako',
)

This is necessary to allow unauthenticated users to see it,
because if you omit it the login view would also require the 'access'
permission.

That's it. So, no view permission declarations, except one, but you
get one less permission, so it sort of evens out.

I hope this helps,

Carlos de la Guardia
> --
> You received this message because you are subscribed to the Google Groups "pylons-discuss" group.
> To post to this group, send email to pylons-...@googlegroups.com.
> To unsubscribe from this group, send email to pylons-discus...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/pylons-discuss?hl=en.
>

Carlos de la Guardia

unread,
Jun 20, 2012, 8:22:35 PM6/20/12
to pylons-...@googlegroups.com
I think your idea of having the possibility of declaring on the route
config a permission that is required by all views that match that
route is a reasonable thing to ask, specially from the point of view
of pure route users. I made a proof of concept here:

https://github.com/cguardia/pyramid/commit/8b3220e6d3e809cceff0db88ebb6fda1607e1a62

It's possible that something similar to this could be added to Pyramid
if I implement this differently, test it and document it. I could do
that if you and other Pylons users or Pyramid newcomers in general
think this is worthwhile.

Now that you have a been through this experience, do you still think
this would be a good thing for Pyramid to have?

Thanks,

Carlos de la Guardia

On Mon, May 28, 2012 at 11:26 AM, Michael Bayer
<mik...@zzzcomputing.com> wrote:

Chris McDonough

unread,
Jun 20, 2012, 9:14:10 PM6/20/12
to pylons-...@googlegroups.com
I'll note that in this case, you could use a class based view as both
the factory and as the view:

from pyramid.view import view_defaults
from pyramid.view import view_config

from pyramid.security import Allow, Authenticated

@view_defaults(route_name='some_admin_thing', renderer='json',
permission='useradmin')
class UserAdmin(object):
def __init__(self, request):
self.request = request

@property
def __acl__(self):
# this is programmatic based on who is logged in,
# but the end result might be:
return [
(Allow, Authenticated, "access"),
(Allow, Authenticated, "useradmin")
]

@view_config(request_method='GET')
def GET(self):
# ...

@view_config(request_method='POST')
def POST(self):
# ...

and in the config...

config.add_route('some_admin_thing', '/admin_something',
factory=UserAdmin)

All of the views hanging off the UserAdmin object will be protected by
useradmin permission, and an instance of UserAdmin will also be used as
the context, which will put all of the security stuff in the same place,
albeit associated transitively via the 'some_admin_thing' route name.

- C


Chris McDonough

unread,
Jun 20, 2012, 9:44:51 PM6/20/12
to pylons-...@googlegroups.com
I'll also note that the comment inside def __acl__ ("this is
programmatic based on who is logged in") is a little creepy and might
indicate that the ACL authorization policy is not being used as intended.

If you're using the ACL authorization policy, the ACL should *very
rarely* (really never) be computed based on who is logged in. An ACL is
meant to be declarative. If it can be completely static, great. Often
it needs to be computed, though. When it is computed, it should be
computed using a combination of data in your database and data in the
URL or query string of the request. In effect, URL/qs data is meant to
compose the arguments to a query which can be used to look up some data
in the database that represents a resource. The ACL protects *that
resource*.

The view execution authorization system then compares the ACL against
the principals represented by the request and the view permission and
allows or denies view execution.

ACL -> thing that protects the content; mentions permissions
and principals
principals -> currently logged in username + his groupnames
permission -> protects a view

Pyramid protects views by doing this (in this order):

- It finds the context using a factory.

- It figures out which view should be called.

- It figures out if there's a permission associated with this view.

- It computes the effective principals based on request data.

- It compares the effective principals against the ACL provided by
the context. If any of the effective principals has been granted
the permission of the view, it allows the view to execute. If
none of the effective principals has been granted the permission
of the view, or the ACL has explicitly denied any of the
effective principals the permission of the view, Pyramid prevents
the view from being executed.

In other words, it's Pyramid's job to figure out whether the current
user can execute the view based on the ACL. If the ACL is computed
based on who is logged in, things get weird pretty fast... it's just a
divide by zero error sort of, and it'd be a better idea to implement a
custom authorization policy if you want to think of things that way.

- C

Michael Bayer

unread,
Jun 21, 2012, 3:20:44 AM6/21/12
to pylons-...@googlegroups.com

On Jun 20, 2012, at 9:44 PM, Chris McDonough wrote:

>
> In other words, it's Pyramid's job to figure out whether the current user can execute the view based on the ACL. If the ACL is computed based on who is logged in, things get weird pretty fast... it's just a divide by zero error sort of, and it'd be a better idea to implement a custom authorization policy if you want to think of things that way.

here's whats not clear about Pyramid, and had to be illustrated for me by an expert. In order to compute the "principals" themselves, based on the current user as well as database data associated with *the user*, you either need to write your own AuthenticationPolicy, and do it in effective_principals, or use that "callback" argument accepted by the provided AuthenticationPolicy.

The Pyramid docs make it very easy to see the example of the context with __acl__ and the dynamic logic inside of it, but then it's not at all clear about adding dynamic rules to authentication policies. It makes the impression to me, as I also saw in some of the examples I was shown on IRC, that the __acl__ hook is "the place where we do any kind of database manipulation of security info for some request". When really, even though that "works" perfectly fine, there are really *two* hooks that the user has to be aware of:

1. the __acl__ is where it is appropriate to do database lookups based on the *resource being requested*

2. the authenticationpolicy custom class or callback is where it is appropriate to do database lookups based on the *user logged in*

This dichotomy should be present any time either of the two sides of the coin are discussed. It leads to confusion (at least for me) that dynamic __acl__'s are stressed as a means to apply dynamic rules to resources, but there's not really much mention of what the correct means to apply dynamic rules to principals. Keeping in mind that when mere mortals read documentation, you can't depend on correct terminology alone implanting the right idea, as a newbie doesn't have strong neural pathways and automatic recognition of new terms. You need to break out the hand puppets, even for me.

Additionally, another concept that I've had trouble with, since I am not using traversal/zodb, is:

3. pyramid is organized around the idea that a web request is looking at *one thing at a time*. That's why there's a "context". You aren't allowed to make "context" into whatever you want (such as when I just made it into a "security token"). It is meant to correspond to the *thing you are looking at*. Pyramid is more opinionated than I initially expected here.

4. If the thing you are looking at is not actually a *single thing*, and is actually *many things*, such as any kind of search result, listing of information, list of articles, word cloud, or especially a composed page with lots of heterogeneous elements (see http://www.mlb.com for an example of this), Pyramid gives you two choices:

a. invent *one thing* that represents that collection. When using traversal/zodb, this is perfectly natural. When using relational databases and routes, it's often completely awkward and artificial, since you're essentially making yourself a fake model object, just so you have a place to stick __acl__ .

b. invent some other system.

I know the context is discussed quite a bit in "url dispatch" and "security". But I think the docs have a hard job here, as the "context" is already a little bit of a bolted-on concept when you're dealing with routes. When we use routes, we often tend to think of the *view* as the thing that is protected, not the *model*. We want to put security stuff on our views in day-to-day, simplistic cases. Such as, "only users with a certain permission can access the administration pages". How I need to come up with "model-like" objects that have __acl__ but really have nothing to do with my actual "model" I've found to be quite awkward to get my head around.

I hit a lot of conceptual dissonance on the IRC channel as Pyramid veterans seem to really see model-level security as something quite natural (and then they say, "because I use traversal and zodb where there's always a single model object anyway"). Model-level security isn't something I get involved in unless I'm building some very fine-grained and elaborate system - usually those end up being hierarchical management types of things which brings us back to traversal/zodb as a strong force here.



Eric Ongerth

unread,
Jun 23, 2012, 2:29:20 AM6/23/12
to pylons-...@googlegroups.com
+1 to Mike's 6/21 post.  I really couldn't say any of that better; 90% of it is things that have occurred to me in my own work; I can't really think of anything to add to the discussion.  I'm not sure there's any great and terrible need to change anything, but it would be very good to keep the perspective he's presenting in consideration.  Pyramid is doing a pretty great job of being many things to many people, and I happen to think that the points Mike illustrates are some of the more noteworthy places were it does seem to strain just a little to do it.
Reply all
Reply to author
Forward
0 new messages