Checking if a route is allowed

79 views
Skip to first unread message

Zsolt Ero

unread,
Nov 10, 2016, 3:48:19 PM11/10/16
to pylons-discuss
Hi, I'd like to do the simplest possible thing in Pyramid / URL Dispatch, yet it seems almost impossibly hard to do it.

So it's the super common case of having a menu, and I'd only like to insert menu items which are available for the current user.

I'm looking for a function to fit in this usage:

{% macro nav_item(request, route_name, text) -%}
 
{% if request.view_execution_permitted(route_name) %}
   
<li>
     
<a href="{{ request.route_url(route_name) }}">{{ text }}</a>
   
</li>
 
{% endif %}
{%- endmacro %}


My problems are the following:

1. view_execution_permitted doesn't work like this, unlike other security functions, for example request.has_permission(). Why?

2. Going the hard way and making a custom wrapper around view_execution_permitted, and adding it to request via add_request_method, I'm still stuck in how to use view_execution_permitted, for the following reason:

2.1. It needs a context. What is a context? I never had to use any context in URL Dispatch with SessionAuthenticationPolicy, and it really isn't explained on the website. I tried Googling view_execution_permitted and grepping for code in Github, but I couldn't find anything, except a Github issue ticket saying: "view_execution_permitted does not work with URL dispatch", which didn't help me.
https://github.com/Pylons/pyramid/issues/673

2.2. It needs a name. Is this route_name? I hope so? 

Maybe I don't even need view_execution_permitted. I just want a simple request.is_allowed_route(route_name). How can I do it?

Mikko Ohtamaa

unread,
Nov 10, 2016, 4:45:58 PM11/10/16
to pylons-...@googlegroups.com

I'm looking for a function to fit in this usage:

{% macro nav_item(request, route_name, text) -%}
 
{% if request.view_execution_permitted(route_name) %}
   
<li>
     
<a href="{{ request.route_url(route_name) }}">{{ text }}</a>
   
</li>
 
{% endif %}
{%- endmacro %}


My problems are the following:

1. view_execution_permitted doesn't work like this, unlike other security functions, for example request.has_permission(). Why?

For this I don't have enough insight to answer.
 

2. Going the hard way and making a custom wrapper around view_execution_permitted, and adding it to request via add_request_method, I'm still stuck in how to use view_execution_permitted, for the following reason:

 

2.1. It needs a context. What is a context? I never had to use any context in URL Dispatch with SessionAuthenticationPolicy, and it really isn't explained on the website. I tried Googling view_execution_permitted and grepping for code in Github, but I couldn't find anything, except a Github issue ticket saying: "view_execution_permitted does not work with URL dispatch", which didn't help me.

If you are using only URL dispatching the context is always Root object. Root object defines site wide root __acl__. You can get this as request.root http://docs.pylonsproject.org/projects/pyramid/en/latest/api/request.html
 

2.2. It needs a name. Is this route_name? I hope so? 

One route can point to multiple views due to predicates (method=POST, etc). Thus, this is most likely the same name you give for add_view() and view_config()
 

Maybe I don't even need view_execution_permitted. I just want a simple request.is_allowed_route(route_name). How can I do it?

I simply check for the permission I know the target has using request.has_permission():

https://websauna.org/docs/narrative/user/permissions.html?highlight=permissions#checking-permissions-in-templates
 

--
You received this message because you are subscribed to the Google Groups "pylons-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pylons-discuss+unsubscribe@googlegroups.com.
To post to this group, send email to pylons-discuss@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/pylons-discuss/a5285237-b1a5-44e5-bd5e-3fd0e4b11c44%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--

Mikko Ohtamaa

unread,
Nov 10, 2016, 4:50:37 PM11/10/16
to pylons-...@googlegroups.com
And to elaborate the following:


I simply check for the permission I know the target has using request.has_permission():

https://websauna.org/docs/narrative/user/permissions.html?highlight=permissions#checking-permissions-in-templates

- Define a Root object

- In this root you have a dynamic __acl__() property that gives logged in users permissions based on their user id or group id

- In your view you have @view_config(permission="my_permission")


-M


Zsolt Ero

unread,
Nov 10, 2016, 5:22:16 PM11/10/16
to pylons-discuss
Thanks. So this is how my site is setup:

I have a RootFactory:
class RootFactory(object):
    __acl__
