Pyramid authentication - how to check effective principals in view code

623 views
Skip to first unread message

Przemyslaw Wegrzyn

unread,
Jun 14, 2012, 7:47:38 AM6/14/12
to pylons-...@googlegroups.com
Hi!

What I need is a possibility to get the list of logged-in user's principals in my view (so I can e.g. disable 'Edit' link for users not allowed to edit - pretty common need, I guess).

I could use pyramid.security.effective_principals(request) call, but as far as I understand this would trigger my authentication policy callback again! I really want to avoid calling my callback more than once per request.

My idea is to attach the effective principals list to the request instance (e.g. as request.effective_principals) before returning from the callback, so I can later reference it in my view. Are there any issues I should be careful about here? Or are there better ways to do it?

BR,
Przemyslaw

Mark Huang

unread,
Jun 14, 2012, 7:49:33 AM6/14/12
to pylons-...@googlegroups.com
Hmm....you bring up a good point.  I'd be interested in the responses stated here.  

Daniel Holth

unread,
Jun 14, 2012, 9:06:27 AM6/14/12
to pylons-...@googlegroups.com
Pyramid is better at answering the question "what permission does the user have?", which is likely what's controlling whether they can actually get to the 'edit' page. Use pyramid.security.has_permission('edit', edit_page_context, request) instead.

After evaluating the work effective_principals actually does per-call, if you decide you must cache effective_principals, consider subclassing your authentication policy. The new policy could stash them on the request but the Pyramid APIs would all work in the same way.

If you have one, it can also be handy to just define a request.user property with your application-level User() object.

Przemyslaw Wegrzyn

unread,
Jun 14, 2012, 9:55:34 AM6/14/12
to pylons-...@googlegroups.com
On 14/06/12 15:06, Daniel Holth wrote:
Pyramid is better at answering the question "what permission does the user have?", which is likely what's controlling whether they can actually get to the 'edit' page. Use pyramid.security.has_permission('edit', edit_page_context, request) instead.
Right, in fact if we're talking about per-instance (not per-type) ACLs (which I've forgotten about writing my previous post) this makes perfect sense. Still it would result in extra calls to effective_principals(), which was my primary concern. If I display a list of objects here, calling it for each one seems overkill without caching (if it hits the database, that is).


After evaluating the work effective_principals actually does per-call, if you decide you must cache effective_principals, consider subclassing your authentication policy. The new policy could stash them on the request but the Pyramid APIs would all work in the same way.
I like the idea, as this solves both original problem as well as the one above.

Any reason why Pyramid itself doesn't cache effective principals for the whole request lifespan?

Well, I know that authors are not happy with overall design, as explained here http://plope.com/pyramid_auth_design_api_postmortem (and I have to say I agree with this post 100%), but principals idea is there anyway, so why not make it more efficient?


If you have one, it can also be handy to just define a request.user property with your application-level User() object.
Indeed.

Thanks for the hint!

BR,
Przemyslaw

Chris McDonough

unread,
Jun 14, 2012, 10:17:51 AM6/14/12
to pylons-...@googlegroups.com
Becase, eventually, someone will need to clear the cache when the list
of effective principals changes as the result of something happening
during a request, such as the current user obtaining a new group or
losing a group. Since the way principals are computed is
application-specific, the framework itself can't know when this happens,
and so it can't clear any cache automatically. And rather than
violating rule of least surprise wrt the freshness of the list of
effective principals and a framework-level cache, we leave it up to the
application to do the caching and clearing of that cache.

But as noted by that post, the groupfinder abstraction is dumb. People
should just create a new authentication policy or subclass an existing
policy. When you do this, there's a natural place to add caching.

>> If you have one, it can also be handy to just define a request.user
>> property with your application-level User() object.
> Indeed.
>
> Thanks for the hint!

One way to do this is documented here:

http://docs.pylonsproject.org/projects/pyramid_cookbook/en/latest/auth/user_object.html

This example does indeed cause the user object itself to be cached for
the duration of the request (although its attributes, such as groups,
won't be cached, of course).

- C

Przemyslaw Wegrzyn

unread,
Jun 14, 2012, 10:23:38 AM6/14/12
to pylons-...@googlegroups.com
On 14/06/12 16:17, Chris McDonough wrote:
>> Well, I know that authors are not happy with overall design, as
>> explained here http://plope.com/pyramid_auth_design_api_postmortem (and
>> I have to say I agree with this post 100%), but principals idea is there
>> anyway, so why not make it more efficient?
>
> Becase, eventually, someone will need to clear the cache when the list
> of effective principals changes as the result of something happening
> during a request, such as the current user obtaining a new group or
> losing a group. Since the way principals are computed is
> application-specific, the framework itself can't know when this
> happens, and so it can't clear any cache automatically. And rather
> than violating rule of least surprise wrt the freshness of the list of
> effective principals and a framework-level cache, we leave it up to
> the application to do the caching and clearing of that cache.
I just mean storing it for the single request, not a full-blown cache
spanning multiple requests. Just to avoid querying it multiple times per
request.

Or do you mean it can change in the middle of a single request? That's a
scary idea :)

>
> But as noted by that post, the groupfinder abstraction is dumb.
> People should just create a new authentication policy or subclass an
> existing policy. When you do this, there's a natural place to add
> caching.
Exactly.

Thanks!

Przemek

Chris McDonough

