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] |
|