= [
       
(Allow, Authenticated, 'user'),
       
(Allow, 'g:admin', 'admin'),
       
(Allow, 'g:superadmin', 'ALL_PERMISSIONS'),
   
]


   
def __init__(self, request):
       
pass


used in config:


    config
= Configurator(
        settings
=settings,
        root_factory
=RootFactory,
        authentication_policy
=authn_policy,
        authorization_policy
=authz_policy,
        session_factory
=session_factory)

And my views are defined like this:
@view_config(route_name='admin_db_list', renderer='admin/db_list.jinja2', permission='superadmin')
def db_list(request): ...


So in this situation, my context is request.root (or request.context), is this right?

If I try view_execution_permitted(request.root, request, name='admin_db_list'), I get an "TypeError: No registered view satisfies the constraints."

Do I understand correctly that the name should be a @view_config name _and_ this means using traversal, so I should just forget about using it?

=> So in conclusion, I can only use request.has_permission and duplicate the permission values in template as well?


Mikko Ohtamaa

unread,
Nov 10, 2016, 5:35:23 PM11/10/16
to pylons-...@googlegroups.com
And my views are defined like this:
@view_config(route_name='admin_db_list', renderer='admin/db_list.jinja2', permission='superadmin')
def db_list(request): ...


So in this situation, my context is request.root (or request.context), is this right?

If I try view_execution_permitted(request.root, request, name='admin_db_list'), I get an "TypeError: No registered view satisfies the constraints."

In @view_config() you don't specify name. This is interesting. I'd guess it is function name "db_list" in this case. Can you try this?
 

Do I understand correctly that the name should be a @view_config name _and_ this means using traversal, so I should just forget about using it?

In traversal:

route_name defines the traversal route name (e.g. "admin" for admin interface traversal, "blog" for blog posts")
name defines view name as part of the path. E.g. "edit" for /blog/my-fancy-kittens/edit

I just checked view_execution_permitted() and name argument is indeed view name (traversal like)

I am not sure, so I let the other comments, whether view_execution_permitted() can be used for non-traversal like views.
 

=> So in conclusion, I can only use request.has_permission and duplicate the permission values in template as well?













On Thursday, 10 November 2016 22:50:37 UTC+1, Mikko Ohtamaa wrote:
And to elaborate the following:

I simply check for the permission I know the target has using request.has_permission():

https://websauna.org/docs/narrative/user/permissions.html?highlight=permissions#checking-permissions-in-templates

- Define a Root object

- In this root you have a dynamic __acl__() property that gives logged in users permissions based on their user id or group id

- In your view you have @view_config(permission="my_permission")


-M


--
You received this message because you are subscribed to the Google Groups "pylons-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pylons-discuss+unsubscribe@googlegroups.com.
To post to this group, send email to pylons-discuss@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Zsolt Ero

unread,
Nov 10, 2016, 5:42:36 PM11/10/16
to pylons-...@googlegroups.com
I tried it, and name doesn't work with any function name, even dotted. On the other hand, the moment I add name='...' to a view_config it stops working (404), so I guess it's only working with traversal.

The best resource I found so far was in this comment:


This might or might not work, but looks complicated enough for me not to know if there is a possible bug in it, that I'll just stick with has_permission and duplicated values in templates.




You received this message because you are subscribed to a topic in the Google Groups "pylons-discuss" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/pylons-discuss/4FlvcEXKon4/unsubscribe.
To unsubscribe from this group and all its topics, send an email to pylons-discus...@googlegroups.com.
To post to this group, send email to pylons-...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/pylons-discuss/CAK8RCUtu-gQbyVKQk9psg5atiX-f%2BTnA0-9R-9N46QDAPZ83oA%40mail.gmail.com.

Mikko Ohtamaa

unread,
Nov 10, 2016, 5:48:36 PM11/10/16
to pylons-...@googlegroups.com


This might or might not work, but looks complicated enough for me not to know if there is a possible bug in it, that I'll just stick with has_permission and duplicated values in templates.


Fair. There is a Pyramid sprint coming in December: https://dragonsprint.com/  If you want to get this resolved and participate locally/remotely we might find tutors help to build route_execution_permitted(request, route_name)

Thanks!
Mikko
 

Zsolt Ero

unread,
Nov 10, 2016, 5:53:30 PM11/10/16
to pylons-...@googlegroups.com
Thanks a lot! I'm thinking about it, since I'm quite close (in Budapest).
--
You received this message because you are subscribed to a topic in the Google Groups "pylons-discuss" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/pylons-discuss/4FlvcEXKon4/unsubscribe.
To unsubscribe from this group and all its topics, send an email to pylons-discus...@googlegroups.com.
To post to this group, send email to pylons-...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/pylons-discuss/CAK8RCUuq%2BQXczJWm4eBjEDphym5x6g%2BR7s3v3A-Uc-uPjyA44A%40mail.gmail.com.

Mikko Ohtamaa

unread,
Nov 10, 2016, 6:25:04 PM11/10/16
to pylons-...@googlegroups.com
On 11 November 2016 at 00:53, Zsolt Ero <zsol...@gmail.com> wrote:
Thanks a lot! I'm thinking about it, since I'm quite close (in Budapest).

See you with the dragons, then! :)

