From c9235146e0102d03bb4548711cd0b3b0637d81fa Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Sun, 9 Jun 2024 16:28:34 -0400 Subject: [PATCH 1/3] docs: remove 'came_from' from login view - The narrative doesn't discuss this (mis-)feature. - Without any authorization, there is no meaninful reason to remember the 'previous' page. - As a general rule, we want to avoid trusting user-supplied data (i.e., from the query string or form params) when constructing redirect URLs. --- docs/quick_tutorial/authentication.rst | 2 +- docs/quick_tutorial/authentication/tutorial/home.pt | 6 ++++-- docs/quick_tutorial/authentication/tutorial/login.pt | 2 -- docs/quick_tutorial/authentication/tutorial/views.py | 7 +------ 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/docs/quick_tutorial/authentication.rst b/docs/quick_tutorial/authentication.rst index 3f6df17de0..da76f3ec77 100644 --- a/docs/quick_tutorial/authentication.rst +++ b/docs/quick_tutorial/authentication.rst @@ -137,7 +137,7 @@ Subsequent requests return that cookie and identify the user. In our template, we fetched the ``logged_in`` value from the view class. We use this to calculate the logged-in user, if any. In the template we can then choose to show a login link to anonymous visitors or a logout link to logged-in -users. +users, including their login name. Extra credit diff --git a/docs/quick_tutorial/authentication/tutorial/home.pt b/docs/quick_tutorial/authentication/tutorial/home.pt index ed911b6739..0e8508558b 100644 --- a/docs/quick_tutorial/authentication/tutorial/home.pt +++ b/docs/quick_tutorial/authentication/tutorial/home.pt @@ -8,8 +8,10 @@
Log In - Logout + + Logout + as ${view.logged_in} +

Hi ${name}

diff --git a/docs/quick_tutorial/authentication/tutorial/login.pt b/docs/quick_tutorial/authentication/tutorial/login.pt index 9e5bfe2ad6..db8080fc88 100644 --- a/docs/quick_tutorial/authentication/tutorial/login.pt +++ b/docs/quick_tutorial/authentication/tutorial/login.pt @@ -8,8 +8,6 @@
- Date: Sun, 9 Jun 2024 21:04:45 -0400 Subject: [PATCH 2/3] 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. --- docs/quick_tutorial/authorization.rst | 14 ++++-- .../authorization/tutorial/__init__.py | 9 +++- .../authorization/tutorial/home.pt | 6 ++- .../authorization/tutorial/login.pt | 2 - .../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 b1ef86a17d..9a5b7c738e 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 255bb35ace..f59d5ab6d2 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 ed911b6739..0e8508558b 100644 --- a/docs/quick_tutorial/authorization/tutorial/home.pt +++ b/docs/quick_tutorial/authorization/tutorial/home.pt @@ -8,8 +8,10 @@
Log In - Logout + + Logout + as ${view.logged_in} +

Hi ${name}

