web/views/authentication.py
changeset 11057 0b59724cb3f2
parent 11052 058bb3dc685f
child 11058 23eb30449fe5
equal deleted inserted replaced
11052:058bb3dc685f 11057:0b59724cb3f2
     1 # copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     3 #
       
     4 # This file is part of CubicWeb.
       
     5 #
       
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
       
     7 # terms of the GNU Lesser General Public License as published by the Free
       
     8 # Software Foundation, either version 2.1 of the License, or (at your option)
       
     9 # any later version.
       
    10 #
       
    11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT
       
    12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
       
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
       
    14 # details.
       
    15 #
       
    16 # You should have received a copy of the GNU Lesser General Public License along
       
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
       
    18 """user authentication component"""
       
    19 
       
    20 __docformat__ = "restructuredtext en"
       
    21 
       
    22 from logilab.common.deprecation import class_renamed
       
    23 
       
    24 from cubicweb import AuthenticationError
       
    25 from cubicweb.view import Component
       
    26 from cubicweb.web import InvalidSession
       
    27 
       
    28 
       
    29 class NoAuthInfo(Exception): pass
       
    30 
       
    31 
       
    32 class WebAuthInfoRetriever(Component):
       
    33     __registry__ = 'webauth'
       
    34     order = None
       
    35     __abstract__ = True
       
    36 
       
    37     def authentication_information(self, req):
       
    38         """retrieve authentication information from the given request, raise
       
    39         NoAuthInfo if expected information is not found.
       
    40         """
       
    41         raise NotImplementedError()
       
    42 
       
    43     def authenticated(self, retriever, req, session, login, authinfo):
       
    44         """callback when return authentication information have opened a
       
    45         repository connection successfully. Take care req has no session
       
    46         attached yet, hence req.execute isn't available.
       
    47         """
       
    48         pass
       
    49 
       
    50     def request_has_auth_info(self, req):
       
    51         """tells from the request if it has enough information
       
    52         to proceed to authentication, would the current session
       
    53         be invalidated
       
    54         """
       
    55         raise NotImplementedError()
       
    56 
       
    57     def revalidate_login(self, req):
       
    58         """returns a login string or None, for repository session validation
       
    59         purposes
       
    60         """
       
    61         raise NotImplementedError()
       
    62 
       
    63     def cleanup_authentication_information(self, req):
       
    64         """called when the retriever has returned some authentication
       
    65         information but we get an authentication error when using them, so it
       
    66         get a chance to clean things up (e.g. remove cookie)
       
    67         """
       
    68         pass
       
    69 
       
    70 WebAuthInfoRetreiver = class_renamed(
       
    71     'WebAuthInfoRetreiver', WebAuthInfoRetriever,
       
    72     '[3.17] WebAuthInfoRetreiver had been renamed into WebAuthInfoRetriever '
       
    73     '("ie" instead of "ei")')
       
    74 
       
    75 
       
    76 class LoginPasswordRetriever(WebAuthInfoRetriever):
       
    77     __regid__ = 'loginpwdauth'
       
    78     order = 10
       
    79 
       
    80     def authentication_information(self, req):
       
    81         """retreive authentication information from the given request, raise
       
    82         NoAuthInfo if expected information is not found.
       
    83         """
       
    84         login, password = req.get_authorization()
       
    85         if not login:
       
    86             raise NoAuthInfo()
       
    87         return login, {'password': password}
       
    88 
       
    89     def request_has_auth_info(self, req):
       
    90         return req.get_authorization()[0] is not None
       
    91 
       
    92     def revalidate_login(self, req):
       
    93         return req.get_authorization()[0]
       
    94 
       
    95 LoginPasswordRetreiver = class_renamed(
       
    96     'LoginPasswordRetreiver', LoginPasswordRetriever,
       
    97     '[3.17] LoginPasswordRetreiver had been renamed into LoginPasswordRetriever '
       
    98     '("ie" instead of "ei")')
       
    99 
       
   100 
       
   101 
       
   102 class RepositoryAuthenticationManager(object):
       
   103     """authenticate user associated to a request and check session validity"""
       
   104 
       
   105     def __init__(self, repo):
       
   106         self.repo = repo
       
   107         vreg = repo.vreg
       
   108         self.log_queries = vreg.config['query-log-file']
       
   109         self.authinforetrievers = sorted(vreg['webauth'].possible_objects(vreg),
       
   110                                          key=lambda x: x.order)
       
   111         # 2-uple login / password, login is None when no anonymous access
       
   112         # configured
       
   113         self.anoninfo = vreg.config.anonymous_user()
       
   114         if self.anoninfo[0]:
       
   115             self.anoninfo = (self.anoninfo[0], {'password': self.anoninfo[1]})
       
   116 
       
   117     def validate_session(self, req, session):
       
   118         """check session validity and return the connected user on success.
       
   119 
       
   120         raise :exc:`InvalidSession` if session is corrupted for a reason or
       
   121         another and should be closed
       
   122 
       
   123         also invoked while going from anonymous to logged in
       
   124         """
       
   125         for retriever in self.authinforetrievers:
       
   126             if retriever.request_has_auth_info(req):
       
   127                 login = retriever.revalidate_login(req)
       
   128                 return self._validate_session(req, session, login)
       
   129         # let's try with the current session
       
   130         return self._validate_session(req, session, None)
       
   131 
       
   132     def _validate_session(self, req, session, login):
       
   133         # check session.login and not user.login, since in case of login by
       
   134         # email, login and cnx.login are the email while user.login is the
       
   135         # actual user login
       
   136         if login and session.login != login:
       
   137             raise InvalidSession('login mismatch')
       
   138 
       
   139     def authenticate(self, req):
       
   140         """authenticate user using connection information found in the request,
       
   141         and return corresponding a :class:`~cubicweb.dbapi.Connection` instance,
       
   142         as well as login used to open the connection.
       
   143 
       
   144         raise :exc:`cubicweb.AuthenticationError` if authentication failed
       
   145         (no authentication info found or wrong user/password)
       
   146         """
       
   147         has_auth = False
       
   148         for retriever in self.authinforetrievers:
       
   149             try:
       
   150                 login, authinfo = retriever.authentication_information(req)
       
   151             except NoAuthInfo:
       
   152                 continue
       
   153             has_auth = True
       
   154             try:
       
   155                 session = self._authenticate(login, authinfo)
       
   156             except AuthenticationError:
       
   157                 retriever.cleanup_authentication_information(req)
       
   158                 continue # the next one may succeed
       
   159             for retriever_ in self.authinforetrievers:
       
   160                 retriever_.authenticated(retriever, req, session, login, authinfo)
       
   161             return session, login
       
   162         # false if no authentication info found, i.e. this is not an
       
   163         # authentication failure
       
   164         if has_auth:
       
   165             req.set_message(req._('authentication failure'))
       
   166         login, authinfo = self.anoninfo
       
   167         if login:
       
   168             session = self._authenticate(login, authinfo)
       
   169             return session, login
       
   170         raise AuthenticationError()
       
   171 
       
   172     def _authenticate(self, login, authinfo):
       
   173         sessionid = self.repo.connect(login, **authinfo)
       
   174         return self.repo._sessions[sessionid]