Nejc Zupan

unread,
Nov 11, 2016, 12:25:06 AM11/11/16
to pylons-...@googlegroups.com
Just a bit of logistics help, should you need it:
* an affordable and reliable way of getting to Ljubljana from Budapest is the van sharing service GoOpti.com. Been using them for years. 
* there is a discussion on the sprint’s mailinglist about sharing an AirBNB place: https://groups.google.com/forum/#!topic/dragonsprint/4Dew-8y6qMk


Cheers,
z.

You received this message because you are subscribed to the Google Groups "pylons-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pylons-discus...@googlegroups.com.

To post to this group, send email to pylons-...@googlegroups.com.

Zsolt Ero

unread,
Nov 11, 2016, 12:47:03 PM11/11/16
to pylons-...@googlegroups.com

Mike Orr

unread,
Nov 11, 2016, 6:40:17 PM11/11/16
to pylons-...@googlegroups.com
On Thu, Nov 10, 2016 at 12:48 PM, Zsolt Ero <zsol...@gmail.com> wrote:
> Hi, I'd like to do the simplest possible thing in Pyramid / URL Dispatch,
> yet it seems almost impossibly hard to do it.
>
> So it's the super common case of having a menu, and I'd only like to insert
> menu items which are available for the current user.
>
> I'm looking for a function to fit in this usage:
>
> {% macro nav_item(request, route_name, text) -%}
> {% if request.view_execution_permitted(route_name) %}
> <li>
> <a href="{{ request.route_url(route_name) }}">{{ text }}</a>
> </li>
> {% endif %}
> {%- endmacro %}
>
>
> My problems are the following:
>
> 1. view_execution_permitted doesn't work like this, unlike other security
> functions, for example request.has_permission(). Why?

There seems to be a logic problem here. Routes aren't
authorization-significant entities; a route is just a URL pattern.
Users belong in roles/groups (Pyramid calls all of these "principals"
-- defined by the "groupfinder" callback in the authentication
policy). Principles have permissions -- defined in the ACLs others
have talked about. A simple URL Dispatch application may have just one
ACL for everything: a root factory defined at startup sets all the
permissions that all the pages need. A more complex URL Dispatch
application may have different roots for different parts of the
application (different view configurations), and each of these can do
their own private traversal to sub-objects. But it's easier to start
with a single root for the entire application. Each request will have
its own instance of that root, which is the "context" available to the
view and the authorization system. Finally, the view configuration
ties all of these together: the view, the route, the root factory with
its ACL, and the permission required to execute the view. The reason
all these are separate is to keep some independence between the URL
structure, the view callables, the role concepts, and the permissions:
so you can design each one autonomously to its best structure, and you
can change one (within limits) without affecting the others.

So the answer to your question is, what determines which menu items
should be displayed? Not the routes, the *roles* (principles). The
roles imply permissions, and r'equest.has_permission' tells whether
the user has that permission (i.e., can execute the views that require
that permission). However, there's no formal listing of which routes
go with which permissions; that's all tied up in the complex view
confugrations. But you should know which permission each page
requires. So you should do a (in Mako syntax which I'm more familiar
with) '% if request.has_permission("foo_permission"): <a
href="${request.route_url('foo_route')}">text</a> % endif'. I have one
application with permissions and I use 'pyramid_layout', so I define
several boolean attributes in the layout object ('can_do_this',
'can_do_that'), and use those to decide which menu items to display in
the site template. For links in individual pages, the view sets
similar booleans. Actually, I don't always use booleans because I like
to generate the URLs outside the templates, so I use None to indicate
no URL (no link), or I give a list of permitted URLs that doesn't
include the unpermitted ones.

