PageRenderTime 24ms CodeModel.GetById 18ms app.highlight 4ms RepoModel.GetById 1ms app.codeStats 0ms

/authorization.rst

https://bitbucket.org/blaflamme/pylonswiking
ReStructuredText | 280 lines | 215 code | 65 blank | 0 comment | 0 complexity | 703019131ff19d9e3037bd7322dc9168 MD5 | raw file
  1.. _wiki2_adding_authorization:
  2
  3====================
  4Adding Authorization
  5====================
  6
  7Our application currently allows anyone with access to the server to
  8view, edit, and add pages to our wiki.  For purposes of demonstration
  9we'll change our application to allow only people whom possess a
 10specific username (`editor`) to add and edit wiki pages but we'll
 11continue allowing anyone with access to the server to view pages.
 12:mod:`repoze.bfg` provides facilities for *authorization* and
 13*authentication*.  We'll make use of both features to provide security
 14to our application.
 15
 16The source code for this tutorial stage can be browsed at
 17`docs.repoze.org
 18<http://docs.repoze.org/bfgwiki2-1.3/authorization>`_.
 19
 20Adding A Root Factory
 21---------------------
 22
 23We're going to start to use a custom :term:`root factory` within our
 24``run.py`` file.  The objects generated by the root factory will be
 25used as the :term:`context` of each request to our application.  In
 26order for :mod:`repoze.bfg` declarative security to work properly, the
 27context object generated during a request must be decorated with
 28security declarations; when we begin to use a custom root factory to
 29generate our contexts, we can begin to make use of the declarative
 30security features of :mod:`repoze.bfg`.
 31
 32Let's modify our ``run.py``, passing in a :term:`root factory` to our
 33:term:`Configurator` constructor.  We'll point it at a new class we
 34create inside our ``models.py`` file.  Add the following statements to
 35your ``models.py`` file:
 36
 37.. code-block:: python
 38
 39   from repoze.bfg.security import Allow
 40   from repoze.bfg.security import Everyone
 41
 42   class RootFactory(object):
 43       __acl__ = [ (Allow, Everyone, 'view'), 
 44                   (Allow, 'group:editors', 'edit') ]
 45       def __init__(self, request):
 46           self.__dict__.update(request.matchdict)
 47
 48The ``RootFactory`` class we've just added will be used by
 49:mod:`repoze.bfg` to construct a ``context`` object.  The context is
 50attached to the request object passed to our view callables as the
 51``context`` attribute.
 52
 53All of our context objects will possess an ``__acl__`` attribute that
 54allows :data:`repoze.bfg.security.Everyone` (a special principal) to
 55view all pages, while allowing only a :term:`principal` named
 56``group:editors`` to edit and add pages.  The ``__acl__`` attribute
 57attached to a context is interpreted specially by :mod:`repoze.bfg` as
 58an access control list during view callable execution.  See
 59:ref:`assigning_acls` for more information about what an :term:`ACL`
 60represents.
 61
 62.. note: Although we don't use the functionality here, the ``factory``
 63   used to create route contexts may differ per-route as opposed to
 64   globally.  See the ``factory`` attribute in
 65   :ref:`route_zcml_directive` for more info.
 66
 67We'll pass the ``RootFactory`` we created in the step above in as the
 68``root_factory`` argument to a :term:`Configurator`.  When we're done,
 69your application's ``run.py`` will look like this.
 70
 71.. literalinclude:: src/authorization/tutorial/run.py
 72   :linenos:
 73   :language: python
 74
 75Configuring a ``repoze.bfg`` Authorization Policy
 76-------------------------------------------------
 77
 78For any :mod:`repoze.bfg` application to perform authorization, we
 79need to add a ``security.py`` module and we'll need to change our
 80``configure.zcml`` file to add an :term:`authentication policy` and an
 81:term:`authorization policy`.
 82
 83Changing ``configure.zcml``
 84~~~~~~~~~~~~~~~~~~~~~~~~~~~
 85
 86We'll change our ``configure.zcml`` file to enable an
 87``AuthTktAuthenticationPolicy`` and an ``ACLAuthorizationPolicy`` to
 88enable declarative security checking.  We'll also change
 89``configure.zcml`` to add a view stanza which points at our ``login``
 90:term:`view callable`, also known as a :term:`forbidden view`.  This
 91configures our newly created login view to show up when
 92:mod:`repoze.bfg` detects that a view invocation can not be
 93authorized.  Also, we'll add ``view_permission`` attributes with the
 94value ``edit`` to the ``edit_page`` and ``add_page`` route
 95declarations.  This indicates that the view callables which these
 96routes reference cannot be invoked without the authenticated user
 97possessing the ``edit`` permission with respect to the current
 98context.
 99