unread,
Jun 14, 2012, 10:29:18 AM6/14/12
to pylons-...@googlegroups.com
On 06/14/2012 10:23 AM, Przemyslaw Wegrzyn wrote:
> On 14/06/12 16:17, Chris McDonough wrote:
>>> Well, I know that authors are not happy with overall design, as
>>> explained here http://plope.com/pyramid_auth_design_api_postmortem (and
>>> I have to say I agree with this post 100%), but principals idea is there
>>> anyway, so why not make it more efficient?
>>
>> Becase, eventually, someone will need to clear the cache when the list
>> of effective principals changes as the result of something happening
>> during a request, such as the current user obtaining a new group or
>> losing a group. Since the way principals are computed is
>> application-specific, the framework itself can't know when this
>> happens, and so it can't clear any cache automatically. And rather
>> than violating rule of least surprise wrt the freshness of the list of
>> effective principals and a framework-level cache, we leave it up to
>> the application to do the caching and clearing of that cache.
> I just mean storing it for the single request, not a full-blown cache
> spanning multiple requests. Just to avoid querying it multiple times per
> request.
>
> Or do you mean it can change in the middle of a single request? That's a
> scary idea :)

Not really that scary; pretty common.

def aview(request):
request.user.add_group(...)

.. code like the above might run as the result of a user completing some
profile information, or verifying an email address, etc.

- C

Przemyslaw Wegrzyn

unread,
Jun 14, 2012, 10:40:37 AM6/14/12
to pylons-...@googlegroups.com
On 14/06/12 16:29, Chris McDonough wrote:
>> spanning multiple requests. Just to avoid querying it multiple times per
>> request.
>>
>> Or do you mean it can change in the middle of a single request? That's a
>> scary idea :)
> I just mean storing it for the single request, not a full-blown cache
>
> Not really that scary; pretty common.
>
> def aview(request):
> request.user.add_group(...)
>
> .. code like the above might run as the result of a user completing
> some profile information, or verifying an email address, etc.
OK, I see your point now. I'd personally rather prefer to stop request
processing and force reload/redirect after any modifications of the
permissions, but that's a matter of taste/particular case, I suppose.

Thansk,
Przemek

Mike Orr

unread,
Jun 14, 2012, 1:04:17 PM6/14/12
to pylons-...@googlegroups.com
On Thu, Jun 14, 2012 at 7:17 AM, Chris McDonough <chr...@plope.com> wrote:
On 06/14/2012 09:55 AM, Przemyslaw Wegrzyn wrote:

Well, I know that authors are not happy with overall design, as
explained here http://plope.com/pyramid_auth_design_api_postmortem (and
I have to say I agree with this post 100%), but principals idea is there
anyway, so why not make it more efficient?

Becase, eventually, someone will need to clear the cache when the list of effective principals changes as the result of something happening during a request, such as the current user obtaining a new group or losing a group.  Since the way principals are computed is application-specific, the framework itself can't know when this happens, and so it can't clear any cache automatically.  And rather than violating rule of least surprise wrt the freshness of the list of effective principals and a framework-level cache, we leave it up to the application to do the caching and clearing of that cache.

But as noted by that post, the groupfinder abstraction is dumb.  People should just create a new authentication policy or subclass an existing policy.  When you do this, there's a natural place to add caching.


One of Pyramid's selling points is its built-in authorization. Pylons never had that, which required me to write my own in one application or use repoze.who/what. My own system works but is non-scalable: I punted on multiple groups and just allowed one group per user, so we have to put users in less-than-ideal groups to avoid the number of groups becoming
    n = (number of permissions)!
Repoze.who/what are WSGI-based so they're more complicated/cumbersome than they have to be (and they didn't exist when I wrote mine). So application developers definitely prefer a framework-supported authorization system.

I've watched the Pyramid auth debates from a distance since my application is still in Pylons. But when I do port it to Pyramid, the issue will come up whether to fully use the auth API or do something different. Parts of the auth API do seem a little cumbersome. Yet if I make something else, I'd want it to be reusable if possible.

So, would it be possible to implement the alternate API as an add-on or tween, bypassing the built-in system or making a compatibility stub to it? Or would the built-in system get in the way too much for that to be feasable?

--
Mike Orr <slugg...@gmail.com>

Daniel Holth

unread,
Jun 14, 2012, 1:49:51 PM6/14/12
to pylons-...@googlegroups.com


One of Pyramid's selling points is its built-in authorization. Pylons never had that, which required me to write my own in one application or use repoze.who/what. My own system works but is non-scalable: I punted on multiple groups and just allowed one group per user, so we have to put users in less-than-ideal groups to avoid the number of groups becoming
    n = (number of permissions)!
Repoze.who/what are WSGI-based so they're more complicated/cumbersome than they have to be (and they didn't exist when I wrote mine). So application developers definitely prefer a framework-supported authorization system.

I've watched the Pyramid auth debates from a distance since my application is still in Pylons. But when I do port it to Pyramid, the issue will come up whether to fully use the auth API or do something different. Parts of the auth API do seem a little cumbersome. Yet if I make something else, I'd want it to be reusable if possible.

So, would it be possible to implement the alternate API as an add-on or tween, bypassing the built-in system or making a compatibility stub to it? Or would the built-in system get in the way too much for that to be feasable?

It depends on exactly how alternate you want it. If what you want to do permits it, the best-supported option would be to implement your own Authentication/Authorization policies. https://github.com/Pylons/pyramid/blob/master/pyramid/interfaces.py#L411 ; see implementations in authentication.py and authorization.py. For me it hasn't been awkward enough to make me want to switch, but I have to look it up every time I add security to a new app.
Reply all
Reply to author
Forward
0 new messages