Authentication with Grok
This How-to applies to:
1.0a1, 0.14, 0.13
This How-to is intended for:
Developer, Advanced Developer
Note that the situation is currently rather low-level for Grok and that this document is rather coarse. Contributions to the document and to the authentication situation (in the form of helpful libraries) would be very welcome!
First we're going to set up an application with custom authentication:
import grok
from zope.app.authentication.authentication import PluggableAuthentication
from zope.app.security.interfaces import IAuthentication
class MyApplication(grok.Application, grok.Model):
grok.local_utility(
PluggableAuthentication, provides=IAuthentication,
setup=setup_authentication,
)
When the application is installed, a local utility will be automatically installed into it. When this local utility is installed, the setup_authentication function will be called to further configure it. Let's implement that now:
def setup_authentication(pau):
"""Set up plugguble authentication utility.
Sets up an IAuthenticatorPlugin and
ICredentialsPlugin (for the authentication mechanism)
"""
pau.credentialsPlugins = ['credentials']
pau.authenticatorPlugins = ['users']
Session-based login
The pluggable authentication system needs at least one credentials plugin, which is responsible for extracting credentials from the user's request, and one authenticator plugin, which authenticates the credentials against an actual user (principal).
Here we've configured the pluggable authentication utility to look up an ICredentialsPlugin utility with the name credentials and a IAuthenticatorPlugin with the name users.
We need to supply these utilities next. In our example we're both going to make them global utilities so they need no special setup in the MyApplication object above. If you need them to store data or configuration information in the ZODB however you should create them as a local utility (and also probably provide a user interface for them). This is left as an exercise to the reader.
Our credentials plugin will use the persistent user-specific session to retrieve credentials information - it's like cookie-based login:
from zope.app.authentication.session import SessionCredentialsPlugin
from zope.app.authentication.interfaces import ICredentialsPlugin
class MySessionCredentialsPlugin(grok.GlobalUtility, SessionCredentialsPlugin):
grok.provides(ICredentialsPlugin)
grok.name('credentials')
loginpagename = 'login'
loginfield = 'form.login'
passwordfield = 'form.password'
Session-based login needs to know about a special login page where the user fills their username and password in a form, so that it can retrieve the information to store in a session. We need to supply it with the name of the form (login) and the name of the form fields.
Let's set up this login page next, using the grok.Form mechanism:
from zope.interface import Interface
from zope import schema
class ILoginForm(Interface):
login = schema.BytesLine(title=u'Username', required=True)
password = schema.Password(title=u'Password', required=True)
class Login(grok.Form):
grok.context(Interface)
grok.require('zope.Public')
form_fields = grok.Fields(ILoginForm)
@grok.action('login')
def handle_login(self, **data):
self.redirect(self.request.form.get('camefrom', ''))
The session credentials plugin will automatically redirect the user to this login form when the credentials cannot be found in the session. It's available on all objects as it's registered for Interface. When the user fills in the credentials they will appear as form.login and form.password in the request where the credentials plugin can find them and store them in the session.
After submitting the form successfully the user is immediately redirected to the URL in camefrom. This camefrom variable is automatically added to request when the login form is rendered and is the original page the user tried to go to when they were redirected to this one (because a login was required first). In the login.pt template we need to make sure we retrieve it so that it is submitted along with the rest of the form. Let's therefore look at a bit of the login.pt template:
...code to render the formlib form...
<input tal:condition="request/camefrom | nothing" type="hidden"
name="camefrom" tal:attributes="value request/form/camefrom | nothing" />
It's also nice to have a logout page:
from zope.app.security.interfaces import (IAuthentication,
IUnauthenticatedPrincipal,
ILogout)
class Logout(grok.View):
grok.context(Interface)
grok.require('zope.Public')
def update(self):
if not IUnauthenticatedPrincipal.providedBy(self.request.principal):
auth = component.getUtility(IAuthentication)
ILogout(auth).logout(self.request)
This page logs out the user if it's an authenticated user (principal).
Authentication
That's what is needed for retrieving user credentials. Let's now look at how we can authenticate the user next. For this we need to set up an IAuthenticatorPlugin with the name users:
from zope.app.authentication.interfaces import IAuthenticatorPlugin
class UserAuthenticatorPlugin(grok.GlobalUtility):
grok.provides(IAuthenticatorPlugin)
grok.name('users')
def authenticateCredentials(self, credentials):
if not isinstance(credentials, dict):
return None
if not ('login' in credentials and 'password' in credentials):
return None
account = self.getAccount(credentials['login'])
if account is None:
return None
if not account.checkPassword(credentials['password']):
return None
return PrincipalInfo(id=account.name,
title=account.name,
description=account.name)
def principalInfo(self, id):
account = self.getAccount(id)
if account is None:
return None
return PrincipalInfo(id=account.name,
title=account.name,
description=account.name)
def getAccount(self, login):
... look up the account object and return it ...
What you need to do is implement the getAccount method to return an instance of an account object. This object should provide a name attribute which is the login name under which the account is used, and a checkPassword method which can be used to check the password. If no such account can be found, None must be returned. In getAccount you should consult the proper database, such as the ZODB, or an LDAP database, or a relational database, so that the proper account object can be retrieved or constructed.
The structure of this particular authenticator plugin is just one example of course - you can rewrite it to suit your particular user database system. You may for instance want to separate out checkPassword from the account object.
There are a few bits and pieces still needed, such as the PrincipalInfo class referred to above:
from zope.app.authentication.interfaces import IPrincipalInfo
class PrincipalInfo(object):
grok.implements(IPrincipalInfo)
def __init__(self, id, title, description):
self.id = id
self.title = title
self.description = description
self.credentialsPlugin = None
self.authenticatorPlugin = None
We'll also give an example of an account object with password management facilities (encrypting the password):
from zope import component
from zope.app.authentication.interfaces import IPasswordManager
class Account(grok.Model):
def __init__(self, name, password):
self.name = name
self.setPassword(password)
def setPassword(self, password):
passwordmanager = component.getUtility(IPasswordManager, 'SHA1')
self.password = passwordmanager.encodePassword(password)
def checkPassword(self, password):
passwordmanager = component.getUtility(IPasswordManager, 'SHA1')
return passwordmanager.checkPassword(self.password, password)
Instead of a grok.Model which allows its storage in the ZODB (such as in a container), you could construct this object on the fly when needed, or you could use an ORM mapper.


local_utility not working
class Osa(grok.Application, grok.Container):
grok.local_utility(
PluggableAuthentication, provides=IAuthentication,
setup=setup_authentication,
)
However setup_authentication is never called (according to pdb and debugging code)?!