# HG changeset patch # User Aurelien Campeas # Date 1285239841 -7200 # Node ID 20a7399ed58d35ee519d0d267082b6e9663b8f87 # Parent de6264ac7c50b992d743109bf0b75c901a5088d2 [doc/book] complete section on authentication plugins diff -r de6264ac7c50 -r 20a7399ed58d 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