Raising 403 within traversal when the Forbidden context is already used to show the login page

130 views
Skip to first unread message

Yap Sok Ann

unread,
Feb 8, 2012, 7:44:00 PM2/8/12
to pylons-discuss
Hi,

What is the best way to raise 403 (i.e. displaying the Not Authorized
error page to the user) from within traversal, when the Forbidden
context is already used to show the login view?

Right now, I try to add some extra code to the beginning of the login
view:

def login(context, request):
referrer = request.url
login_url = request.route_url('login')

if authenticated_userid(request) is not None:
if referrer == login_url:
return Response('You have already logged in')
else:
# This is for Forbidden raised in traversal.
return Response('Not Authorized', status=403)

# continue with the login form
...

but it doesn't feel right. Plus, this ad hoc 403 page looks different
than those created by pyramid.httpexceptions. How can I make it better?

Jonathan Vanasco

unread,
Feb 8, 2012, 8:13:18 PM2/8/12
to pylons-discuss
i don't use traversal... but can't you just use an httpexception?

http://readthedocs.org/docs/pyramid/en/1.0-branch/api/httpexceptions.html

class HTTPForbidden(detail=None, headers=None, comment=None,
body_template=None, **kw)
subclass of HTTPClientError
This indicates that the server understood the request, but is
refusing to fulfill it.
code: 403, title: Forbidden


from pyramid import httpexceptions
....
return httpexceptions.HTTPForbidden()

Yap Sok Ann

unread,
Feb 9, 2012, 3:39:12 AM2/9/12
to pylons-discuss
HTTPForbidden is exactly what I throw in the traversal code, which
then get mapped to the login view. If I throw HTTPForbidden again in
the login view, it will result in 500 Internal Server Error, thus I
manually create a 403 response and return that instead.

On Feb 9, 9:13 am, Jonathan Vanasco <jonat...@findmeon.com> wrote:
> i don't use traversal... but can't you just use an httpexception?
>
> http://readthedocs.org/docs/pyramid/en/1.0-branch/api/httpexceptions....

Simon Yarde

unread,
Feb 9, 2012, 4:50:40 AM2/9/12
to pylons-...@googlegroups.com
I'm pretty sure you need to be using 401 for pages that require authorisation, and not 403.

Maybe try to untangle your approach so that the login page is never throwing 401 (or 403). The protected resource  should raise the exception and your app design handles it by issuing a redirect to login. The login should always be publicly accessible, regardless of whether you tell the user they are already logged in or not. URIs that represent content should not also serve login forms.

Or did I miss something? :)

10.4.4 403 Forbidden

The server understood the request, but is refusing to fulfill it. Authorization will not help and the request SHOULD NOT be repeated. If the request method was not HEAD and the server wishes to make public why the request has not been fulfilled, it SHOULD describe the reason for the refusal in the entity. If the server does not wish to make this information available to the client, the status code 404 (Not Found) can be used instead.

--
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.

Yap Sok Ann

unread,
Feb 9, 2012, 11:36:44 AM2/9/12
to pylons-discuss
That's what I thought too, but it seems like the "standard" for
pyramid is to show the login view for 403:

http://docs.pylonsproject.org/projects/pyramid/en/latest/tutorials/wiki/authorization.html#add-login-and-logout-views

I think I will just rename my view from "login" to "not_authorized",
and make the 403 response looks more conforming.

Mike Orr

unread,
Feb 9, 2012, 1:40:57 PM2/9/12
to pylons-...@googlegroups.com
On Thu, Feb 9, 2012 at 8:36 AM, Yap Sok Ann <sok...@gmail.com> wrote:
> That's what I thought too, but it seems like the "standard" for
> pyramid is to show the login view for 403:
>
> http://docs.pylonsproject.org/projects/pyramid/en/latest/tutorials/wiki/authorization.html#add-login-and-logout-views

This appears to be a mistake on Pyramid's part. Wouldn't it be better
to fix Pyramid to use 401 HTTPUnauthorized for not-logged-in rather
than using 403 HTTPForbidden for both cases?

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

Michael Merickel

unread,
Feb 9, 2012, 2:48:26 PM2/9/12
to pylons-...@googlegroups.com
Pyramid internally raises a HTTPForbidden... this is the safest thing for Pyramid to do, and requires the fewest assumptions about what your app actually wants. From that point, you can catch the HTTPForbidden in an exception view, determine what you actually want to do, and return that.

For example:

@view_config(context=HTTPForbidden, renderer='unauthorized.mako')
def forbidden_view(request)
    if authenticated_userid(request) is None:
        # user is not logged in, let's redirect them to the login view
        # or we could return a HTTPUnauthorized here or something if we knew
        # what information to send in the www-authenticate header required by a 401
        return HTTPFound(request.route_url('login'))

    # user is logged in and does not have access to the resource
    # so let's render the unauthorized template and set the response code to be 403
    # because they really are forbidden
    request.response.status = 403
    return {}

HTTPUnauthorized is supposed to return a way for the user to authenticated, so it's not generally applicable. For example you would use it in a "http basic" policy to get the browser to display a challenge.

Simon Yarde

unread,
Feb 10, 2012, 12:24:10 PM2/10/12
to pylons-...@googlegroups.com
Sorry I should have added, but resisted as I felt my message was long enough..

Just because Pyramid (or your app) raises an http exception doesn't mean you have to return that view. As Michael describes you can intercept and downgrade the exception as you deem appropriate. 