diff --git a/docs/quick_tutorial/authorization/tutorial/login.pt b/docs/quick_tutorial/authorization/tutorial/login.pt index 9e5bfe2ad6..db8080fc88 100644 --- a/docs/quick_tutorial/authorization/tutorial/login.pt +++ b/docs/quick_tutorial/authorization/tutorial/login.pt @@ -8,8 +8,6 @@ - Date: Sun, 9 Jun 2024 21:15:03 -0400 Subject: [PATCH 3/3] chore: remove 'retail_forms' fossil Ten years on, it has never landed in the generated docs. --- .../retail_forms/development.ini | 9 -- docs/quick_tutorial/retail_forms/setup.py | 20 ---- .../retail_forms/tutorial/__init__.py | 13 --- .../retail_forms/tutorial/tests.py | 36 ------- .../retail_forms/tutorial/views.py | 96 ------------------- .../retail_forms/tutorial/wiki_view.pt | 19 ---- .../retail_forms/tutorial/wikipage_addedit.pt | 37 ------- .../retail_forms/tutorial/wikipage_view.pt | 17 ---- 8 files changed, 247 deletions(-) delete mode 100644 docs/quick_tutorial/retail_forms/development.ini delete mode 100644 docs/quick_tutorial/retail_forms/setup.py delete mode 100644 docs/quick_tutorial/retail_forms/tutorial/__init__.py delete mode 100644 docs/quick_tutorial/retail_forms/tutorial/tests.py delete mode 100644 docs/quick_tutorial/retail_forms/tutorial/views.py delete mode 100644 docs/quick_tutorial/retail_forms/tutorial/wiki_view.pt delete mode 100644 docs/quick_tutorial/retail_forms/tutorial/wikipage_addedit.pt delete mode 100644 docs/quick_tutorial/retail_forms/tutorial/wikipage_view.pt diff --git a/docs/quick_tutorial/retail_forms/development.ini b/docs/quick_tutorial/retail_forms/development.ini deleted file mode 100644 index 78d7479e7c..0000000000 --- a/docs/quick_tutorial/retail_forms/development.ini +++ /dev/null @@ -1,9 +0,0 @@ -[app:main] -use = egg:tutorial -pyramid.reload_templates = true -pyramid.includes = - pyramid_debugtoolbar - -[server:main] -use = egg:waitress#main -listen = localhost:6543 diff --git a/docs/quick_tutorial/retail_forms/setup.py b/docs/quick_tutorial/retail_forms/setup.py deleted file mode 100644 index dda0a2cc4e..0000000000 --- a/docs/quick_tutorial/retail_forms/setup.py +++ /dev/null @@ -1,20 +0,0 @@ -from setuptools import setup - -# List of dependencies installed via `pip install -e .` -# by virtue of the Setuptools `install_requires` value below. -requires = [ - 'deform', - 'pyramid', - 'pyramid_chameleon', - 'waitress', -] - -setup( - name='tutorial', - install_requires=requires, - entry_points={ - 'paste.app_factory': [ - 'main = tutorial:main' - ], - }, -) diff --git a/docs/quick_tutorial/retail_forms/tutorial/__init__.py b/docs/quick_tutorial/retail_forms/tutorial/__init__.py deleted file mode 100644 index dff7457cfe..0000000000 --- a/docs/quick_tutorial/retail_forms/tutorial/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -from pyramid.config import Configurator - - -def main(global_config, **settings): - config = Configurator(settings=settings) - config.include('pyramid_chameleon') - config.add_route('wiki_view', '/') - config.add_route('wikipage_add', '/add') - config.add_route('wikipage_view', '/{uid}') - config.add_route('wikipage_edit', '/{uid}/edit') - config.add_static_view('deform_static', 'deform:static/') - config.scan('.views') - return config.make_wsgi_app() diff --git a/docs/quick_tutorial/retail_forms/tutorial/tests.py b/docs/quick_tutorial/retail_forms/tutorial/tests.py deleted file mode 100644 index 5a2c409046..0000000000 --- a/docs/quick_tutorial/retail_forms/tutorial/tests.py +++ /dev/null @@ -1,36 +0,0 @@ -import unittest - -from pyramid import testing - - -class TutorialViewTests(unittest.TestCase): - def setUp(self): - self.config = testing.setUp() - - def tearDown(self): - testing.tearDown() - - def test_home(self): - from .views import WikiViews - - request = testing.DummyRequest() - inst = WikiViews(request) - response = inst.wiki_view() - self.assertEqual(len(response['pages']), 3) - - -class TutorialFunctionalTests(unittest.TestCase): - def setUp(self): - from tutorial import main - - app = main({}) - from webtest import TestApp - - self.testapp = TestApp(app) - - def tearDown(self): - testing.tearDown() - - def test_home(self): - res = self.testapp.get('/', status=200) - self.assertIn(b'Wiki: View', res.body) diff --git a/docs/quick_tutorial/retail_forms/tutorial/views.py b/docs/quick_tutorial/retail_forms/tutorial/views.py deleted file mode 100644 index c6f4e38773..0000000000 --- a/docs/quick_tutorial/retail_forms/tutorial/views.py +++ /dev/null @@ -1,96 +0,0 @@ -import colander -import deform.widget - -from pyramid.httpexceptions import HTTPFound -from pyramid.view import view_config - -pages = { - '100': dict(uid='100', title='Page 100', body='100'), - '101': dict(uid='101', title='Page 101', body='101'), - '102': dict(uid='102', title='Page 102', body='102') -} - -class WikiPage(colander.MappingSchema): - title = colander.SchemaNode(colander.String()) - body = colander.SchemaNode( - colander.String(), - widget=deform.widget.RichTextWidget() - ) - - -class WikiViews: - def __init__(self, request): - self.request = request - - @property - def wiki_form(self): - schema = WikiPage() - return deform.Form(schema, buttons=('submit',)) - - @property - def reqts(self): - return self.wiki_form.get_widget_resources() - - @view_config(route_name='wiki_view', renderer='wiki_view.pt') - def wiki_view(self): - return dict(pages=pages.values()) - - @view_config(route_name='wikipage_add', - renderer='wikipage_addedit.pt') - def wikipage_add(self): - form = self.wiki_form - - if 'submit' in self.request.params: - controls = self.request.POST.items() - try: - appstruct = self.wiki_form.validate(controls) - except deform.ValidationFailure as e: - # Form is NOT valid - return dict(form=e.render()) - - # Form is valid, make a new identifier and add to list - last_uid = int(sorted(pages.keys())[-1]) - new_uid = str(last_uid + 1) - pages[new_uid] = dict( - uid=new_uid, title=appstruct['title'], - body=appstruct['body'] - ) - - # Now visit new page - url = self.request.route_url('wikipage_view', uid=new_uid) - return HTTPFound(url) - - return dict(form=form) - - @view_config(route_name='wikipage_view', renderer='wikipage_view.pt') - def wikipage_view(self): - uid = self.request.matchdict['uid'] - page = pages[uid] - return dict(page=page) - - @view_config(route_name='wikipage_edit', - renderer='wikipage_addedit.pt') - def wikipage_edit(self): - uid = self.request.matchdict['uid'] - page = pages[uid] - - wiki_form = self.wiki_form - - if 'submit' in self.request.params: - controls = self.request.POST.items() - try: - appstruct = wiki_form.validate(controls) - except deform.ValidationFailure as e: - return dict(page=page, form=e.render()) - - # Change the content and redirect to the view - page['title'] = appstruct['title'] - page['body'] = appstruct['body'] - - url = self.request.route_url('wikipage_view', - uid=page['uid']) - return HTTPFound(url) - - form = wiki_form.render(page) - - return dict(page=page, form=form) \ No newline at end of file diff --git a/docs/quick_tutorial/retail_forms/tutorial/wiki_view.pt b/docs/quick_tutorial/retail_forms/tutorial/wiki_view.pt deleted file mode 100644 index 9e3afe4950..0000000000 --- a/docs/quick_tutorial/retail_forms/tutorial/wiki_view.pt +++ /dev/null @@ -1,19 +0,0 @@ - - - - Wiki: View - - -

Wiki

- -Add - WikiPage - - - \ No newline at end of file diff --git a/docs/quick_tutorial/retail_forms/tutorial/wikipage_addedit.pt b/docs/quick_tutorial/retail_forms/tutorial/wikipage_addedit.pt deleted file mode 100644 index 586f4c44b1..0000000000 --- a/docs/quick_tutorial/retail_forms/tutorial/wikipage_addedit.pt +++ /dev/null @@ -1,37 +0,0 @@ - - - - WikiPage: Add/Edit - - - - - - - - -

Wiki

- -
-
- ${structure:field.title} - * -
-
- ${structure:field.serialize()} -
-
    -
  • - ${structure:error} -
  • -
-
- - - - diff --git a/docs/quick_tutorial/retail_forms/tutorial/wikipage_view.pt b/docs/quick_tutorial/retail_forms/tutorial/wikipage_view.pt deleted file mode 100644 index cb9ff526e7..0000000000 --- a/docs/quick_tutorial/retail_forms/tutorial/wikipage_view.pt +++ /dev/null @@ -1,17 +0,0 @@ - - - - WikiPage: View - - - - Up - | - - Edit - - -

${page.title}

-

${structure: page.body}

- - \ No newline at end of file