diff options
| author | Michael Merickel <github@m.merickel.org> | 2018-11-26 17:10:21 -0600 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-11-26 17:10:21 -0600 |
| commit | 587fe72fae0efda3a860d37a1ea2449a41dab622 (patch) | |
| tree | ad938e23efd1be67821ddfb710748e746c92c420 /docs/tutorials/wiki/authorization.rst | |
| parent | eea97ca673a53f8aa039a78e61833f78d5d59583 (diff) | |
| parent | 81171e861d25d394c0ccb8a6139a9b89dc4f039c (diff) | |
| download | pyramid-587fe72fae0efda3a860d37a1ea2449a41dab622.tar.gz pyramid-587fe72fae0efda3a860d37a1ea2449a41dab622.tar.bz2 pyramid-587fe72fae0efda3a860d37a1ea2449a41dab622.zip | |
Merge pull request #3421 from mmerickel/drop-py2
remove py2 from the codebase
Diffstat (limited to 'docs/tutorials/wiki/authorization.rst')
| -rw-r--r-- | docs/tutorials/wiki/authorization.rst | 460 |
1 files changed, 220 insertions, 240 deletions
diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst index b7eeb19ae..ef914cab5 100644 --- a/docs/tutorials/wiki/authorization.rst +++ b/docs/tutorials/wiki/authorization.rst @@ -1,39 +1,33 @@ .. _wiki_adding_authorization: -==================== -Adding authorization -==================== - -: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 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 +======================================= +Adding authorization and authentication +======================================= + +:app:`Pyramid` provides facilities for :term:`authentication` and :term:`authorization`. +We will 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 will change that to allow only people who are members of a *group* named ``group:editors`` to add and edit wiki pages. +We will continue to allow anyone with access to the server to view pages. + +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. We will implement the access control with the following steps: -* Add password hashing dependencies. -* 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``). +- Add password hashing dependencies. +- 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``). Then we will add the login and logout features: -* 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``). +- 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``). Access control @@ -43,14 +37,15 @@ Access control Add dependencies ~~~~~~~~~~~~~~~~ -Just like in :ref:`wiki_defining_views`, we need a new dependency. We need to add the `bcrypt <https://pypi.org/project/bcrypt/>`_ package, to our tutorial package's ``setup.py`` file by assigning this dependency to the ``requires`` parameter in the ``setup()`` function. +Just like in :ref:`wiki_defining_views`, we need a new dependency. +We need to add the `bcrypt <https://pypi.org/project/bcrypt/>`_ package to our tutorial package's ``setup.py`` file by assigning this dependency to the ``requires`` parameter in the ``setup()`` function. Open ``setup.py`` and edit it to look like the following: .. literalinclude:: src/authorization/setup.py - :linenos: - :emphasize-lines: 23 - :language: python + :linenos: + :emphasize-lines: 23 + :language: python Only the highlighted line needs to be added. @@ -58,7 +53,9 @@ Do not forget to run ``pip install -e .`` just like in :ref:`wiki-running-pip-in .. note:: - We are using the ``bcrypt`` package from PyPI to hash our passwords securely. There are other one-way hash algorithms for passwords if bcrypt is an issue on your system. Just make sure that it's an algorithm approved for storing passwords versus a generic one-way hash. + We are using the ``bcrypt`` package from PyPI to hash our passwords securely. + There are other one-way hash algorithms for passwords if bcrypt is an issue on your system. + Just make sure that it is an algorithm approved for storing passwords versus a generic one-way hash. Add users and groups @@ -67,78 +64,78 @@ Add users and groups Create a new ``tutorial/security.py`` module with the following content: .. literalinclude:: src/authorization/tutorial/security.py - :linenos: - :language: python + :linenos: + :language: python -The ``groupfinder`` function accepts a userid and a request and -returns one of these values: +The ``groupfinder`` function accepts a ``userid`` and a ``request`` +It returns one of these values: -- If ``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``. +- If ``userid`` exists in the system, it will return either a sequence of group identifiers, or an empty sequence if the user is not 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. +For example: + +- ``groupfinder('editor', request )`` returns ``['group:editor']``. +- ``groupfinder('viewer', request)`` returns ``[]``. +- ``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. There are two helper methods that will help us later to authenticate users. The first is ``hash_password`` which takes a raw password and transforms it using -bcrypt into an irreversible representation, a process known as "hashing". The -second method, ``check_password``, will allow us to compare the hashed value of the -submitted password against the hashed value of the password stored in the user's -record. If the two hashed values match, then the submitted -password is valid, and we can authenticate the user. +bcrypt into an irreversible representation, a process known as "hashing". +The second method, ``check_password``, will allow us to compare the hashed value of the submitted password against the hashed value of the password stored in the user's +record. +If the two hashed values match, then the submitted password is valid, and we can authenticate the user. -We hash passwords so that it is impossible to decrypt and use them to -authenticate in the application. If we stored passwords foolishly in clear text, -then anyone with access to the database could retrieve any password to authenticate -as any user. +We hash passwords so that it is impossible to decrypt and use them to authenticate in the application. +If we stored passwords foolishly in clear text, then anyone with access to the database could retrieve any password to authenticate as any user. In a production system, user and group data will most often be saved and come from a -database, but here we use "dummy" data to represent user and groups sources. +database. +Here we use "dummy" data to represent user and groups sources. + Add an ACL ~~~~~~~~~~ -Open ``tutorial/models.py`` and add the following import -statement near the top: +Open ``tutorial/models.py`` and add the following import statement near the top: -.. literalinclude:: src/authorization/tutorial/models.py - :lines: 4-8 - :lineno-match: - :language: python +.. literalinclude:: src/authorization/tutorial/models/__init__.py + :lines: 4-8 + :lineno-match: + :language: python Add the following lines to the ``Wiki`` class: -.. literalinclude:: src/authorization/tutorial/models.py - :lines: 9-13 - :lineno-match: - :emphasize-lines: 4-5 - :language: python - -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. - -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. - -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. +.. literalinclude:: src/authorization/tutorial/models/__init__.py + :lines: 9-13 + :lineno-match: + :emphasize-lines: 4-5 + :language: python + +We import :data:`~pyramid.security.Allow`, an action which means that +permission is allowed. +We also import :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. + +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. + +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 is only happenstance that we assigned 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. + +.. seealso:: + + See :ref:`assigning_acls` for more information about what an :term:`ACL` represents. + Add authentication and authorization policies ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -147,123 +144,113 @@ Open ``tutorial/__init__.py`` and add the highlighted import statements: .. literalinclude:: src/authorization/tutorial/__init__.py - :lines: 1-8 - :linenos: - :emphasize-lines: 3-6,8 - :language: python + :lines: 1-8 + :linenos: + :emphasize-lines: 3-6,8 + :language: python Now add those policies to the configuration: .. literalinclude:: src/authorization/tutorial/__init__.py - :lines: 18-25 - :lineno-match: - :emphasize-lines: 2-4,6-7 - :language: python + :lines: 15-25 + :lineno-match: + :emphasize-lines: 4-6,8-9 + :language: python Only the highlighted lines need to be added. -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. +We enabled an ``AuthTktAuthenticationPolicy`` which is based in an auth ticket that may be included in the request. +We also enabled an ``ACLAuthorizationPolicy`` which uses an ACL to determine the *allow* or *deny* outcome for a view. + +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 earlier. -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. Add permission declarations ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Open ``tutorial/views.py`` and add a ``permission='edit'`` parameter -to the ``@view_config`` decorators for ``add_page()`` and ``edit_page()``: -.. literalinclude:: src/authorization/tutorial/views.py - :lines: 49-51 - :emphasize-lines: 2-3 - :language: python +Open ``tutorial/views/default.py`` and add a ``permission='edit'`` parameter to the ``@view_config`` decorators for ``add_page()`` and ``edit_page()``: + +.. literalinclude:: src/authorization/tutorial/views/default.py + :lines: 49-51 + :emphasize-lines: 2-3 + :language: python -.. literalinclude:: src/authorization/tutorial/views.py - :lines: 68-70 - :emphasize-lines: 2-3 - :language: python +.. literalinclude:: src/authorization/tutorial/views/default.py + :lines: 68-70 + :emphasize-lines: 2-3 + :language: python -Only the highlighted lines, along with their preceding commas, need to be -edited and added. +Only the highlighted lines, along with their preceding commas, need to be edited and added. -The result is that only users who possess the ``edit`` permission at the time -of the request may invoke those two views. +The result is that only users who possess the ``edit`` permission at the time of the request may invoke those two views. Add a ``permission='view'`` parameter to the ``@view_config`` decorator for ``view_wiki()`` and ``view_page()`` as follows: -.. literalinclude:: src/authorization/tutorial/views.py - :lines: 23-24 - :emphasize-lines: 1-2 - :language: python +.. literalinclude:: src/authorization/tutorial/views/default.py + :lines: 23-24 + :emphasize-lines: 1-2 + :language: python -.. literalinclude:: src/authorization/tutorial/views.py - :lines: 28-29 - :emphasize-lines: 1-2 - :language: python +.. literalinclude:: src/authorization/tutorial/views/default.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. +Only the highlighted lines, along with their preceding commas, need to be edited and added. This allows anyone to invoke these two views. -We are done with the changes needed to control access. The changes that -follow will add the login and logout feature. +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 will 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 will 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. -Add the following import statements to the head of -``tutorial/views.py``: +Add the following import statements to the head of ``tutorial/views/default.py``: -.. literalinclude:: src/authorization/tutorial/views.py - :lines: 6-17 - :emphasize-lines: 1-12 - :language: python +.. literalinclude:: src/authorization/tutorial/views/default.py + :lines: 4-15 + :emphasize-lines: 2-10,12 + :language: python All the highlighted lines need to be added or edited. -: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. +: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. Now add the ``login`` and ``logout`` views at the end of the file: -.. literalinclude:: src/authorization/tutorial/views.py - :lines: 80- - :lineno-match: - :language: python +.. literalinclude:: src/authorization/tutorial/views/default.py + :lines: 78- + :lineno-match: + :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. +- 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, then they will be shown the login form before being allowed to continue. 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``. +``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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -271,134 +258,127 @@ Add the ``login.pt`` Template Create ``tutorial/templates/login.pt`` with the following content: .. literalinclude:: src/authorization/tutorial/templates/login.pt - :language: html + :language: html + +The above template is referenced in the login view that we just added in ``views.py``. -The above template is referenced in the login view that we just added in -``views.py``. Return a ``logged_in`` flag to the renderer ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Open ``tutorial/views.py`` again. Add a ``logged_in`` parameter to -the return value of ``view_page()``, ``add_page()``, and ``edit_page()`` as -follows: +Open ``tutorial/views/default.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: 46-47 - :emphasize-lines: 1-2 - :language: python +.. literalinclude:: src/authorization/tutorial/views/default.py + :lines: 45-46 + :emphasize-lines: 1-2 + :language: python -.. literalinclude:: src/authorization/tutorial/views.py - :lines: 65-66 - :emphasize-lines: 1-2 - :language: python +.. literalinclude:: src/authorization/tutorial/views/default.py + :lines: 65-66 + :emphasize-lines: 1-2 + :language: python -.. literalinclude:: src/authorization/tutorial/views.py - :lines: 76-78 - :emphasize-lines: 2-3 - :language: python +.. literalinclude:: src/authorization/tutorial/views/default.py + :lines: 77-79 + :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. +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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Open ``tutorial/templates/edit.pt`` and -``tutorial/templates/view.pt`` and add the following code as -indicated by the highlighted lines. +Open ``tutorial/templates/edit.pt`` and ``tutorial/templates/view.pt``. +Add the following code as indicated by the highlighted lines. .. literalinclude:: src/authorization/tutorial/templates/edit.pt - :lines: 35-39 - :emphasize-lines: 2-4 - :language: html + :lines: 4-8 + :emphasize-lines: 2-4 + :language: html -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 +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. + Reviewing our changes --------------------- -Our ``tutorial/__init__.py`` will look like this when we're done: +Our ``tutorial/__init__.py`` will look like this when we are done: .. literalinclude:: src/authorization/tutorial/__init__.py - :linenos: - :emphasize-lines: 4-5,8,19-21,23-24 - :language: python + :linenos: + :emphasize-lines: 3-6,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: +Our ``tutorial/models.py`` will look like this when we are done: -.. literalinclude:: src/authorization/tutorial/models.py - :linenos: - :emphasize-lines: 4-7,12-13 - :language: python +.. literalinclude:: src/authorization/tutorial/models/__init__.py + :linenos: + :emphasize-lines: 4-8,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: +Our ``tutorial/views/default.py`` will look like this when we are done: -.. literalinclude:: src/authorization/tutorial/views.py - :linenos: - :emphasize-lines: 8,11-15,17,24,29,47,51,66,70,78,80- - :language: python +.. literalinclude:: src/authorization/tutorial/views/default.py + :linenos: + :emphasize-lines: 5-12,15,21-22,27-28,45-46,50-51,65-66,70-71,78- + :language: python Only the highlighted lines need to be added or edited. -Our ``tutorial/templates/edit.pt`` template will look like this when -we're done: +Our ``tutorial/templates/edit.pt`` template will look like this when we are done: .. literalinclude:: src/authorization/tutorial/templates/edit.pt - :linenos: - :emphasize-lines: 36-38 - :language: html + :linenos: + :emphasize-lines: 5-7 + :language: html Only the highlighted lines need to be added or edited. -Our ``tutorial/templates/view.pt`` template will look like this when -we're done: +Our ``tutorial/templates/view.pt`` template will look like this when we are done: .. literalinclude:: src/authorization/tutorial/templates/view.pt - :linenos: - :emphasize-lines: 36-38 - :language: html + :linenos: + :emphasize-lines: 5-7 + :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. +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 is 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, then a login form will be displayed. + Supplying the credentials with the username ``editor`` and 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 will see a Logout link in the upper right hand corner. + When we click it, we are logged out, and redirected back to the front page. + +- To generate a not found error, visit http://localhost:6543/wakawaka which will invoke the ``notfound_view`` view provided by the cookiecutter. |
