summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTres Seaver <tseaver@palladion.com>2024-06-09 21:04:45 -0400
committerTres Seaver <tseaver@palladion.com>2024-06-09 21:11:38 -0400
commite72d437280d39bf8a8f3f62c6987268537ad5b11 (patch)
tree6318d2ba12327906dde9d387372944627f3eca3b
parentc9235146e0102d03bb4548711cd0b3b0637d81fa (diff)
downloadpyramid-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.rst14
-rw-r--r--docs/quick_tutorial/authorization/tutorial/__init__.py9
-rw-r--r--docs/quick_tutorial/authorization/tutorial/home.pt6
-rw-r--r--docs/quick_tutorial/authorization/tutorial/login.pt2
-rw-r--r--docs/quick_tutorial/authorization/tutorial/views.py43
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,
)