web/views/authentication.py
changeset 3658 d8f2ec7e91fa
parent 3647 2941f4a0aab9
child 4252 6c4f109c2b03
equal deleted inserted replaced
3657:706d7bf0ae3d 3658:d8f2ec7e91fa
     8 __docformat__ = "restructuredtext en"
     8 __docformat__ = "restructuredtext en"
     9 
     9 
    10 from logilab.common.decorators import clear_cache
    10 from logilab.common.decorators import clear_cache
    11 
    11 
    12 from cubicweb import AuthenticationError, BadConnectionId
    12 from cubicweb import AuthenticationError, BadConnectionId
       
    13 from cubicweb.view import Component
    13 from cubicweb.dbapi import repo_connect, ConnectionProperties
    14 from cubicweb.dbapi import repo_connect, ConnectionProperties
    14 from cubicweb.web import ExplicitLogin, InvalidSession
    15 from cubicweb.web import ExplicitLogin, InvalidSession
    15 from cubicweb.web.application import AbstractAuthenticationManager
    16 from cubicweb.web.application import AbstractAuthenticationManager
       
    17 
       
    18 class NoAuthInfo(Exception): pass
       
    19 
       
    20 
       
    21 class WebAuthInfoRetreiver(Component):
       
    22     __registry__ = 'webauth'
       
    23     order = None
       
    24 
       
    25     def authentication_information(self, req):
       
    26         """retreive authentication information from the given request, raise
       
    27         NoAuthInfo if expected information is not found.
       
    28         """
       
    29         raise NotImplementedError()
       
    30 
       
    31     def authenticated(self, req, cnx, retreiver):
       
    32         """callback when return authentication information have opened a
       
    33         repository connection successfully
       
    34         """
       
    35         pass
       
    36 
       
    37 
       
    38 class LoginPasswordRetreiver(WebAuthInfoRetreiver):
       
    39     __regid__ = 'loginpwdauth'
       
    40     order = 10
       
    41 
       
    42     def __init__(self, vreg):
       
    43         self.anoninfo = vreg.config.anonymous_user()
       
    44 
       
    45     def authentication_information(self, req):
       
    46         """retreive authentication information from the given request, raise
       
    47         NoAuthInfo if expected information is not found.
       
    48         """
       
    49         login, password = req.get_authorization()
       
    50         if not login:
       
    51             # No session and no login -> try anonymous
       
    52             login, password = self.anoninfo
       
    53             if not login: # anonymous not authorized
       
    54                 raise NoAuthInfo()
       
    55         return login, {'password': password}
    16 
    56 
    17 
    57 
    18 class RepositoryAuthenticationManager(AbstractAuthenticationManager):
    58 class RepositoryAuthenticationManager(AbstractAuthenticationManager):
    19     """authenticate user associated to a request and check session validity"""
    59     """authenticate user associated to a request and check session validity"""
    20 
    60 
    21     def __init__(self, vreg):
    61     def __init__(self, vreg):
    22         super(RepositoryAuthenticationManager, self).__init__(vreg)
    62         super(RepositoryAuthenticationManager, self).__init__(vreg)
    23         self.repo = vreg.config.repository(vreg)
    63         self.repo = vreg.config.repository(vreg)
    24         self.log_queries = vreg.config['query-log-file']
    64         self.log_queries = vreg.config['query-log-file']
       
    65         self.authinforetreivers = sorted(vreg['webauth'].possible_objects(vreg),
       
    66                                     key=lambda x: x.order)
       
    67         assert self.authinforetreivers
    25 
    68 
    26     def validate_session(self, req, session):
    69     def validate_session(self, req, session):
    27         """check session validity, and return eventually hijacked session
    70         """check session validity, and return eventually hijacked session
    28 
    71 
    29         :raise InvalidSession:
    72         :raise InvalidSession:
    44                 cnx.close()
    87                 cnx.close()
    45                 raise InvalidSession('login mismatch')
    88                 raise InvalidSession('login mismatch')
    46         except BadConnectionId:
    89         except BadConnectionId:
    47             # check if a connection should be automatically restablished
    90             # check if a connection should be automatically restablished
    48             if (login is None or login == cnx.login):
    91             if (login is None or login == cnx.login):
    49                 login, password = cnx.login, cnx.password
    92                 cnx = self._authenticate(req, cnx.login, cnx.authinfo)
    50                 cnx = self.authenticate(req, login, password)
       
    51                 user = cnx.user(req)
    93                 user = cnx.user(req)
    52                 # backport session's data
    94                 # backport session's data
    53                 cnx.data = session.data
    95                 cnx.data = session.data
    54             else:
    96             else:
    55                 raise InvalidSession('bad connection id')
    97                 raise InvalidSession('bad connection id')
    56         # associate the connection to the current request
    98         # associate the connection to the current request
    57         req.set_connection(cnx, user)
    99         req.set_connection(cnx, user)
    58         return cnx
   100         return cnx
    59 
   101 
    60     def authenticate(self, req, _login=None, _password=None):
   102     def authenticate(self, req):
    61         """authenticate user and return corresponding user object
   103         """authenticate user and return corresponding user object
    62 
   104 
    63         :raise ExplicitLogin: if authentication is required (no authentication
   105         :raise ExplicitLogin: if authentication is required (no authentication
    64         info found or wrong user/password)
   106         info found or wrong user/password)
    65 
   107 
    66         Note: this method is violating AuthenticationManager interface by
   108         Note: this method is violating AuthenticationManager interface by
    67         returning a session instance instead of the user. This is expected by
   109         returning a session instance instead of the user. This is expected by
    68         the InMemoryRepositorySessionManager.
   110         the InMemoryRepositorySessionManager.
    69         """
   111         """
    70         if _login is not None:
   112         for retreiver in self.authinforetreivers:
    71             login, password = _login, _password
   113             try:
       
   114                 login, authinfo = retreiver.authentication_information(req)
       
   115             except NoAuthInfo:
       
   116                 continue
       
   117             cnx = self._authenticate(req, login, authinfo)
       
   118             break
    72         else:
   119         else:
    73             login, password = req.get_authorization()
   120             raise ExplicitLogin()
    74         if not login:
   121         for retreiver_ in self.authinforetreivers:
    75             # No session and no login -> try anonymous
   122             retreiver_.authenticated(req, cnx, retreiver)
    76             login, password = self.vreg.config.anonymous_user()
   123         return cnx
    77             if not login: # anonymous not authorized
   124 
    78                 raise ExplicitLogin()
   125     def _authenticate(self, req, login, authinfo):
    79         # remove possibly cached cursor coming from closed connection
   126         # remove possibly cached cursor coming from closed connection
    80         clear_cache(req, 'cursor')
   127         clear_cache(req, 'cursor')
    81         cnxprops = ConnectionProperties(self.vreg.config.repo_method,
   128         cnxprops = ConnectionProperties(self.vreg.config.repo_method,
    82                                         close=False, log=self.log_queries)
   129                                         close=False, log=self.log_queries)
    83         try:
   130         try:
    84             cnx = repo_connect(self.repo, login, password=password,
   131             cnx = repo_connect(self.repo, login, cnxprops=cnxprops, **authinfo)
    85                                cnxprops=cnxprops)
       
    86         except AuthenticationError:
   132         except AuthenticationError:
    87             req.set_message(req._('authentication failure'))
   133             req.set_message(req._('authentication failure'))
    88             # restore an anonymous connection if possible
   134             # restore an anonymous connection if possible
    89             anonlogin, anonpassword = self.vreg.config.anonymous_user()
   135             anonlogin, anonpassword = self.vreg.config.anonymous_user()
    90             if anonlogin and anonlogin != login:
   136             if anonlogin and anonlogin != login:
    91                 cnx = repo_connect(self.repo, anonlogin, password=anonpassword,
   137                 cnx = repo_connect(self.repo, anonlogin, password=anonpassword,
    92                                    cnxprops=cnxprops)
   138                                    cnxprops=cnxprops)
    93                 self._init_cnx(cnx, anonlogin, anonpassword)
   139                 self._init_cnx(cnx, anonlogin, {'password': anonpassword})
    94             else:
   140             else:
    95                 raise ExplicitLogin()
   141                 raise ExplicitLogin()
    96         else:
   142         else:
    97             self._init_cnx(cnx, login, password)
   143             self._init_cnx(cnx, login, authinfo)
    98         # associate the connection to the current request
   144         # associate the connection to the current request
    99         req.set_connection(cnx)
   145         req.set_connection(cnx)
   100         return cnx
   146         return cnx
   101 
   147 
   102     def _init_cnx(self, cnx, login, password):
   148     def _init_cnx(self, cnx, login, authinfo):
   103         # decorate connection
   149         # decorate connection
   104         if login == self.vreg.config.anonymous_user()[0]:
   150         if login == self.vreg.config.anonymous_user()[0]:
   105             cnx.anonymous_connection = True
   151             cnx.anonymous_connection = True
   106         cnx.vreg = self.vreg
   152         cnx.vreg = self.vreg
   107         cnx.login = login
   153         cnx.login = login
   108         cnx.password = password
   154         cnx.authinfo = authinfo
   109 
   155