diff options
| author | Tres Seaver <tseaver@palladion.com> | 2024-06-09 21:04:45 -0400 |
|---|---|---|
| committer | Tres Seaver <tseaver@palladion.com> | 2024-06-09 21:11:38 -0400 |
| commit | e72d437280d39bf8a8f3f62c6987268537ad5b11 (patch) | |
| tree | 6318d2ba12327906dde9d387372944627f3eca3b | |
| parent | c9235146e0102d03bb4548711cd0b3b0637d81fa (diff) | |
| download | pyramid-e72d437280d39bf8a8f3f62c6987268537ad5b11.tar.gz pyramid-e72d437280d39bf8a8f3f62c6987268537ad5b11.tar.bz2 pyramid-e72d437280d39bf8a8f3f62c6987268537ad5b11.zip | |
fix: store 'came_from' information in the session
- As with the previous commit, we want to avoid trusting user-supplied data
from the query string or form parameters when constructing redirect URLs.
- Storing the route name and matchdict for the view being forbidden in
the session allows us to construct the redirect URL on successful
login cleanly.
- In order to clarify that the logic of storing the 'came from'
information is separate from rendering or processing the login form,
this PR splits the `@forbidden_view` mapping onto a separate view function.
| -rw-r--r-- | docs/quick_tutorial/authorization.rst | 14 | ||||
| -rw-r--r-- | docs/quick_tutorial/authorization/tutorial/__init__.py | 9 | ||||
| -rw-r--r-- | docs/quick_tutorial/authorization/tutorial/home.pt | 6 | ||||
| -rw-r--r-- | docs/quick_tutorial/authorization/tutorial/login.pt | 2 | ||||
| -rw-r--r-- | docs/quick_tutorial/authorization/tutorial/views.py | 43 |
5 files changed, 56 insertions, 18 deletions
diff --git a/docs/quick_tutorial/authorization.rst b/docs/quick_tutorial/authorization.rst index b1ef86a17..9a5b7c738 100644 --- a/docs/quick_tutorial/authorization.rst +++ b/docs/quick_tutorial/authorization.rst @@ -104,9 +104,17 @@ Of course, this only applies on ``Root``. Some other part of the site (a.k.a. *context*) might have a different ACL. If you are not logged in and visit ``/howdy``, you need to get shown the login -screen. How does Pyramid know what is the login page to use? We explicitly told -Pyramid that the ``login`` view should be used by decorating the view with -``@forbidden_view_config``. +screen. How does Pyramid know what is the login page to use? We defined an +explicit "forbidden view", decorating that view with +``@forbidden_view_config``, and then had it store the information about the +route being protected in the request's session, before redirecting to the +login view. + +.. note:: + + We use the session to store the ``came_from`` information, rather than a + hidden form input, in order to avoid trusting user-supplied data (from the + form or query string) when constructing redirect URLs. Extra credit diff --git a/docs/quick_tutorial/authorization/tutorial/__init__.py b/docs/quick_tutorial/authorization/tutorial/__init__.py index 255bb35ac..f59d5ab6d 100644 --- a/docs/quick_tutorial/authorization/tutorial/__init__.py +++ b/docs/quick_tutorial/authorization/tutorial/__init__.py @@ -1,11 +1,16 @@ from pyramid.config import Configurator +from pyramid.session import SignedCookieSessionFactory from .security import SecurityPolicy def main(global_config, **settings): - config = Configurator(settings=settings, - root_factory='.resources.Root') + my_session_factory = SignedCookieSessionFactory('itsaseekreet') + config = Configurator( + settings=settings, + root_factory='.resources.Root', + session_factory=my_session_factory, + ) config.include('pyramid_chameleon') config.set_security_policy( diff --git a/docs/quick_tutorial/authorization/tutorial/home.pt b/docs/quick_tutorial/authorization/tutorial/home.pt index ed911b673..0e8508558 100644 --- a/docs/quick_tutorial/authorization/tutorial/home.pt +++ b/docs/quick_tutorial/authorization/tutorial/home.pt @@ -8,8 +8,10 @@ <div> <a tal:condition="view.logged_in is None" href="${request.application_url}/login">Log In</a> - <a tal:condition="view.logged_in is not None" - href="${request.application_url}/logout">Logout</a> + <span tal:condition="view.logged_in is not None"> + <a href="${request.application_url}/logout">Logout</a> + as ${view.logged_in} + </span> </div> <h1>Hi ${name}</h1> diff --git a/docs/quick_tutorial/authorization/tutorial/login.pt b/docs/quick_tutorial/authorization/tutorial/login.pt index 9e5bfe2ad..db8080fc8 100644 --- a/docs/quick_tutorial/authorization/tutorial/login.pt +++ b/docs/quick_tutorial/authorization/tutorial/login.pt @@ -8,8 +8,6 @@ <span tal:replace="message"/> <form action="${url}" method="post"> - <input type="hidden" name="came_from" - value="${came_from}"/> <label for="login">Username</label> <input type="text" id="login" name="login" diff --git a/docs/quick_tutorial/authorization/tutorial/views.py b/docs/quick_tutorial/authorization/tutorial/views.py index b9c828086..9b3bbe42a 100644 --- a/docs/quick_tutorial/authorization/tutorial/views.py +++ b/docs/quick_tutorial/authorization/tutorial/views.py @@ -30,33 +30,58 @@ class TutorialViews: def hello(self): return {'name': 'Hello View'} + @forbidden_view_config() + def forbidden(self): + request = self.request + session = request.session + if request.matched_route is not None: + session['came_from'] = { + 'route_name': request.matched_route.name, + 'route_kwargs': request.matchdict, + } + if request.authenticated_userid is not None: + session['message'] = ( + f'User {request.authenticated_userid} is not allowed ' + f'to see this resource. Please log in as another user.' + ) + else: + if 'came_from' in session: + del session['came_from'] + + return HTTPFound(request.route_url('login')) + @view_config(route_name='login', renderer='login.pt') - @forbidden_view_config(renderer='login.pt') def login(self): request = self.request + session = request.session login_url = request.route_url('login') - referrer = request.url - if referrer == login_url: - referrer = '/' # never use login form itself as came_from - came_from = request.params.get('came_from', referrer) - message = '' + came_from = session.get('came_from') + message = session.get('message', '') login = '' password = '' + if 'form.submitted' in request.params: login = request.params['login'] password = request.params['password'] hashed_pw = USERS.get(login) if hashed_pw and check_password(password, hashed_pw): headers = remember(request, login) - return HTTPFound(location=came_from, - headers=headers) + + if came_from is not None: + return_to = request.route_url( + came_from['route_name'], **came_from['route_kwargs'], + ) + else: + return_to = request.route_url('home') + + return HTTPFound(location=return_to, headers=headers) + message = 'Failed login' return dict( name='Login', message=message, url=request.application_url + '/login', - came_from=came_from, login=login, password=password, ) |
