diff options
Diffstat (limited to 'docs/tutorials/wiki/authorization.rst')
| -rw-r--r-- | docs/tutorials/wiki/authorization.rst | 253 |
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 ------------------------------------ |
