author | Denis Laxalde <denis.laxalde@logilab.fr> |
Mon, 19 Jun 2017 18:15:28 +0200 | |
changeset 12188 | fea018b2e056 |
parent 12046 | 9056a41d91ba |
child 12279 | e2953e0de494 |
permissions | -rw-r--r-- |
# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. # # CubicWeb is free software: you can redistribute it and/or modify it under the # terms of the GNU Lesser General Public License as published by the Free # Software Foundation, either version 2.1 of the License, or (at your option) # any later version. # # CubicWeb is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see <http://www.gnu.org/licenses/>. """user authentication component""" from logilab.common.deprecation import class_renamed from logilab.common.textutils import unormalize from cubicweb import AuthenticationError from cubicweb.utils import make_uid from cubicweb.view import Component from cubicweb.web import InvalidSession from cubicweb.server.session import Connection class NoAuthInfo(Exception): pass class WebAuthInfoRetriever(Component): __registry__ = 'webauth' order = None __abstract__ = True def authentication_information(self, req): """retrieve authentication information from the given request, raise NoAuthInfo if expected information is not found. """ raise NotImplementedError() def authenticated(self, retriever, req, session, 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. """ pass def request_has_auth_info(self, req): """tells from the request if it has enough information to proceed to authentication, would the current session be invalidated """ raise NotImplementedError() def revalidate_login(self, req): """returns a login string or None, for repository session validation purposes """ raise NotImplementedError() def cleanup_authentication_information(self, req): """called when the retriever has returned some authentication information but we get an authentication error when using them, so it get a chance to clean things up (e.g. remove cookie) """ pass WebAuthInfoRetreiver = class_renamed( 'WebAuthInfoRetreiver', WebAuthInfoRetriever, '[3.17] WebAuthInfoRetreiver had been renamed into WebAuthInfoRetriever ' '("ie" instead of "ei")') class LoginPasswordRetriever(WebAuthInfoRetriever): __regid__ = 'loginpwdauth' order = 10 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: raise NoAuthInfo() return login, {'password': password} def request_has_auth_info(self, req): return req.get_authorization()[0] is not None def revalidate_login(self, req): return req.get_authorization()[0] LoginPasswordRetreiver = class_renamed( 'LoginPasswordRetreiver', LoginPasswordRetriever, '[3.17] LoginPasswordRetreiver had been renamed into LoginPasswordRetriever ' '("ie" instead of "ei")') class Session(object): """In-memory user session """ def __init__(self, repo, user): self.user = user # XXX deprecate and store only a login. self.repo = repo self.sessionid = make_uid(unormalize(user.login)) self.data = {} def __unicode__(self): return '<session %s (0x%x)>' % (unicode(self.user.login), id(self)) @property def anonymous_session(self): # XXX for now, anonymous_user only exists in webconfig (and testconfig). # It will only be present inside all-in-one instance. # there is plan to move it down to global config. if not hasattr(self.repo.config, 'anonymous_user'): # not a web or test config, no anonymous user return False return self.user.login == self.repo.config.anonymous_user()[0] def new_cnx(self): """Return a new Connection object linked to the session The returned Connection will *not* be managed by the Session. """ cnx = Connection(self.repo, self.user) cnx.session = self return cnx class RepositoryAuthenticationManager(object): """authenticate user associated to a request and check session validity""" def __init__(self, repo): self.repo = repo vreg = repo.vreg self.log_queries = vreg.config['query-log-file'] self.authinforetrievers = sorted(vreg['webauth'].possible_objects(vreg), key=lambda x: x.order) # 2-uple login / password, login is None when no anonymous access # configured self.anoninfo = vreg.config.anonymous_user() if self.anoninfo[0]: self.anoninfo = (self.anoninfo[0], {'password': self.anoninfo[1]}) def validate_session(self, req, session): """check session validity and return the connected user on success. raise :exc:`InvalidSession` if session is corrupted for a reason or another and should be closed also invoked while going from anonymous to logged in """ for retriever in self.authinforetrievers: if retriever.request_has_auth_info(req): login = retriever.revalidate_login(req) return self._validate_session(req, session, login) # let's try with the current session return self._validate_session(req, session, None) def _validate_session(self, req, session, login): # check session.login and not user.login, since in case of login by # email, login and cnx.login are the email while user.login is the # actual user login if login and session.user.login != login: raise InvalidSession('login mismatch') def authenticate(self, req): """authenticate user using connection information found in the request, and return corresponding a :class:`~cubicweb.dbapi.Connection` instance, as well as login used to open the connection. raise :exc:`cubicweb.AuthenticationError` if authentication failed (no authentication info found or wrong user/password) """ has_auth = False for retriever in self.authinforetrievers: try: login, authinfo = retriever.authentication_information(req) except NoAuthInfo: continue has_auth = True try: session = self._authenticate(login, authinfo) except AuthenticationError: retriever.cleanup_authentication_information(req) continue # the next one may succeed for retriever_ in self.authinforetrievers: retriever_.authenticated(retriever, req, session, login, authinfo) return session, login # false if no authentication info found, i.e. this is not an # authentication failure if has_auth: req.set_message(req._('authentication failure')) login, authinfo = self.anoninfo if login: session = self._authenticate(login, authinfo) return session, login raise AuthenticationError() def _authenticate(self, login, authinfo): with self.repo.internal_cnx() as cnx: user = self.repo.authenticate_user(cnx, login, **authinfo) return Session(self.repo, user)