summaryrefslogtreecommitdiff
path: root/docs/tutorials/wiki/authorization.rst
diff options
context:
space:
mode:
authorChristoph Zwerschke <cito@online.de>2016-04-19 20:07:12 +0200
committerChristoph Zwerschke <cito@online.de>2016-04-19 20:07:12 +0200
commit3629c49e46207ff5162a82883c14937e6ef4c186 (patch)
tree1306181202cb8313f16080789f5b9ab1eeb61d53 /docs/tutorials/wiki/authorization.rst
parent804ba0b2f434781e77d2b5191f1cd76a490f6610 (diff)
parent6c16fb020027fac47e4d2e335cd9e264dba8aa3b (diff)
downloadpyramid-3629c49e46207ff5162a82883c14937e6ef4c186.tar.gz
pyramid-3629c49e46207ff5162a82883c14937e6ef4c186.tar.bz2
pyramid-3629c49e46207ff5162a82883c14937e6ef4c186.zip
Merge remote-tracking branch 'refs/remotes/Pylons/master'
Diffstat (limited to 'docs/tutorials/wiki/authorization.rst')
-rw-r--r--docs/tutorials/wiki/authorization.rst523
1 files changed, 299 insertions, 224 deletions
diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst
index 358c1d5eb..44097b35b 100644
--- a/docs/tutorials/wiki/authorization.rst
+++ b/docs/tutorials/wiki/authorization.rst
@@ -1,297 +1,372 @@
+.. _wiki_adding_authorization:
+
====================
-Adding Authorization
+Adding authorization
====================
-Our application currently allows anyone with access to the server to view,
-edit, and add pages to our wiki. For purposes of demonstration we'll change
-our application to allow people whom are members of a *group* named
-``group:editors`` to add and edit wiki pages but we'll continue allowing
-anyone with access to the server to view pages. :app:`Pyramid` provides
-facilities for :term:`authorization` and :term:`authentication`. We'll make
-use of both features to provide security to our application.
+:app:`Pyramid` provides facilities for :term:`authentication` and
+:term:`authorization`. We'll make use of both features to provide security to
+our application. Our application currently allows anyone with access to the
+server to view, edit, and add pages to our wiki. We'll change that to allow
+only people who are members of a *group* named ``group:editors`` to add and
+edit wiki pages, but we'll continue allowing anyone with access to the server
+to view pages.
-We will add an :term:`authentication policy` and an
-:term:`authorization policy` to our :term:`application
-registry`, add a ``security.py`` module and give our :term:`root`
-resource an :term:`ACL`.
+We will also add a login page and a logout link on all the pages. The login
+page will be shown when a user is denied access to any of the views that
+require permission, instead of a default "403 Forbidden" page.
-Then we will add ``login`` and ``logout`` views, and modify the
-existing views to make them return a ``logged_in`` flag to the
-renderer and add :term:`permission` declarations to their ``view_config``
-decorators.
+We will implement the access control with the following steps:
-Finally, we will add a ``login.pt`` template and change the existing
-``view.pt`` and ``edit.pt`` to show a "Logout" link when not logged in.
+* Add users and groups (``security.py``, a new module).
+* Add an :term:`ACL` (``models.py``).
+* Add an :term:`authentication policy` and an :term:`authorization policy`
+ (``__init__.py``).
+* Add :term:`permission` declarations to the ``edit_page`` and ``add_page``
+ views (``views.py``).
-The source code for this tutorial stage can be browsed via
-`http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/authorization/
-<http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/authorization/>`_.
+Then we will add the login and logout feature:
-Adding Authentication and Authorization Policies
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+* Add ``login`` and ``logout`` views (``views.py``).
+* Add a login template (``login.pt``).
+* Make the existing views return a ``logged_in`` flag to the renderer
+ (``views.py``).
+* Add a "Logout" link to be shown when logged in and viewing or editing a page
+ (``view.pt``, ``edit.pt``).
-We'll change our package's ``__init__.py`` file to enable an
-``AuthTktAuthenticationPolicy`` and an ``ACLAuthorizationPolicy`` to enable
-declarative security checking. We need to import the new policies:
-.. literalinclude:: src/authorization/tutorial/__init__.py
- :lines: 4-5,8
+Access control
+--------------
+
+Add users and groups
+~~~~~~~~~~~~~~~~~~~~
+
+Create a new ``tutorial/security.py`` module with the
+following content:
+
+.. literalinclude:: src/authorization/tutorial/security.py
:linenos:
:language: python
-Then, we'll add those policies to the configuration:
+The ``groupfinder`` function accepts a userid and a request and
+returns one of these values:
-.. literalinclude:: src/authorization/tutorial/__init__.py
- :lines: 16-18,26-28
+- If the userid exists in the system, it will return a sequence of group
+ identifiers (or an empty sequence if the user isn't a member of any groups).
+- If the userid *does not* exist in the system, it will return ``None``.
+
+For example, ``groupfinder('editor', request )`` returns ``['group:editor']``,
+``groupfinder('viewer', request)`` returns ``[]``, and ``groupfinder('admin',
+request)`` returns ``None``. We will use ``groupfinder()`` as an
+:term:`authentication policy` "callback" that will provide the
+:term:`principal` or principals for a user.
+
+In a production system, user and group data will most often come from a
+database, but here we use "dummy" data to represent user and groups sources.
+
+Add an ACL
+~~~~~~~~~~
+
+Open ``tutorial/models.py`` and add the following import
+statement at the head:
+
+.. literalinclude:: src/authorization/tutorial/models.py
+ :lines: 4-7
:linenos:
:language: python
-Note that the creation of an ``AuthTktAuthenticationPolicy`` requires two
-arguments: ``secret`` and ``callback``. ``secret`` is a string representing
-an encryption key used by the "authentication ticket" machinery represented
-by this policy: it is required. The ``callback`` is a reference to a
-``groupfinder`` function in the ``tutorial`` package's ``security.py`` file.
-We haven't added that module yet, but we're about to.
-
-When you're done, your ``__init__.py`` will
-look like so:
+Add the following lines to the ``Wiki`` class:
-.. literalinclude:: src/authorization/tutorial/__init__.py
+.. literalinclude:: src/authorization/tutorial/models.py
+ :lines: 9-13
:linenos:
+ :lineno-start: 9
+ :emphasize-lines: 4-5
:language: python
-Adding ``security.py``
-~~~~~~~~~~~~~~~~~~~~~~
+We import :data:`~pyramid.security.Allow`, an action that means that
+permission is allowed, and :data:`~pyramid.security.Everyone`, a special
+:term:`principal` that is associated to all requests. Both are used in the
+:term:`ACE` entries that make up the ACL.
-Add a ``security.py`` module within your package (in the same
-directory as ``__init__.py``, ``views.py``, etc.) with the following
-content:
+The ACL is a list that needs to be named `__acl__` and be an attribute of a
+class. We define an :term:`ACL` with two :term:`ACE` entries: the first entry
+allows any user the `view` permission. The second entry allows the
+``group:editors`` principal the `edit` permission.
-.. literalinclude:: src/authorization/tutorial/security.py
+The ``Wiki`` class that contains the ACL is the :term:`resource` constructor
+for the :term:`root` resource, which is a ``Wiki`` instance. The ACL is
+provided to each view in the :term:`context` of the request as the ``context``
+attribute.
+
+It's only happenstance that we're assigning this ACL at class scope. An ACL
+can be attached to an object *instance* too; this is how "row level security"
+can be achieved in :app:`Pyramid` applications. We actually need only *one*
+ACL for the entire system, however, because our security requirements are
+simple, so this feature is not demonstrated. See :ref:`assigning_acls` for
+more information about what an :term:`ACL` represents.
+
+Add authentication and authorization policies
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Open ``tutorial/__init__.py`` and add the highlighted import
+statements:
+
+.. literalinclude:: src/authorization/tutorial/__init__.py
+ :lines: 1-8
:linenos:
+ :emphasize-lines: 4-5,8
:language: python
-The ``groupfinder`` function defined here is an :term:`authentication policy`
-"callback"; it is a callable that accepts a userid and a request. If the
-userid exists in the system, the callback will
-return a sequence of group identifiers (or an empty sequence if the user
-isn't a member of any groups). If the userid *does not* exist in the system,
-the callback will return ``None``. In a production system, user and group data will
-most often come from a database, but here we use "dummy" data to represent
-user and groups sources. Note that the ``editor`` user is a member of the
-``group:editors`` group in our dummy group data (the ``GROUPS`` data
-structure).
+Now add those policies to the configuration:
-Giving Our Root Resource an ACL
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.. literalinclude:: src/authorization/tutorial/__init__.py
+ :lines: 18-23
+ :linenos:
+ :lineno-start: 18
+ :emphasize-lines: 1-3,5-6
+ :language: python
-We need to give our root resource object an :term:`ACL`. This ACL will be
-sufficient to provide enough information to the :app:`Pyramid` security
-machinery to challenge a user who doesn't have appropriate credentials when
-he attempts to invoke the ``add_page`` or ``edit_page`` views.
+Only the highlighted lines need to be added.
-We need to perform some imports at module scope in our ``models.py`` file:
+We are enabling an ``AuthTktAuthenticationPolicy``, which is based in an auth
+ticket that may be included in the request. We are also enabling an
+``ACLAuthorizationPolicy``, which uses an ACL to determine the *allow* or
+*deny* outcome for a view.
-.. code-block:: python
- :linenos:
+Note that the :class:`pyramid.authentication.AuthTktAuthenticationPolicy`
+constructor accepts two arguments: ``secret`` and ``callback``. ``secret`` is
+a string representing an encryption key used by the "authentication ticket"
+machinery represented by this policy: it is required. The ``callback`` is the
+``groupfinder()`` function that we created before.
- from pyramid.security import Allow
- from pyramid.security import Everyone
+Add permission declarations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Open ``tutorial/views.py`` and add a ``permission='edit'`` parameter
+to the ``@view_config`` decorators for ``add_page()`` and ``edit_page()``:
-Our root resource object is a ``Wiki`` instance. We'll add the following
-line at class scope to our ``Wiki`` class:
+.. literalinclude:: src/authorization/tutorial/views.py
+ :lines: 50-52
+ :emphasize-lines: 2-3
+ :language: python
-.. code-block:: python
- :linenos:
+.. literalinclude:: src/authorization/tutorial/views.py
+ :lines: 70-72
+ :emphasize-lines: 2-3
+ :language: python
- __acl__ = [ (Allow, Everyone, 'view'),
- (Allow, 'group:editors', 'edit') ]
+Only the highlighted lines, along with their preceding commas, need to be
+edited and added.
-It's only happenstance that we're assigning this ACL at class scope. An ACL
-can be attached to an object *instance* too; this is how "row level security"
-can be achieved in :app:`Pyramid` applications. We actually only need *one*
-ACL for the entire system, however, because our security requirements are
-simple, so this feature is not demonstrated.
+The result is that only users who possess the ``edit`` permission at the time
+of the request may invoke those two views.
-Our resulting ``models.py`` file will now look like so:
+Add a ``permission='view'`` parameter to the ``@view_config`` decorator for
+``view_wiki()`` and ``view_page()`` as follows:
-.. literalinclude:: src/authorization/tutorial/models.py
- :linenos:
+.. literalinclude:: src/authorization/tutorial/views.py
+ :lines: 23-24
+ :emphasize-lines: 1-2
:language: python
-Adding Login and Logout Views
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.. literalinclude:: src/authorization/tutorial/views.py
+ :lines: 28-29
+ :emphasize-lines: 1-2
+ :language: python
+
+Only the highlighted lines, along with their preceding commas, need to be
+edited and added.
-We'll add a ``login`` view which renders a login form and processes
-the post from the login form, checking credentials.
+This allows anyone to invoke these two views.
-We'll also add a ``logout`` view to our application and provide a link
-to it. This view will clear the credentials of the logged in user and
+We are done with the changes needed to control access. The changes that
+follow will add the login and logout feature.
+
+Login, logout
+-------------
+
+Add login and logout views
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We'll add a ``login`` view which renders a login form and processes the post
+from the login form, checking credentials.
+
+We'll also add a ``logout`` view callable to our application and provide a
+link to it. This view will clear the credentials of the logged in user and
redirect back to the front page.
-We'll add a different file (for presentation convenience) to add login
-and logout views. Add a file named ``login.py`` to your application
-(in the same directory as ``views.py``) with the following content:
+Add the following import statements to the head of
+``tutorial/views.py``:
-.. literalinclude:: src/authorization/tutorial/login.py
- :linenos:
+.. literalinclude:: src/authorization/tutorial/views.py
+ :lines: 6-17
+ :emphasize-lines: 1-12
:language: python
-Note that the ``login`` view callable in the ``login.py`` file has *two* view
-configuration decorators. The order of these decorators is unimportant.
-Each just adds a different :term:`view configuration` for the ``login`` view
-callable.
-
-The first view configuration decorator configures the ``login`` view callable
-so it will be invoked when someone visits ``/login`` (when the context is a
-Wiki and the view name is ``login``). The second decorator (with context of
-``pyramid.exceptions.Forbidden``) specifies a :term:`forbidden view`. This
-configures our login view to be presented to the user when :app:`Pyramid`
-detects that a view invocation can not be authorized. Because we've
-configured a forbidden view, the ``login`` view callable will be invoked
-whenever one of our users tries to execute a view callable that they are not
-allowed to invoke as determined by the :term:`authorization policy` in use.
-In our application, for example, this means that if a user has not logged in,
-and he tries to add or edit a Wiki page, he will be shown the login form.
-Before being allowed to continue on to the add or edit form, he will have to
-provide credentials that give him permission to add or edit via this login
-form.
-
-Changing Existing Views
-~~~~~~~~~~~~~~~~~~~~~~~
-
-Then we need to change each of our ``view_page``, ``edit_page`` and
-``add_page`` views in ``views.py`` to pass a "logged in" parameter
-into its template. We'll add something like this to each view body:
-
-.. ignore-next-block
-.. code-block:: python
- :linenos:
+All the highlighted lines need to be added or edited.
- from pyramid.security import authenticated_userid
- logged_in = authenticated_userid(request)
+:meth:`~pyramid.view.forbidden_view_config` will be used to customize the
+default 403 Forbidden page. :meth:`~pyramid.security.remember` and
+:meth:`~pyramid.security.forget` help to create and expire an auth ticket
+cookie.
-We'll then change the return value of each view that has an associated
-``renderer`` to pass the resulting ``logged_in`` value to the
-template. For example:
+Now add the ``login`` and ``logout`` views at the end of the file:
-.. ignore-next-block
-.. code-block:: python
+.. literalinclude:: src/authorization/tutorial/views.py
+ :lines: 82-116
:linenos:
+ :lineno-start: 82
+ :language: python
+
+``login()`` has two decorators:
+
+- a ``@view_config`` decorator which associates it with the ``login`` route
+ and makes it visible when we visit ``/login``,
+- a ``@forbidden_view_config`` decorator which turns it into a
+ :term:`forbidden view`. ``login()`` will be invoked when a user tries to
+ execute a view callable for which they lack authorization. For example, if
+ a user has not logged in and tries to add or edit a Wiki page, they will be
+ shown the login form before being allowed to continue.
- return dict(page = context,
- content = content,
- logged_in = logged_in,
- edit_url = edit_url)
-
-Adding ``permission`` Declarations to our ``view_config`` Decorators
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-To protect each of our views with a particular permission, we need to pass a
-``permission`` argument to each of our :class:`pyramid.view.view_config`
-decorators. To do so, within ``views.py``:
-
-- We add ``permission='view'`` to the decorator attached to the
- ``view_wiki`` and ``view_page`` view functions. This makes the
- assertion that only users who possess the ``view`` permission
- against the context resource at the time of the request may
- invoke these views. We've granted
- :data:`pyramid.security.Everyone` the view permission at the
- root model via its ACL, so everyone will be able to invoke the
- ``view_wiki`` and ``view_page`` views.
-
-- We add ``permission='edit'`` to the decorator attached to the
- ``add_page`` and ``edit_page`` view functions. This makes the
- assertion that only users who possess the effective ``edit``
- permission against the context resource at the time of the
- request may invoke these views. We've granted the
- ``group:editors`` principal the ``edit`` permission at the
- root model via its ACL, so only a user whom is a member of
- the group named ``group:editors`` will able to invoke the
- ``add_page`` or ``edit_page`` views. We've likewise given
- the ``editor`` user membership to this group via the
- ``security.py`` file by mapping him to the ``group:editors``
- group in the ``GROUPS`` data structure (``GROUPS
- = {'editor':['group:editors']}``); the ``groupfinder``
- function consults the ``GROUPS`` data structure. This means
- that the ``editor`` user can add and edit pages.
-
-Adding the ``login.pt`` Template
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Add a ``login.pt`` template to your templates directory. It's
-referred to within the login view we just added to ``login.py``.
+The order of these two :term:`view configuration` decorators is unimportant.
+
+``logout()`` is decorated with a ``@view_config`` decorator which associates
+it with the ``logout`` route. It will be invoked when we visit ``/logout``.
+
+Add the ``login.pt`` Template
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Create ``tutorial/templates/login.pt`` with the following content:
.. literalinclude:: src/authorization/tutorial/templates/login.pt
- :language: xml
+ :language: html
+
+The above template is referenced in the login view that we just added in
+``views.py``.
+
+Return a ``logged_in`` flag to the renderer
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Change ``view.pt`` and ``edit.pt``
+Open ``tutorial/views.py`` again. Add a ``logged_in`` parameter to
+the return value of ``view_page()``, ``add_page()``, and ``edit_page()`` as
+follows:
+
+.. literalinclude:: src/authorization/tutorial/views.py
+ :lines: 47-48
+ :emphasize-lines: 1-2
+ :language: python
+
+.. literalinclude:: src/authorization/tutorial/views.py
+ :lines: 67-68
+ :emphasize-lines: 1-2
+ :language: python
+
+.. literalinclude:: src/authorization/tutorial/views.py
+ :lines: 78-80
+ :emphasize-lines: 2-3
+ :language: python
+
+Only the highlighted lines need to be added or edited.
+
+The :meth:`pyramid.request.Request.authenticated_userid` will be ``None`` if
+the user is not authenticated, or a userid if the user is authenticated.
+
+Add a "Logout" link when logged in
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-We'll also need to change our ``edit.pt`` and ``view.pt`` templates to
-display a "Logout" link if someone is logged in. This link will
-invoke the logout view.
+Open ``tutorial/templates/edit.pt`` and
+``tutorial/templates/view.pt`` and add the following code as
+indicated by the highlighted lines.
-To do so we'll add this to both templates within the ``<div id="right"
-class="app-welcome align-right">`` div:
+.. literalinclude:: src/authorization/tutorial/templates/edit.pt
+ :lines: 34-38
+ :emphasize-lines: 3-5
+ :language: html
-.. code-block:: xml
+The attribute ``tal:condition="logged_in"`` will make the element be included
+when ``logged_in`` is any user id. The link will invoke the logout view. The
+above element will not be included if ``logged_in`` is ``None``, such as when
+a user is not authenticated.
- <span tal:condition="logged_in">
- <a href="${request.application_url}/logout">Logout</a>
- </span>
+Reviewing our changes
+---------------------
-Seeing Our Changes To ``views.py`` and our Templates
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Our ``tutorial/__init__.py`` will look like this when we're done:
-Our ``views.py`` module will look something like this when we're done:
+.. literalinclude:: src/authorization/tutorial/__init__.py
+ :linenos:
+ :emphasize-lines: 4-5,8,18-20,22-23
+ :language: python
+
+Only the highlighted lines need to be added or edited.
+
+Our ``tutorial/models.py`` will look like this when we're done:
+
+.. literalinclude:: src/authorization/tutorial/models.py
+ :linenos:
+ :emphasize-lines: 4-7,12-13
+ :language: python
+
+Only the highlighted lines need to be added or edited.
+
+Our ``tutorial/views.py`` will look like this when we're done:
.. literalinclude:: src/authorization/tutorial/views.py
:linenos:
+ :emphasize-lines: 8,11-15,17,24,29,48,52,68,72,80,82-120
:language: python
-Our ``edit.pt`` template will look something like this when we're done:
+Only the highlighted lines need to be added or edited.
+
+Our ``tutorial/templates/edit.pt`` template will look like this when
+we're done:
.. literalinclude:: src/authorization/tutorial/templates/edit.pt
:linenos:
- :language: xml
+ :emphasize-lines: 36-38
+ :language: html
+
+Only the highlighted lines need to be added or edited.
-Our ``view.pt`` template will look something like this when we're done:
+Our ``tutorial/templates/view.pt`` template will look like this when
+we're done:
.. literalinclude:: src/authorization/tutorial/templates/view.pt
:linenos:
- :language: xml
-
-Viewing the Application in a Browser
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-We can finally examine our application in a browser. The views we'll try are
-as follows:
-
-- Visiting ``http://localhost:6543/`` in a browser invokes the ``view_wiki``
- view. This always redirects to the ``view_page`` view of the ``FrontPage``
- page resource. It is executable by any user.
-
-- Visiting ``http://localhost:6543/FrontPage/`` in a browser invokes the
- ``view_page`` view of the ``FrontPage`` Page resource. This is because
- it's the :term:`default view` (a view without a ``name``) for ``Page``
- resources. It is executable by any user.
-
-- Visiting ``http://localhost:6543/FrontPage/edit_page`` in a browser invokes
- the edit view for the ``FrontPage`` Page resource. It is executable by
- only the ``editor`` user. If a different user (or the anonymous user)
- invokes it, a login form will be displayed. Supplying the credentials with
- the username ``editor``, password ``editor`` will show the edit page form
- being displayed.
-
-- Visiting ``http://localhost:6543/add_page/SomePageName`` in a
- browser invokes the add view for a page. It is executable by only
- the ``editor`` user. If a different user (or the anonymous user)
- invokes it, a login form will be displayed. Supplying the
- credentials with the username ``editor``, password ``editor`` will
- show the edit page form being displayed.
-
-- After logging in (as a result of hitting an edit or add page and
- submitting the login form with the ``editor`` credentials), we'll see
- a Logout link in the upper right hand corner. When we click it,
- we're logged out, and redirected back to the front page.
+ :emphasize-lines: 36-38
+ :language: html
+
+Only the highlighted lines need to be added or edited.
+
+Viewing the application in a browser
+------------------------------------
+
+We can finally examine our application in a browser (See
+:ref:`wiki-start-the-application`). Launch a browser and visit each of the
+following URLs, checking that the result is as expected:
+
+- http://localhost:6543/ invokes the ``view_wiki`` view. This always
+ redirects to the ``view_page`` view of the ``FrontPage`` Page resource. It
+ is executable by any user.
+
+- http://localhost:6543/FrontPage invokes the ``view_page`` view of the
+ ``FrontPage`` Page resource. This is because it's the :term:`default view`
+ (a view without a ``name``) for ``Page`` resources. It is executable by any
+ user.
+
+- http://localhost:6543/FrontPage/edit_page invokes the edit view for the
+ FrontPage object. It is executable by only the ``editor`` user. If a
+ different user (or the anonymous user) invokes it, a login form will be
+ displayed. Supplying the credentials with the username ``editor``, password
+ ``editor`` will display the edit page form.
+
+- http://localhost:6543/add_page/SomePageName invokes the add view for a page.
+ It is executable by only the ``editor`` user. If a different user (or the
+ anonymous user) invokes it, a login form will be displayed. Supplying the
+ credentials with the username ``editor``, password ``editor`` will display
+ the edit page form.
+
+- After logging in (as a result of hitting an edit or add page and submitting
+ the login form with the ``editor`` credentials), we'll see a Logout link in
+ the upper right hand corner. When we click it, we're logged out, and
+ redirected back to the front page.