If you need to do something dynamically (e.g., user-defined URLs and
permissions that the developer doesn't know about), then studying
Kotti might be useful, because it allows site admins to define users
and roles and pages and link them together.

So I don't think you'll need to ask whether a route is permitted to a
user; that concept belongs in the principles and ACLs. But there may
be something I'm missing. As for 'view_execution_permitted', I've
never used that and I'm not sure it's useful for application
developers, especially in a URL Dispatch application. It may be
something Pyramid uses more internally. The fact that it requires a
view name is a red flag. Routes kind of replace view names in URL
Dispatch. Traversal uses view names extensively to choose between
multiple views for a resource. URL Dispatch uses mostly the route name
to choose a view, and specifying a view name could accidentally make
the view configuration not match the request.

Zsolt Ero

unread,
Nov 12, 2016, 12:25:45 PM11/12/16
to pylons-discuss
Here is what I see:
- The way Pyramid does everything super flexibly (URL structure, the view callables, the role concepts, and the permissions as you said it) can make it very complicated to do otherwise simple things, such as making a request.route_allowed()...
- If request.route_url() exists and works, why can we not make request.route_allowed()? I understand that one route may point to multiple views, but that mechanism is implemented already, since Pyramid can decide what to do on every incoming request.
- The only thing what I can imagine is not obvious from a request and a route_url is the GET/POST, etc. kind, since the user might want to change that between requests. (For example calling a form, etc.). 

So why can we not make a simple request.route_allowed(**kwargs, method='GET') method, where kwargs would be the same as for route_url()?

Zsolt

Zsolt Ero

unread,
Nov 12, 2016, 1:13:56 PM11/12/16
to pylons-discuss
OK, here is something I made, by looking at pviews util.

def route_allowed(request, route_name):
   
from zope.interface import providedBy
   
from pyramid.interfaces import IRouteRequest
   
from pyramid.interfaces import IRequest
   
from pyramid.view import _find_views


    reg
= request.registry
    request_iface
= reg.queryUtility(IRouteRequest, name=route_name, default=IRequest)
    context_iface
= providedBy(request.context)
    views
= _find_views(reg, request_iface, context_iface, '')


   
assert len(views) == 1


    view
= views[0]
    permission
= view.__permission__


   
return bool(request.has_permission(permission))

Can you explain why this is wrong or how can I make it better? It is limited to the exactly one matching view case (assert len(views) == 1), but I believe it's a fair limitation and is not a problem for a typical URL Dispatch app, and definitely not a problem for my use case.

Zsolt

Mikko Ohtamaa

unread,
Nov 12, 2016, 2:02:33 PM11/12/16
to pylons-...@googlegroups.com
Hi Zsolt,

I like this approach, as it makes front end template development straightforward even if it doesn't represent the pinnacle of clean backend.

--
You received this message because you are subscribed to the Google Groups "pylons-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pylons-discuss+unsubscribe@googlegroups.com.
To post to this group, send email to pylons-discuss@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/pylons-discuss/648eb04e-4036-4161-985a-c7317e8cd653%40googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--

Mike Orr

unread,
Nov 12, 2016, 2:40:41 PM11/12/16
to pylons-...@googlegroups.com
On Sat, Nov 12, 2016 at 9:25 AM, Zsolt Ero <zsol...@gmail.com> wrote:
> Here is what I see:
> - The way Pyramid does everything super flexibly (URL structure, the view
> callables, the role concepts, and the permissions as you said it) can make
> it very complicated to do otherwise simple things, such as making a
> request.route_allowed()...
> - If request.route_url() exists and works, why can we not make
> request.route_allowed()? I understand that one route may point to multiple
> views, but that mechanism is implemented already, since Pyramid can decide
> what to do on every incoming request.

I'll defer to other core developers on this.

> - The only thing what I can imagine is not obvious from a request and a
> route_url is the GET/POST, etc. kind, since the user might want to change
> that between requests. (For example calling a form, etc.).
>
> So why can we not make a simple request.route_allowed(**kwargs,
> method='GET') method, where kwargs would be the same as for route_url()?

You have a point there, true means it's allowed for at least some kind
of request, and it's up to the application writer to know what those
are. Although it might as well default to GET because this is for
showing regular hyperlinks, and regular hyperlinks can only do GET. A
POST would either imply a form has been shown and the POST should be
allowed, or it's an AJAX request in which case the app developer
should know whether it's allowed.
Reply all
Reply to author
Forward
0 new messages