summaryrefslogtreecommitdiff
path: root/docs/tutorials/wiki/authorization.rst
diff options
context:
space:
mode:
authorMichael Merickel <michael@merickel.org>2020-01-12 14:39:43 -0600
committerMichael Merickel <michael@merickel.org>2020-01-12 14:39:43 -0600
commit3e38f884549658c41e8698a55cad0b8b3729333f (patch)
tree23ace98834c75e7a5d8beef12d827838de56c309 /docs/tutorials/wiki/authorization.rst
parent246f6ad9e6eaac613f7df2a78af3aed85bfaf79a (diff)
downloadpyramid-3e38f884549658c41e8698a55cad0b8b3729333f.tar.gz
pyramid-3e38f884549658c41e8698a55cad0b8b3729333f.tar.bz2
pyramid-3e38f884549658c41e8698a55cad0b8b3729333f.zip
update wiki authorization chapter
Diffstat (limited to 'docs/tutorials/wiki/authorization.rst')
-rw-r--r--docs/tutorials/wiki/authorization.rst253
1 files changed, 103 insertions, 150 deletions
diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst
index 2ff9deb31..1469fae44 100644
--- a/docs/tutorials/wiki/authorization.rst
+++ b/docs/tutorials/wiki/authorization.rst
@@ -18,8 +18,8 @@ We will implement the access control with the following steps:
- Add password hashing dependencies.
- Add users and groups (``security.py``, a new module).
+- Add a :term:`security policy` (``security.py``).
- 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:
@@ -43,8 +43,9 @@ We need to add the `bcrypt <https://pypi.org/project/bcrypt/>`_ package to our t
Open ``setup.py`` and edit it to look like the following:
.. literalinclude:: src/authorization/setup.py
- :linenos:
- :emphasize-lines: 23
+ :lines: 11-30
+ :lineno-match:
+ :emphasize-lines: 2
:language: python
Only the highlighted line needs to be added.
@@ -58,8 +59,8 @@ Do not forget to run ``pip install -e .`` just like in :ref:`wiki-running-pip-in
Just make sure that it is an algorithm approved for storing passwords versus a generic one-way hash.
-Add users and groups
-~~~~~~~~~~~~~~~~~~~~
+Add the security policy
+~~~~~~~~~~~~~~~~~~~~~~~
Create a new ``tutorial/security.py`` module with the following content:
@@ -67,21 +68,52 @@ Create a new ``tutorial/security.py`` module with the following content:
:linenos:
:language: python
-The ``groupfinder`` function accepts a ``userid`` and a ``request``
-It returns one of these values:
+Since we've added a new ``tutorial/security.py`` module, we need to include it.
+Open the file ``tutorial/__init__.py`` and edit the following lines:
+
+.. literalinclude:: src/authorization/tutorial/__init__.py
+ :linenos:
+ :emphasize-lines: 21
+ :language: python
+
+The security policy controls several aspects of authentication and authorization:
+
+- Identifying the current user / :term:`identity` for a ``request``.
+
+- Authorizating access to resources.
+
+- Creating payloads for remembering and forgetting users.
+
+
+Identifying logged-in users
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``MySecurityPolicy.authenticated_identity`` method inspects the ``request`` and determines if it came from an authenticated user.
+It does this by utilizing the :class:`pyramid.authentication.AuthTktCookieHelper` class which stores the :term:`identity` in a cryptographically-signed cookie.
+If a ``request`` does contain an identity then we perform a final check to determine if the user is valid in our current ``USERS`` store.
+
+
+Authorizing access to resources
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``MySecurityPolicy.permits`` method determines if the ``request`` is allowed a specific ``permission`` on the given ``context``.
+This process is done in a few steps:
-- 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``.
+- Convert the ``request`` into a list of :term:`principals <principal>` via the ``MySecurityPolicy.effective_principals`` method.
-For example:
+- Compare the list of principals to the ``context`` using the :class:`pyramid.authorization.ACLHelper`.
+ It will only allow access if it can find an :term:`ACE` that grants one of the principals the necessary permission.
-- ``groupfinder('editor', request )`` returns ``['group:editor']``.
-- ``groupfinder('viewer', request)`` returns ``[]``.
-- ``groupfinder('admin', request)`` returns ``None``.
+For our application we've defined a list of a few principals:
-We will use ``groupfinder()`` as an :term:`authentication policy` "callback" that will provide the :term:`principal` or principals for a user.
+- ``u:<userid>``
+- ``group:editor``
+- :attr:`pyramid.security.Authenticated`
+- :attr:`pyramid.security.Everyone`
-There are two helper methods that will help us later to authenticate users.
+Later, various wiki pages will grant some of these principals access to edit, or add new pages.
+
+Finally, 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
@@ -96,22 +128,52 @@ database.
Here we use "dummy" data to represent user and groups sources.
+Add new settings
+~~~~~~~~~~~~~~~~
+
+Our authentication policy is expecting a new setting, ``auth.secret``. Open
+the file ``development.ini`` and add the highlighted line below:
+
+.. literalinclude:: src/authorization/development.ini
+ :lines: 19-21
+ :emphasize-lines: 3
+ :lineno-match:
+ :language: ini
+
+Finally, best practices tell us to use a different secret in each environment, so
+open ``production.ini`` and add a different secret:
+
+.. literalinclude:: src/authorization/production.ini
+ :lines: 17-19
+ :emphasize-lines: 3
+ :lineno-match:
+ :language: ini
+
+And ``testing.ini``:
+
+.. literalinclude:: src/authorization/testing.ini
+ :lines: 17-19
+ :emphasize-lines: 3
+ :lineno-match:
+ :language: ini
+
+
Add an ACL
~~~~~~~~~~
Open ``tutorial/models/__init__.py`` and add the following import statement near the top:
.. literalinclude:: src/authorization/tutorial/models/__init__.py
- :lines: 4-8
+ :lines: 4-7
:lineno-match:
:language: python
Add the following lines to the ``Wiki`` class:
.. literalinclude:: src/authorization/tutorial/models/__init__.py
- :lines: 9-13
+ :pyobject: Wiki
:lineno-match:
- :emphasize-lines: 4-5
+ :emphasize-lines: 4-7
:language: python
We import :data:`~pyramid.security.Allow`, an action which means that
@@ -137,49 +199,20 @@ We actually need only *one* ACL for the entire system, however, because our secu
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: 3-6,8
- :language: python
-
-Now add those policies to the configuration:
-
-.. literalinclude:: src/authorization/tutorial/__init__.py
- :lines: 15-25
- :lineno-match:
- :emphasize-lines: 4-6,8-9
- :language: python
-
-Only the highlighted lines need to be added.
-
-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.
-
-
Add permission declarations
~~~~~~~~~~~~~~~~~~~~~~~~~~~
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
+ :lines: 39-41
+ :lineno-match:
:emphasize-lines: 2-3
:language: python
.. literalinclude:: src/authorization/tutorial/views/default.py
- :lines: 69-71
+ :lines: 58-60
+ :lineno-match:
:emphasize-lines: 2-3
:language: python
@@ -188,16 +221,20 @@ Only the highlighted lines, along with their preceding commas, need to be edited
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:
+``view_wiki()`` as follows:
.. literalinclude:: src/authorization/tutorial/views/default.py
- :lines: 21-22
- :emphasize-lines: 1-2
+ :lines: 12
+ :lineno-match:
+ :emphasize-lines: 1
:language: python
+And ``view_page()`` as follows:
+
.. literalinclude:: src/authorization/tutorial/views/default.py
- :lines: 27-28
- :emphasize-lines: 1-2
+ :lines: 17-19
+ :lineno-match:
+ :emphasize-lines: 2-3
:language: python
Only the highlighted lines, along with their preceding commas, need to be edited and added.
@@ -220,25 +257,15 @@ We will add a ``login`` view which renders a login form and processes the post f
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/default.py``:
+Add a new file ``tutorial/views/auth.py`` with the following contents:
-.. literalinclude:: src/authorization/tutorial/views/default.py
- :lines: 4-15
- :emphasize-lines: 2-10,12
+.. literalinclude:: src/authorization/tutorial/views/auth.py
+ :lineno-match:
: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.
-Now add the ``login`` and ``logout`` views at the end of the file:
-
-.. 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``.
@@ -263,41 +290,15 @@ Create ``tutorial/templates/login.pt`` with the following content:
The above template is referenced in the login view that we just added in ``views.py``.
-Return a ``logged_in`` flag to the renderer
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Add a "Login" and "Logout" links
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-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:
+Open ``tutorial/templates/layout.pt`` and add the following code as indicated by the highlighted lines.
-.. literalinclude:: src/authorization/tutorial/views/default.py
- :lines: 45-46
- :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/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.
-
-
-Add a "Logout" link when logged in
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-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: 4-8
- :emphasize-lines: 2-4
+.. literalinclude:: src/authorization/tutorial/templates/layout.pt
+ :lines: 34-43
+ :lineno-match:
+ :emphasize-lines: 2-9
:language: html
The attribute ``tal:condition="logged_in"`` will make the element be included when ``logged_in`` is any user id.
@@ -306,54 +307,6 @@ The above element will not be included if ``logged_in`` is ``None``, such as whe
a user is not authenticated.
-Reviewing our changes
----------------------
-
-Our ``tutorial/__init__.py`` will look like this when we are done:
-
-.. literalinclude:: src/authorization/tutorial/__init__.py
- :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/__init__.py`` will look like this when we are done:
-
-.. 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/default.py`` will look like this when we are done:
-
-.. 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 are done:
-
-.. literalinclude:: src/authorization/tutorial/templates/edit.pt
- :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 are done:
-
-.. literalinclude:: src/authorization/tutorial/templates/view.pt
- :linenos:
- :emphasize-lines: 5-7
- :language: html
-
-Only the highlighted lines need to be added or edited.
-
Viewing the application in a browser
------------------------------------