pluggable authentication information retreiver
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Tue, 13 Oct 2009 16:00:09 +0200
changeset 3658 d8f2ec7e91fa
parent 3657 706d7bf0ae3d
child 3659 993997b4b41d
pluggable authentication information retreiver
web/views/authentication.py
--- a/web/views/authentication.py	Tue Oct 13 15:59:47 2009 +0200
+++ b/web/views/authentication.py	Tue Oct 13 16:00:09 2009 +0200
@@ -10,10 +10,50 @@
 from logilab.common.decorators import clear_cache
 
 from cubicweb import AuthenticationError, BadConnectionId
+from cubicweb.view import Component
 from cubicweb.dbapi import repo_connect, ConnectionProperties
 from cubicweb.web import ExplicitLogin, InvalidSession
 from cubicweb.web.application import AbstractAuthenticationManager
 
+class NoAuthInfo(Exception): pass
+
+
+class WebAuthInfoRetreiver(Component):
+    __registry__ = 'webauth'
+    order = None
+
+    def authentication_information(self, req):
+        """retreive authentication information from the given request, raise
+        NoAuthInfo if expected information is not found.
+        """
+        raise NotImplementedError()
+
+    def authenticated(self, req, cnx, retreiver):
+        """callback when return authentication information have opened a
+        repository connection successfully
+        """
+        pass
+
+
+class LoginPasswordRetreiver(WebAuthInfoRetreiver):
+    __regid__ = 'loginpwdauth'
+    order = 10
+
+    def __init__(self, vreg):
+        self.anoninfo = vreg.config.anonymous_user()
+
+    def authentication_information(self, req):
+        """retreive authentication information from the given request, raise
+        NoAuthInfo if expected information is not found.
+        """
+        login, password = req.get_authorization()
+        if not login:
+            # No session and no login -> try anonymous
+            login, password = self.anoninfo
+            if not login: # anonymous not authorized
+                raise NoAuthInfo()
+        return login, {'password': password}
+
 
 class RepositoryAuthenticationManager(AbstractAuthenticationManager):
     """authenticate user associated to a request and check session validity"""
@@ -22,6 +62,9 @@
         super(RepositoryAuthenticationManager, self).__init__(vreg)
         self.repo = vreg.config.repository(vreg)
         self.log_queries = vreg.config['query-log-file']
+        self.authinforetreivers = sorted(vreg['webauth'].possible_objects(vreg),
+                                    key=lambda x: x.order)
+        assert self.authinforetreivers
 
     def validate_session(self, req, session):
         """check session validity, and return eventually hijacked session
@@ -46,8 +89,7 @@
         except BadConnectionId:
             # check if a connection should be automatically restablished
             if (login is None or login == cnx.login):
-                login, password = cnx.login, cnx.password
-                cnx = self.authenticate(req, login, password)
+                cnx = self._authenticate(req, cnx.login, cnx.authinfo)
                 user = cnx.user(req)
                 # backport session's data
                 cnx.data = session.data
@@ -57,7 +99,7 @@
         req.set_connection(cnx, user)
         return cnx
 
-    def authenticate(self, req, _login=None, _password=None):
+    def authenticate(self, req):
         """authenticate user and return corresponding user object
 
         :raise ExplicitLogin: if authentication is required (no authentication
@@ -67,22 +109,26 @@
         returning a session instance instead of the user. This is expected by
         the InMemoryRepositorySessionManager.
         """
-        if _login is not None:
-            login, password = _login, _password
+        for retreiver in self.authinforetreivers:
+            try:
+                login, authinfo = retreiver.authentication_information(req)
+            except NoAuthInfo:
+                continue
+            cnx = self._authenticate(req, login, authinfo)
+            break
         else:
-            login, password = req.get_authorization()
-        if not login:
-            # No session and no login -> try anonymous
-            login, password = self.vreg.config.anonymous_user()
-            if not login: # anonymous not authorized
-                raise ExplicitLogin()
+            raise ExplicitLogin()
+        for retreiver_ in self.authinforetreivers:
+            retreiver_.authenticated(req, cnx, retreiver)
+        return cnx
+
+    def _authenticate(self, req, login, authinfo):
         # remove possibly cached cursor coming from closed connection
         clear_cache(req, 'cursor')
         cnxprops = ConnectionProperties(self.vreg.config.repo_method,
                                         close=False, log=self.log_queries)
         try:
-            cnx = repo_connect(self.repo, login, password=password,
-                               cnxprops=cnxprops)
+            cnx = repo_connect(self.repo, login, cnxprops=cnxprops, **authinfo)
         except AuthenticationError:
             req.set_message(req._('authentication failure'))
             # restore an anonymous connection if possible
@@ -90,20 +136,20 @@
             if anonlogin and anonlogin != login:
                 cnx = repo_connect(self.repo, anonlogin, password=anonpassword,
                                    cnxprops=cnxprops)
-                self._init_cnx(cnx, anonlogin, anonpassword)
+                self._init_cnx(cnx, anonlogin, {'password': anonpassword})
             else:
                 raise ExplicitLogin()
         else:
-            self._init_cnx(cnx, login, password)
+            self._init_cnx(cnx, login, authinfo)
         # associate the connection to the current request
         req.set_connection(cnx)
         return cnx
 
-    def _init_cnx(self, cnx, login, password):
+    def _init_cnx(self, cnx, login, authinfo):
         # decorate connection
         if login == self.vreg.config.anonymous_user()[0]:
             cnx.anonymous_connection = True
         cnx.vreg = self.vreg
         cnx.login = login
-        cnx.password = password
+        cnx.authinfo = authinfo