/authorization.rst
https://bitbucket.org/blaflamme/pylonswiking · ReStructuredText · 280 lines · 215 code · 65 blank · 0 comment · 0 complexity · 703019131ff19d9e3037bd7322dc9168 MD5 · raw file
- .. _wiki2_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 only people whom possess a
- specific username (`editor`) to add and edit wiki pages but we'll
- continue allowing anyone with access to the server to view pages.
- :mod:`repoze.bfg` provides facilities for *authorization* and
- *authentication*. We'll make use of both features to provide security
- to our application.
- The source code for this tutorial stage can be browsed at
- `docs.repoze.org
- <http://docs.repoze.org/bfgwiki2-1.3/authorization>`_.
- Adding A Root Factory
- ---------------------
- We're going to start to use a custom :term:`root factory` within our
- ``run.py`` file. The objects generated by the root factory will be
- used as the :term:`context` of each request to our application. In
- order for :mod:`repoze.bfg` declarative security to work properly, the
- context object generated during a request must be decorated with
- security declarations; when we begin to use a custom root factory to
- generate our contexts, we can begin to make use of the declarative
- security features of :mod:`repoze.bfg`.
- Let's modify our ``run.py``, passing in a :term:`root factory` to our
- :term:`Configurator` constructor. We'll point it at a new class we
- create inside our ``models.py`` file. Add the following statements to
- your ``models.py`` file:
- .. code-block:: python
- from repoze.bfg.security import Allow
- from repoze.bfg.security import Everyone
- class RootFactory(object):
- __acl__ = [ (Allow, Everyone, 'view'),
- (Allow, 'group:editors', 'edit') ]
- def __init__(self, request):
- self.__dict__.update(request.matchdict)
- The ``RootFactory`` class we've just added will be used by
- :mod:`repoze.bfg` to construct a ``context`` object. The context is
- attached to the request object passed to our view callables as the
- ``context`` attribute.
- All of our context objects will possess an ``__acl__`` attribute that
- allows :data:`repoze.bfg.security.Everyone` (a special principal) to
- view all pages, while allowing only a :term:`principal` named
- ``group:editors`` to edit and add pages. The ``__acl__`` attribute
- attached to a context is interpreted specially by :mod:`repoze.bfg` as
- an access control list during view callable execution. See
- :ref:`assigning_acls` for more information about what an :term:`ACL`
- represents.
- .. note: Although we don't use the functionality here, the ``factory``
- used to create route contexts may differ per-route as opposed to
- globally. See the ``factory`` attribute in
- :ref:`route_zcml_directive` for more info.
- We'll pass the ``RootFactory`` we created in the step above in as the
- ``root_factory`` argument to a :term:`Configurator`. When we're done,
- your application's ``run.py`` will look like this.
- .. literalinclude:: src/authorization/tutorial/run.py
- :linenos:
- :language: python
- Configuring a ``repoze.bfg`` Authorization Policy
- -------------------------------------------------
- For any :mod:`repoze.bfg` application to perform authorization, we
- need to add a ``security.py`` module and we'll need to change our
- ``configure.zcml`` file to add an :term:`authentication policy` and an
- :term:`authorization policy`.
- Changing ``configure.zcml``
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~
- We'll change our ``configure.zcml`` file to enable an
- ``AuthTktAuthenticationPolicy`` and an ``ACLAuthorizationPolicy`` to
- enable declarative security checking. We'll also change
- ``configure.zcml`` to add a view stanza which points at our ``login``
- :term:`view callable`, also known as a :term:`forbidden view`. This
- configures our newly created login view to show up when
- :mod:`repoze.bfg` detects that a view invocation can not be
- authorized. Also, we'll add ``view_permission`` attributes with the
- value ``edit`` to the ``edit_page`` and ``add_page`` route
- declarations. This indicates that the view callables which these
- routes reference cannot be invoked without the authenticated user
- possessing the ``edit`` permission with respect to the current
- context.
- This makes the assertion that only users who possess the effective
- ``edit`` permission at the time of the request may invoke those two
- views. We've granted the ``group:editors`` principal the ``edit``
- permission at the root model via its ACL, so only the a user whom is a
- member of the group named ``group:editors`` will able to invoke the
- views associated with the ``add_page`` or ``edit_page`` routes.
- When you're done, your ``configure.zcml`` will look like so
- .. literalinclude:: src/authorization/tutorial/configure.zcml
- :linenos:
- :language: xml
- Note that the ``authtktauthenticationpolicy`` tag has two attributes:
- ``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
- string, representing a :term:`dotted Python name`, which points at the
- ``groupfinder`` function in the current directory's ``security.py``
- file. We haven't added that module yet, but we're about to.
- Adding ``security.py``
- ~~~~~~~~~~~~~~~~~~~~~~
- Add a ``security.py`` module within your package (in the same
- directory as "run.py", "views.py", etc) with the following content:
- .. literalinclude:: src/authorization/tutorial/security.py
- :linenos:
- :language: python
- The groupfinder 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).
- We've given the ``editor`` user membership to the ``group:editors`` by
- mapping him to this group in the ``GROUPS`` data structure (``GROUPS =
- {'editor':['group:editors']}``). Since the ``groupfinder`` function
- consults the ``GROUPS`` data structure, this will mean that, as a
- result of the ACL attached to the root returned by the root factory,
- and the permission associated with the ``add_page`` and ``edit_page``
- views, the ``editor`` user should be able to add and edit pages.
- Adding Login and Logout Views
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- We'll add a ``login`` view callable 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 view callables. Add a file named ``login.py`` to your
- application (in the same directory as ``views.py``) with the following
- content:
- .. literalinclude:: src/authorization/tutorial/login.py
- :linenos:
- :language: python
- 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 to
- its template. We'll add something like this to each view body:
- .. ignore-next-block
- .. code-block:: python
- :linenos:
- from repoze.bfg.security import authenticated_userid
- logged_in = authenticated_userid(request)
- We'll then change the return value of these views to pass the
- `resulting `logged_in`` value to the template, e.g.:
- .. ignore-next-block
- .. code-block:: python
- :linenos:
- return dict(page = context,
- content = content,
- logged_in = logged_in,
- edit_url = edit_url)
- 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``.
- .. literalinclude:: src/authorization/tutorial/templates/login.pt
- :linenos:
- :language: xml
- Change ``view.pt`` and ``edit.pt``
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- 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.
- To do so we'll add this to both templates within the ``<div
- class="main_content">`` div:
- .. code-block:: xml
- :linenos:
- <span tal:condition="logged_in">
- <a href="${request.application_url}/logout">Logout</a>
- </span>
- 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 object. It is executable by any user.
- - Visiting ``http://localhost:6543/FrontPage`` in a browser invokes
- the ``view_page`` view of the FrontPage page object.
- - Visiting ``http://localhost:6543/FrontPage/edit_page`` in a browser
- 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.
- - 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
- display the edit page form.
- Seeing Our Changes To ``views.py`` and our Templates
- ----------------------------------------------------
- Our ``views.py`` module will look something like this when we're done:
- .. literalinclude:: src/authorization/tutorial/views.py
- :linenos:
- :language: python
- Our ``edit.pt`` template will look something like this when we're done:
- .. literalinclude:: src/authorization/tutorial/templates/edit.pt
- :linenos:
- :language: xml
- Our ``view.pt`` template will look something like this when we're done:
- .. literalinclude:: src/authorization/tutorial/templates/view.pt
- :linenos:
- :language: xml
- Revisiting the Application
- ---------------------------
- When we revisit the application in a browser, and log 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.