================================
Pluggable-Authentication Utility
================================
The Pluggable-Authentication Utility (PAU) provides a framework for
authenticating principals and associating information with them. It uses
plugins and subscribers to get its work done.
For a pluggable-authentication utility to be used, it should be
registered as a utility providing the
`zope.authentication.interfaces.IAuthentication` interface.
Authentication
--------------
The primary job of PAU is to authenticate principals. It uses two types of
plug-ins in its work:
- Credentials Plugins
- Authenticator Plugins
Credentials plugins are responsible for extracting user credentials from a
request. A credentials plugin may in some cases issue a 'challenge' to obtain
credentials. For example, a 'session' credentials plugin reads credentials
from a session (the "extraction"). If it cannot find credentials, it will
redirect the user to a login form in order to provide them (the "challenge").
Authenticator plugins are responsible for authenticating the credentials
extracted by a credentials plugin. They are also typically able to create
principal objects for credentials they successfully authenticate.
Given a request object, the PAU returns a principal object, if it can. The PAU
does this by first iterateing through its credentials plugins to obtain a
set of credentials. If it gets credentials, it iterates through its
authenticator plugins to authenticate them.
If an authenticator succeeds in authenticating a set of credentials, the PAU
uses the authenticator to create a principal corresponding to the credentials.
The authenticator notifies subscribers if an authenticated principal is
created. Subscribers are responsible for adding data, especially groups, to
the principal. Typically, if a subscriber adds data, it should also add
corresponding interface declarations.
Simple Credentials Plugin
~~~~~~~~~~~~~~~~~~~~~~~~~
To illustrate, we'll create a simple credentials plugin::
>>> from zope import interface
>>> from zope.app.authentication import interfaces
>>> class MyCredentialsPlugin(object):
...
... interface.implements(interfaces.ICredentialsPlugin)
...
... def extractCredentials(self, request):
... return request.get('credentials')
...
... def challenge(self, request):
... pass # challenge is a no-op for this plugin
...
... def logout(self, request):
... pass # logout is a no-op for this plugin
As a plugin, MyCredentialsPlugin needs to be registered as a named utility::
>>> myCredentialsPlugin = MyCredentialsPlugin()
>>> provideUtility(myCredentialsPlugin, name='My Credentials Plugin')
Simple Authenticator Plugin
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Next we'll create a simple authenticator plugin. For our plugin, we'll need
an implementation of IPrincipalInfo::
>>> class PrincipalInfo(object):
...
... interface.implements(interfaces.IPrincipalInfo)
...
... def __init__(self, id, title, description):
... self.id = id
... self.title = title
... self.description = description
...
... def __repr__(self):
... return 'PrincipalInfo(%r)' % self.id
Our authenticator uses this type when it creates a principal info::
>>> class MyAuthenticatorPlugin(object):
...
... interface.implements(interfaces.IAuthenticatorPlugin)
...
... def authenticateCredentials(self, credentials):
... if credentials == 'secretcode':
... return PrincipalInfo('bob', 'Bob', '')
...
... def principalInfo(self, id):
... pass # plugin not currently supporting search
As with the credentials plugin, the authenticator plugin must be registered
as a named utility::
>>> myAuthenticatorPlugin = MyAuthenticatorPlugin()
>>> provideUtility(myAuthenticatorPlugin, name='My Authenticator Plugin')
Principal Factories
~~~~~~~~~~~~~~~~~~~
While authenticator plugins provide principal info, they are not responsible
for creating principals. This function is performed by factory adapters. For
these tests we'll borrow some factories from the principal folder::
>>> from zope.app.authentication import principalfolder
>>> provideAdapter(principalfolder.AuthenticatedPrincipalFactory)
>>> provideAdapter(principalfolder.FoundPrincipalFactory)
For more information on these factories, see their docstrings.
Configuring a PAU
~~~~~~~~~~~~~~~~~
Finally, we'll create the PAU itself::
>>> from zope.app import authentication
>>> pau = authentication.PluggableAuthentication('xyz_')
and configure it with the two plugins::
>>> pau.credentialsPlugins = ('My Credentials Plugin', )
>>> pau.authenticatorPlugins = ('My Authenticator Plugin', )
Using the PAU to Authenticate
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We can now use the PAU to authenticate a sample request::
>>> from zope.publisher.browser import TestRequest
>>> print pau.authenticate(TestRequest())
None
In this case, we cannot authenticate an empty request. In the same way, we
will not be able to authenticate a request with the wrong credentials::
>>> print pau.authenticate(TestRequest(credentials='let me in!'))
None
However, if we provide the proper credentials::
>>> request = TestRequest(credentials='secretcode')
>>> principal = pau.authenticate(request)
>>> principal
Principal('xyz_bob')
we get an authenticated principal.
Authenticated Principal Creates Events
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We can verify that the appropriate event was published::
>>> [event] = getEvents(interfaces.IAuthenticatedPrincipalCreated)
>>> event.principal is principal
True
>>> event.info
PrincipalInfo('bob')
>>> event.request is request
True
The info object has the id, title, and description of the principal. The info
object is also generated by the authenticator plugin, so the plugin may
itself have provided additional information on the info object::
>>> event.info.title
'Bob'
>>> event.info.id # does not include pau prefix
'bob'
>>> event.info.description
''
It is also decorated with two other attributes, credentialsPlugin and
authenticatorPlugin: these are the plugins used to extract credentials for and
authenticate this principal. These attributes can be useful for subscribers
that want to react to the plugins used. For instance, subscribers can
determine that a given credential plugin does or does not support logout, and
provide information usable to show or hide logout user interface::
>>> event.info.credentialsPlugin is myCredentialsPlugin
True
>>> event.info.authenticatorPlugin is myAuthenticatorPlugin
True
Normally, we provide subscribers to these events that add additional
information to the principal. For example, we'll add one that sets
the title::
>>> def add_info(event):
... event.principal.title = event.info.title
>>> provideHandler(add_info, [interfaces.IAuthenticatedPrincipalCreated])
Now, if we authenticate a principal, its title is set::
>>> principal = pau.authenticate(request)
>>> principal.title
'Bob'
Multiple Authenticator Plugins
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The PAU works with multiple authenticator plugins. It uses each plugin, in the
order specified in the PAU's authenticatorPlugins attribute, to authenticate
a set of credentials.
To illustrate, we'll create another authenticator::
>>> class MyAuthenticatorPlugin2(MyAuthenticatorPlugin):
...
... def authenticateCredentials(self, credentials):
... if credentials == 'secretcode':
... return PrincipalInfo('black', 'Black Spy', '')
... elif credentials == 'hiddenkey':
... return PrincipalInfo('white', 'White Spy', '')
>>> provideUtility(MyAuthenticatorPlugin2(), name='My Authenticator Plugin 2')
If we put it before the original authenticator::
>>> pau.authenticatorPlugins = (
... 'My Authenticator Plugin 2',
...