The reason your code doesn't work is not because of Pyramid raising 403 or 401 exception, it's because you have a tangle (which was initially resulting in the server error loop). You never actually want to serve a 401 view because you'll always be redirecting with HTTPFound to login as Michael's example and the tutorial you're following shows (unless your user is not human and knows how to authenticate and retry). The 403 view (NOT the login page) is what you serve when you have an authenticated user who doesn't have the necessary permission; you might like to say 'you don't have permission to see this' rather than just 'forbidden', because forbidden doesn't really make sense in a web-app (i.e. if something is totally forbidden then don't serve it!).

I might have come at it differently raising 401 initially (all we can say is 'auth required' because we don't know who the user is) and then issue 403 if the authenticated user lacked a particular permission, but then I probably haven't spent as much time wrestling with this matter - I would be intrigued  to learn of the reasoning if you have the time Michael? :)

I always seem to find the meaning of http status code 302 rather odd in the auth context because it says "The requested resource resides temporarily under a different URI", which is not accurate but seems to be the best fit.

Mike Orr

unread,
Feb 10, 2012, 12:48:42 PM2/10/12
to pylons-...@googlegroups.com
On Fri, Feb 10, 2012 at 9:24 AM, Simon Yarde <simon...@me.com> wrote:
> I always seem to find the meaning of http status code 302 rather odd in the
> auth context because it says "The requested resource resides temporarily
> under a different URI", which is not accurate but seems to be the best fit.

I've been wondering that too. How is it throwing off search engines,
or do they parse the page to guess if it's a login form. There should
either be a code for redirect with error, or 4xx errors should also be
allowed to have a Location header.

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

Michael Merickel

unread,
Feb 10, 2012, 12:53:40 PM2/10/12
to pylons-...@googlegroups.com
Likely pyramid.httpexceptions.HTTPSeeOther should be used instead (status code 303).

http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
The new URI is not a substitute reference for the originally requested resource. 


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

Michael Merickel

unread,
Feb 10, 2012, 12:58:40 PM2/10/12
to pylons-...@googlegroups.com
On Fri, Feb 10, 2012 at 11:24 AM, Simon Yarde <simon...@me.com> wrote:
I might have come at it differently raising 401 initially (all we can say is 'auth required' because we don't know who the user is) and then issue 403 if the authenticated user lacked a particular permission, but then I probably haven't spent as much time wrestling with this matter - I would be intrigued  to learn of the reasoning if you have the time Michael? :)

The issue, as I see it, is that 401 should not be raised unless you have a custom authentication policy (and system) that explicitly supports returning a WWW-Authenticate header to challenge the browser, and then accepting the response. For example, for a Basic policy:

Server: return the challenge in your forbidden view.
Client: enter username/password into the popup box, submitting a new request for the same content, but with credentials in the header.
Server: receive credentials as part of the request, and your authentication policy would see those credentials, and allow access to the content.

If you don't have this type of mechanism in place, 401 doesn't make sense. I'd be happy to be corrected.

Simon Yarde

unread,
Feb 10, 2012, 1:13:40 PM2/10/12
to pylons-...@googlegroups.com
I think that makes total sense. I guess I'm mulling over the best-fit-approach of using the available Pyramid exceptions, and wondering if it would be better to create more meaningful exceptions for the web app and then handle these by serving the best-fit-http-response. Say for example the 303 might be the best choice but I might be restricted to using 302 because of user agent support, but at the app level the exception remains accurate (e.g. raise AuthenticationRequired, or raise PermissionRequired, do stuff, serve most appropriate http response). I can't help thinking that trying to shoe horn http protocol into application exception logic is at the heart of the confusion the original poster is experiencing?
--

Yap Sok Ann

unread,
Feb 10, 2012, 8:50:45 PM2/10/12
to pylons-discuss
Hi Simon,

Actually, the code I posted does work, and I was just looking for a
better solution.

If you read my reply to you:

> I think I will just rename my view from "login" to "not_authorized", and make the 403 response looks more conforming.

that's essentially the same as what Michael suggested with the view
named "forbidden_view" and the associated template
"unauthorized.mako".

I think the source of confusion is that the tutorial uses a view named
"login" for the Forbidden context. Once the view is renamed to
something like "not_authorized" or "forbidden_view", everything flows
nicely.

On Feb 11, 2:13 am, Simon Yarde <simonya...@me.com> wrote:
> I think that makes total sense. I guess I'm mulling over the best-fit-approach of using the available Pyramid exceptions, and wondering if it would be better to create more meaningful exceptions for the web app and then handle these by serving the best-fit-http-response. Say for example the 303 might be the best choice but I might be restricted to using 302 because of user agent support, but at the app level the exception remains accurate (e.g. raise AuthenticationRequired, or raise PermissionRequired, do stuff, serve most appropriate http response). I can't help thinking that trying to shoe horn http protocol into application exception logic is at the heart of the confusion the original poster is experiencing?
>
> On 10 Feb 2012, at 18:58, Michael Merickel <mmeri...@gmail.com> wrote:

Michael Merickel

unread,
Feb 10, 2012, 9:03:43 PM2/10/12
to pylons-...@googlegroups.com
The real bad practice in the tutorial currently is that it shows you a login view even if you are already logged in. It's not actually "wrong" to show the login template at the content URL instead of redirecting the user to a login URL. However it's also not usually what you want. Thus the open ticket below.

Reply all
Reply to author
Forward
0 new messages