100This makes the assertion that only users who possess the effective
101``edit`` permission at the time of the request may invoke those two
102views.  We've granted the ``group:editors`` principal the ``edit``
103permission at the root model via its ACL, so only the a user whom is a
104member of the group named ``group:editors`` will able to invoke the
105views associated with the ``add_page`` or ``edit_page`` routes.  
106
107When you're done, your ``configure.zcml`` will look like so
108
109.. literalinclude:: src/authorization/tutorial/configure.zcml
110   :linenos:
111   :language: xml
112
113Note that the ``authtktauthenticationpolicy`` tag has two attributes:
114``secret`` and ``callback``.  ``secret`` is a string representing an
115encryption key used by the "authentication ticket" machinery
116represented by this policy: it is required.  The ``callback`` is a
117string, representing a :term:`dotted Python name`, which points at the
118``groupfinder`` function in the current directory's ``security.py``
119file.  We haven't added that module yet, but we're about to.
120
121Adding ``security.py``
122~~~~~~~~~~~~~~~~~~~~~~
123
124Add a ``security.py`` module within your package (in the same
125directory as "run.py", "views.py", etc) with the following content:
126
127.. literalinclude:: src/authorization/tutorial/security.py
128   :linenos:
129   :language: python
130
131The groupfinder defined here is an :term:`authentication policy`
132"callback"; it is a callable that accepts a userid and a request.  If
133the userid exists in the system, the callback will return a sequence
134of group identifiers (or an empty sequence if the user isn't a member
135of any groups).  If the userid *does not* exist in the system, the
136callback will return ``None``.  In a production system, user and group
137data will most often come from a database, but here we use "dummy"
138data to represent user and groups sources. Note that the ``editor``
139user is a member of the ``group:editors`` group in our dummy group
140data (the ``GROUPS`` data structure).
141
142We've given the ``editor`` user membership to the ``group:editors`` by
143mapping him to this group in the ``GROUPS`` data structure (``GROUPS =
144{'editor':['group:editors']}``).  Since the ``groupfinder`` function
145consults the ``GROUPS`` data structure, this will mean that, as a
146result of the ACL attached to the root returned by the root factory,
147and the permission associated with the ``add_page`` and ``edit_page``
148views, the ``editor`` user should be able to add and edit pages.
149
150Adding Login and Logout Views
151~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
152
153We'll add a ``login`` view callable which renders a login form and
154processes the post from the login form, checking credentials.
155
156We'll also add a ``logout`` view callable to our application and
157provide a link to it.  This view will clear the credentials of the
158logged in user and redirect back to the front page.
159
160We'll add a different file (for presentation convenience) to add login
161and logout view callables.  Add a file named ``login.py`` to your
162application (in the same directory as ``views.py``) with the following
163content:
164
165.. literalinclude:: src/authorization/tutorial/login.py
166   :linenos:
167   :language: python
168
169Changing Existing Views
170~~~~~~~~~~~~~~~~~~~~~~~
171
172Then we need to change each of our ``view_page``, ``edit_page`` and
173``add_page`` views in ``views.py`` to pass a "logged in" parameter to
174its template.  We'll add something like this to each view body:
175
176.. ignore-next-block
177.. code-block:: python
178   :linenos:
179
180   from repoze.bfg.security import authenticated_userid
181   logged_in = authenticated_userid(request)
182
183We'll then change the return value of these views to pass the
184`resulting `logged_in`` value to the template, e.g.:
185
186.. ignore-next-block
187.. code-block:: python
188   :linenos:
189
190   return dict(page = context,
191               content = content,
192               logged_in = logged_in,
193               edit_url = edit_url)
194
195Adding the ``login.pt`` Template
196~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
197
198Add a ``login.pt`` template to your templates directory.  It's
199referred to within the login view we just added to ``login.py``.
200
201.. literalinclude:: src/authorization/tutorial/templates/login.pt
202   :linenos:
203   :language: xml
204
205Change ``view.pt`` and ``edit.pt``
206~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
207
208We'll also need to change our ``edit.pt`` and ``view.pt`` templates to
209display a "Logout" link if someone is logged in.  This link will
210invoke the logout view.
211
212To do so we'll add this to both templates within the ``<div
213class="main_content">`` div:
214
215.. code-block:: xml
216   :linenos:
217
218   <span tal:condition="logged_in">
219      <a href="${request.application_url}/logout">Logout</a>
220   </span>
221
222Viewing the Application in a Browser
223------------------------------------
224
225We can finally examine our application in a browser.  The views we'll
226try are as follows:
227
228- Visiting ``http://localhost:6543/`` in a browser invokes the
229  ``view_wiki`` view.  This always redirects to the ``view_page`` view
230  of the FrontPage page object.  It is executable by any user.
231
232- Visiting ``http://localhost:6543/FrontPage`` in a browser invokes
233  the ``view_page`` view of the FrontPage page object.
234
235- Visiting ``http://localhost:6543/FrontPage/edit_page`` in a browser
236  invokes the edit view for the FrontPage object.  It is executable by
237  only the ``editor`` user.  If a different user (or the anonymous
238  user) invokes it, a login form will be displayed.  Supplying the
239  credentials with the username ``editor``, password ``editor`` will
240  display the edit page form.
241
242- Visiting ``http://localhost:6543/add_page/SomePageName`` in a
243  browser invokes the add view for a page.  It is executable by only
244  the ``editor`` user.  If a different user (or the anonymous user)
245  invokes it, a login form will be displayed.  Supplying the
246  credentials with the username ``editor``, password ``editor`` will
247  display the edit page form.
248
249Seeing Our Changes To ``views.py`` and our Templates
250----------------------------------------------------
251
252Our ``views.py`` module will look something like this when we're done:
253
254.. literalinclude:: src/authorization/tutorial/views.py
255   :linenos:
256   :language: python
257
258Our ``edit.pt`` template will look something like this when we're done:
259
260.. literalinclude:: src/authorization/tutorial/templates/edit.pt
261   :linenos:
262   :language: xml
263
264Our ``view.pt`` template will look something like this when we're done:
265
266.. literalinclude:: src/authorization/tutorial/templates/view.pt
267   :linenos:
268   :language: xml
269
270Revisiting the Application
271---------------------------
272
273When we revisit the application in a browser, and log in (as a result
274of hitting an edit or add page and submitting the login form with the
275``editor`` credentials), we'll see a Logout link in the upper right
276hand corner.  When we click it, we're logged out, and redirected back
277to the front page.
278
279
280