[doc/book] complete section on authentication plugins stable
authorAurelien Campeas <aurelien.campeas@logilab.fr>
Thu, 23 Sep 2010 13:04:01 +0200
branchstable
changeset 6319 20a7399ed58d
parent 6318 de6264ac7c50
child 6320 f2e925ae7122
[doc/book] complete section on authentication plugins
doc/book/en/devrepo/repo/sessions.rst
--- a/doc/book/en/devrepo/repo/sessions.rst	Thu Sep 23 10:49:05 2010 +0200
+++ b/doc/book/en/devrepo/repo/sessions.rst	Thu Sep 23 13:04:01 2010 +0200
@@ -90,10 +90,21 @@
 Sometimes CubicWeb's out-of-the-box authentication schemes (cookie and
 http) are not sufficient. Nowadays there is a plethore of such schemes
 and the framework cannot provide them all, but as the sequence above
-may show, it is extensible.
+shows, it is extensible.
 
 Two levels have to be considered when writing an authentication
-plugin: the web client the repository.
+plugin: the web client and the repository.
+
+We invented a scenario where it makes sense to have a new plugin in
+each side: some middleware will do pre-authentication and under the
+right circumstances add a new HTTP `x-foo-user` header to the query
+before it reaches the CubicWeb instance. For a concrete example of
+this, see the `apachekerberos`_ cube.
+
+.. _`apachekerberos`: http://www.cubicweb.org/project/cubicweb-apachekerberos
+
+Repository authentication plugins
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 On the repository side, it is possible to register a source
 authentifier using the following kind of code:
@@ -102,11 +113,12 @@
 
  from cubicweb.server.sources import native
 
- class FooAuthentifier(native.BaseAuthentifier):
+ class FooAuthentifier(native.LoginPasswordAuthentifier):
      """ a source authentifier plugin
-     if the password is 'foo', it's ok
+     if 'foo' in authentication information, no need to check
+     password
      """
-     auth_rql = ('Any X,P WHERE X is CWUser, X login %(login)s, X upassword P')
+     auth_rql = 'Any X WHERE X is CWUser, X login %(login)s'
 
      def authenticate(self, session, login, **kwargs):
          """return CWUser eid for the given login
@@ -114,14 +126,19 @@
          else raise `AuthenticationError`
          """
          session.debug('authentication by %s', self.__class__.__name__)
+         if 'foo' not in kwargs:
+             return super(FooAuthentifier, self).authenticate(session, login, **kwargs)
          try:
              rset = session.execute(self.auth_rql, {'login': login})
-             if rset[0][1].lower() == u'foo':
-                 return rset[0][0]
+             return rset[0][0]
          except Exception, exc:
              session.debug('authentication failure (%s)', exc)
-             pass
-         raise AuthenticationError('user password is not foo')
+         raise AuthenticationError('foo user is unknown to us')
+
+Since repository authentifiers are not appobjects, we have to register
+them through a `server_startup` hook.
+
+.. sourcecode:: python
 
  class ServerStartupHook(hook.Hook):
      """ register the foo authenticator """
@@ -132,4 +149,53 @@
          self.debug('registering foo authentifier')
          self.repo.system_source.add_authentifier(FooAuthentifier())
 
+Web authentication plugins
+~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+.. sourcecode:: python
+
+ class XFooUserRetriever(authentication.LoginPasswordRetreiver):
+     """ authenticate by the x-foo-user http header
+     or just do normal login/password authentication
+     """
+     __regid__ = 'x-foo-user'
+     order = 0
+
+     def authentication_information(self, req):
+         """retrieve authentication information from the given request, raise
+         NoAuthInfo if expected information is not found
+         """
+         self.debug('web authenticator building auth info')
+         try:
+            login = req.get_header('x-foo-user')
+            if login:
+                return login, {'foo': True}
+            else:
+                return super(XFooUserRetriever, self).authentication_information(self, req)
+         except Exception, exc:
+            self.debug('web authenticator failed (%s)', exc)
+         raise authentication.NoAuthInfo()
+
+     def authenticated(self, retriever, req, cnx, login, authinfo):
+         """callback when return authentication information have opened a
+         repository connection successfully. Take care req has no session
+         attached yet, hence req.execute isn't available.
+
+         Here we set a flag on the request to indicate that the user is
+         foo-authenticated. Can be used by a selector
+         """
+         self.debug('web authenticator running post authentication callback')
+         cnx.foo_user = authinfo.get('foo')
+
+In the `authenticated` method we add (in an admitedly slightly hackish
+way) an attribute to the connection object. This, in turn, can be used
+to build a selector dispatching on the fact that the user was
+preauthenticated or not.
+
+.. sourcecode:: python
+
+ @objectify_selector
+ def foo_authenticated(cls, req, rset=None, **kwargs):
+     if hasattr(req.cnx, 'foo_user') and req.foo_user:
+         return 1
+     return 0