diff options
Diffstat (limited to 'docs/tutorials/wiki/authorization.rst')
| -rw-r--r-- | docs/tutorials/wiki/authorization.rst | 261 |
1 files changed, 106 insertions, 155 deletions
diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst index 2ff9deb31..995dfa729 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's :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. -- 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: +Authorizing access to resources +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- ``groupfinder('editor', request )`` returns ``['group:editor']``. -- ``groupfinder('viewer', request)`` returns ``[]``. -- ``groupfinder('admin', request)`` returns ``None``. +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: -We will use ``groupfinder()`` as an :term:`authentication policy` "callback" that will provide the :term:`principal` or principals for a user. +- Convert the ``request`` into a list of :term:`principals <principal>` via the ``MySecurityPolicy.effective_principals`` method. -There are two helper methods that will help us later to authenticate users. +- 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. + +For our application we've defined a list of a few principals: + +- ``u:<userid>`` +- ``group:editor`` +- :attr:`pyramid.security.Authenticated` +- :attr:`pyramid.security.Everyone` + +Various wiki pages will grant some of these principals access to edit existing or add new pages. + +Finally there are two helper methods that will help us 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 + +Best practices tell us to use a different secret in each environment. +Open ``production.ini`` and add a different secret: + +.. literalinclude:: src/authorization/production.ini + :lines: 17-19 + :emphasize-lines: 3 + :lineno-match: + :language: ini + +Edit ``testing.ini`` to add its unique secret: + +.. 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,72 +199,45 @@ 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()``: +Open ``tutorial/views/default.py``. +Add a ``permission='view'`` parameter to the ``@view_config`` decorators for ``view_wiki()`` and ``view_page()`` as follows: .. literalinclude:: src/authorization/tutorial/views/default.py - :lines: 49-51 - :emphasize-lines: 2-3 + :lines: 12 + :lineno-match: + :emphasize-lines: 1 :language: python .. literalinclude:: src/authorization/tutorial/views/default.py - :lines: 69-71 + :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. -The result is that only users who possess the ``edit`` permission at the time of the request may invoke those two views. +This allows anyone to invoke these two views. -Add a ``permission='view'`` parameter to the ``@view_config`` decorator for -``view_wiki()`` and ``view_page()`` as follows: +Next add a ``permission='edit'`` parameter to the ``@view_config`` decorators for ``add_page()`` and ``edit_page()``: .. literalinclude:: src/authorization/tutorial/views/default.py - :lines: 21-22 - :emphasize-lines: 1-2 + :lines: 39-41 + :lineno-match: + :emphasize-lines: 2-3 :language: python .. literalinclude:: src/authorization/tutorial/views/default.py - :lines: 27-28 - :emphasize-lines: 1-2 + :lines: 58-60 + :lineno-match: + :emphasize-lines: 2-3 :language: python Only the highlighted lines, along with their preceding commas, need to be edited and added. -This allows anyone to invoke these two views. +The result is that only users who possess the ``edit`` permission at the time of the request may invoke those two views. We are done with the changes needed to control access. The changes that follow will add the login and logout feature. @@ -220,25 +255,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 +288,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 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -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: +Add "Login" and "Logout" links +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. 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 +Open ``tutorial/templates/layout.pt`` and add the following code as indicated by the highlighted lines. -.. 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 +305,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 ------------------------------------ |
