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 |