commit | author | age
|
993216
|
1 |
from zope.interface import implements |
TS |
2 |
|
b482a1
|
3 |
from repoze.who.interfaces import IAPI |
993216
|
4 |
from repoze.who.interfaces import IAPIFactory |
TS |
5 |
from repoze.who.interfaces import IIdentifier |
|
6 |
from repoze.who.interfaces import IAuthenticator |
|
7 |
from repoze.who.interfaces import IChallenger |
|
8 |
from repoze.who.interfaces import IMetadataProvider |
|
9 |
|
|
10 |
|
|
11 |
def get_api(environ): |
|
12 |
return environ.get('repoze.who.api') |
|
13 |
|
|
14 |
|
|
15 |
class APIFactory(object): |
|
16 |
implements(IAPIFactory) |
|
17 |
|
|
18 |
def __init__(self, |
|
19 |
identifiers=(), |
|
20 |
authenticators=(), |
|
21 |
challengers=(), |
|
22 |
mdproviders=(), |
|
23 |
request_classifier=None, |
|
24 |
challenge_decider=None, |
b482a1
|
25 |
remote_user_key = 'REMOTE_USER', |
993216
|
26 |
logger=None, |
TS |
27 |
): |
|
28 |
self.identifiers = identifiers |
|
29 |
self.authenticators = authenticators |
|
30 |
self.challengers = challengers |
|
31 |
self.mdproviders = mdproviders |
|
32 |
self.request_classifier = request_classifier |
|
33 |
self.challenge_decider = challenge_decider |
b482a1
|
34 |
self.remote_user_key = remote_user_key |
993216
|
35 |
self.logger = logger |
TS |
36 |
|
|
37 |
def __call__(self, environ): |
|
38 |
""" See IAPIFactory. |
|
39 |
""" |
|
40 |
api = environ.get('repoze.who.api') |
|
41 |
if api is None: |
b482a1
|
42 |
api = environ['repoze.who.api'] = API(environ, |
TS |
43 |
self.identifiers, |
993216
|
44 |
self.authenticators, |
TS |
45 |
self.challengers, |
|
46 |
self.mdproviders, |
|
47 |
self.request_classifier, |
|
48 |
self.challenge_decider, |
b482a1
|
49 |
self.remote_user_key, |
993216
|
50 |
self.logger, |
TS |
51 |
) |
|
52 |
return api |
|
53 |
|
|
54 |
|
|
55 |
def verify(plugin, iface): |
|
56 |
from zope.interface.verify import verifyObject |
|
57 |
verifyObject(iface, plugin, tentative=True) |
|
58 |
|
|
59 |
|
|
60 |
def make_registries(identifiers, authenticators, challengers, mdproviders): |
|
61 |
from zope.interface.verify import BrokenImplementation |
|
62 |
interface_registry = {} |
|
63 |
name_registry = {} |
|
64 |
|
|
65 |
for supplied, iface in [ (identifiers, IIdentifier), |
|
66 |
(authenticators, IAuthenticator), |
|
67 |
(challengers, IChallenger), |
|
68 |
(mdproviders, IMetadataProvider)]: |
|
69 |
|
|
70 |
for name, value in supplied: |
|
71 |
try: |
|
72 |
verify(value, iface) |
|
73 |
except BrokenImplementation, why: |
|
74 |
why = str(why) |
|
75 |
raise ValueError(str(name) + ': ' + why) |
|
76 |
L = interface_registry.setdefault(iface, []) |
|
77 |
L.append(value) |
|
78 |
name_registry[name] = value |
|
79 |
|
|
80 |
return interface_registry, name_registry |
|
81 |
|
|
82 |
|
|
83 |
def match_classification(iface, plugins, classification): |
|
84 |
result = [] |
|
85 |
for plugin in plugins: |
|
86 |
|
|
87 |
plugin_classifications = getattr(plugin, 'classifications', {}) |
|
88 |
iface_classifications = plugin_classifications.get(iface) |
|
89 |
if not iface_classifications: # good for any |
|
90 |
result.append(plugin) |
|
91 |
continue |
|
92 |
if classification in iface_classifications: |
|
93 |
result.append(plugin) |
|
94 |
|
|
95 |
return result |
|
96 |
|
|
97 |
|
|
98 |
class API(object): |
b482a1
|
99 |
implements(IAPI) |
993216
|
100 |
|
TS |
101 |
def __init__(self, |
b482a1
|
102 |
environ, |
993216
|
103 |
identifiers, |
TS |
104 |
authenticators, |
|
105 |
challengers, |
|
106 |
mdproviders, |
|
107 |
request_classifier, |
|
108 |
challenge_decider, |
b482a1
|
109 |
remote_user_key, |
993216
|
110 |
logger, |
TS |
111 |
): |
b482a1
|
112 |
self.environ = environ |
993216
|
113 |
(self.interface_registry, |
TS |
114 |
self.name_registry) = make_registries(identifiers, authenticators, |
|
115 |
challengers, mdproviders) |
9a8e60
|
116 |
self.identifiers = identifiers |
993216
|
117 |
self.authenticators = authenticators |
TS |
118 |
self.challengers = challengers |
|
119 |
self.mdproviders = mdproviders |
|
120 |
self.challenge_decider = challenge_decider |
b482a1
|
121 |
self.remote_user_key = remote_user_key |
993216
|
122 |
self.logger = logger |
b482a1
|
123 |
classification = self.classification = (request_classifier and |
TS |
124 |
request_classifier(environ)) |
|
125 |
logger and logger.info('request classification: %s' % classification) |
9a8e60
|
126 |
|
b482a1
|
127 |
def authenticate(self): |
TS |
128 |
|
|
129 |
ids = self._identify() |
|
130 |
|
|
131 |
# ids will be list of tuples: [ (IIdentifier, identity) ] |
|
132 |
if ids: |
|
133 |
auth_ids = self._authenticate(ids) |
|
134 |
|
|
135 |
# auth_ids will be a list of five-tuples in the form |
|
136 |
# ( (auth_rank, id_rank), authenticator, identifier, identity, |
|
137 |
# userid ) |
|
138 |
# |
|
139 |
# When sorted, its first element will represent the "best" |
|
140 |
# identity for this request. |
|
141 |
|
|
142 |
if auth_ids: |
|
143 |
auth_ids.sort() |
|
144 |
best = auth_ids[0] |
|
145 |
rank, authenticator, identifier, identity, userid = best |
|
146 |
identity = Identity(identity) # dont show contents at print |
|
147 |
identity['authenticator'] = authenticator |
|
148 |
identity['identifier'] = identifier |
|
149 |
|
|
150 |
# allow IMetadataProvider plugins to scribble on the identity |
|
151 |
self._add_metadata(identity) |
|
152 |
|
|
153 |
# add the identity to the environment; a downstream |
|
154 |
# application can mutate it to do an 'identity reset' |
|
155 |
# as necessary, e.g. identity['login'] = 'foo', |
|
156 |
# identity['password'] = 'bar' |
|
157 |
self.environ['repoze.who.identity'] = identity |
|
158 |
# set the REMOTE_USER |
|
159 |
self.environ[self.remote_user_key] = userid |
|
160 |
return identity |
|
161 |
|
|
162 |
self.logger and self.logger.info( |
|
163 |
'no identities found, not authenticating') |
|
164 |
|
|
165 |
def challenge(self, status='403 Forbidden', app_headers=()): |
|
166 |
""" See IAPI. |
|
167 |
""" |
|
168 |
identity = self.environ.get('repoze.who.identity', {}) |
|
169 |
identifier = identity.get('identifier') |
|
170 |
|
|
171 |
logger = self.logger |
|
172 |
|
|
173 |
forget_headers = [] |
|
174 |
|
|
175 |
if identifier: |
|
176 |
id_forget_headers = identifier.forget(self.environ, identity) |
|
177 |
if id_forget_headers is not None: |
|
178 |
forget_headers.extend(id_forget_headers) |
|
179 |
logger and logger.info('forgetting via headers from %s: %s' |
|
180 |
% (identifier, forget_headers)) |
|
181 |
|
|
182 |
candidates = self.interface_registry.get(IChallenger, ()) |
52bc23
|
183 |
logger and logger.debug('challengers registered: %s' % repr(candidates)) |
b482a1
|
184 |
plugins = match_classification(IChallenger, candidates, |
TS |
185 |
self.classification) |
52bc23
|
186 |
logger and logger.debug('challengers matched for ' |
TS |
187 |
'classification "%s": %s' |
|
188 |
% (self.classification, plugins)) |
b482a1
|
189 |
for plugin in plugins: |
TS |
190 |
app = plugin.challenge(self.environ, status, app_headers, |
|
191 |
forget_headers) |
|
192 |
if app is not None: |
|
193 |
# new WSGI application |
|
194 |
logger and logger.info( |
|
195 |
'challenger plugin %s "challenge" returned an app' % ( |
|
196 |
plugin)) |
|
197 |
return app |
|
198 |
|
|
199 |
# signifies no challenge |
|
200 |
logger and logger.info('no challenge app returned') |
|
201 |
return None |
|
202 |
|
b213af
|
203 |
def remember(self, identity=None): |
b482a1
|
204 |
""" See IAPI. |
TS |
205 |
""" |
|
206 |
headers = () |
b213af
|
207 |
if identity is None: |
TS |
208 |
identity = self.environ.get('repoze.who.identity', {}) |
b482a1
|
209 |
identifier = identity.get('identifier') |
TS |
210 |
if identifier: |
cfe26c
|
211 |
got_headers = identifier.remember(self.environ, identity) |
BS |
212 |
if got_headers: |
|
213 |
headers = got_headers |
b482a1
|
214 |
logger = self.logger |
TS |
215 |
logger and logger.info('remembering via headers from %s: %s' |
|
216 |
% (identifier, headers)) |
|
217 |
return headers |
|
218 |
|
b213af
|
219 |
def forget(self, identity=None): |
b482a1
|
220 |
""" See IAPI. |
TS |
221 |
""" |
|
222 |
headers = () |
b213af
|
223 |
if identity is None: |
TS |
224 |
identity = self.environ.get('repoze.who.identity', {}) |
b482a1
|
225 |
identifier = identity.get('identifier') |
TS |
226 |
if identifier: |
cfe26c
|
227 |
got_headers = identifier.forget(self.environ, identity) |
BS |
228 |
if got_headers: |
|
229 |
headers = got_headers |
b482a1
|
230 |
logger = self.logger |
TS |
231 |
logger and logger.info('forgetting via headers from %s: %s' |
|
232 |
% (identifier, headers)) |
|
233 |
return headers |
|
234 |
|
9a8e60
|
235 |
def login(self, credentials, identifier_name=None): |
TS |
236 |
""" See IAPI. |
|
237 |
""" |
|
238 |
if identifier_name is not None: |
|
239 |
identifier = self.name_registry[identifier_name] |
|
240 |
else: |
|
241 |
identifier = self.identifiers[0][1] |
|
242 |
# Pretend that the given identifier extracted the identity. |
|
243 |
authenticated = self._authenticate([(identifier, credentials)]) |
|
244 |
if authenticated: |
|
245 |
# and therefore can remember it |
|
246 |
rank, plugin, identifier, identity, userid = authenticated[0] |
|
247 |
headers = identifier.remember(self.environ, identity) |
|
248 |
return identity, headers |
|
249 |
else: |
|
250 |
# or forget it |
|
251 |
headers = identifier.forget(self.environ, None) |
|
252 |
return None, headers |
|
253 |
|
924f24
|
254 |
def logout(self, identifier_name=None): |
TS |
255 |
""" See IAPI. |
|
256 |
""" |
|
257 |
if identifier_name is not None: |
|
258 |
identifier = self.name_registry[identifier_name] |
|
259 |
else: |
|
260 |
identifier = self.identifiers[0][1] |
|
261 |
# Pretend that the given identifier extracted the identity. |
e24a75
|
262 |
headers = identifier.forget(self.environ, None) |
CM |
263 |
|
|
264 |
# we need to remove the identity for hybrid middleware/api usages to |
|
265 |
# work correctly: middleware calls ``remember`` unconditionally "on |
|
266 |
# the way out", and if an identity is found, competing login headers |
|
267 |
# will be set. |
|
268 |
if 'repoze.who.identity' in self.environ: |
|
269 |
del self.environ['repoze.who.identity'] |
|
270 |
|
|
271 |
return headers |
924f24
|
272 |
|
b482a1
|
273 |
def _identify(self): |
TS |
274 |
""" See IAPI. |
|
275 |
""" |
993216
|
276 |
logger = self.logger |
TS |
277 |
candidates = self.interface_registry.get(IIdentifier, ()) |
52bc23
|
278 |
logger and self.logger.debug('identifier plugins registered: %s' % |
993216
|
279 |
(candidates,)) |
b482a1
|
280 |
plugins = match_classification(IIdentifier, candidates, |
TS |
281 |
self.classification) |
52bc23
|
282 |
logger and self.logger.debug( |
993216
|
283 |
'identifier plugins matched for ' |
b482a1
|
284 |
'classification "%s": %s' % (self.classification, plugins)) |
993216
|
285 |
|
TS |
286 |
results = [] |
|
287 |
for plugin in plugins: |
b482a1
|
288 |
identity = plugin.identify(self.environ) |
993216
|
289 |
if identity is not None: |
TS |
290 |
logger and logger.debug( |
|
291 |
'identity returned from %s: %s' % (plugin, identity)) |
|
292 |
results.append((plugin, identity)) |
|
293 |
else: |
|
294 |
logger and logger.debug( |
|
295 |
'no identity returned from %s (%s)' % (plugin, identity)) |
|
296 |
|
|
297 |
logger and logger.debug('identities found: %s' % (results,)) |
|
298 |
return results |
|
299 |
|
b482a1
|
300 |
def _authenticate(self, identities): |
TS |
301 |
""" See IAPI. |
|
302 |
""" |
993216
|
303 |
logger = self.logger |
TS |
304 |
candidates = self.interface_registry.get(IAuthenticator, []) |
52bc23
|
305 |
logger and self.logger.debug('authenticator plugins registered: %s' % |
993216
|
306 |
candidates) |
TS |
307 |
plugins = match_classification(IAuthenticator, candidates, |
b482a1
|
308 |
self.classification) |
52bc23
|
309 |
logger and self.logger.debug( |
993216
|
310 |
'authenticator plugins matched for ' |
b482a1
|
311 |
'classification "%s": %s' % (self.classification, plugins)) |
993216
|
312 |
|
TS |
313 |
auth_rank = 0 |
b213af
|
314 |
results = [] |
993216
|
315 |
|
TS |
316 |
for plugin in plugins: |
b213af
|
317 |
identifier_rank = 0 |
993216
|
318 |
for identifier, identity in identities: |
b482a1
|
319 |
userid = plugin.authenticate(self.environ, identity) |
993216
|
320 |
if userid is not None: |
TS |
321 |
logger and logger.debug( |
|
322 |
'userid returned from %s: "%s"' % (plugin, userid)) |
|
323 |
|
|
324 |
# stamp the identity with the userid |
|
325 |
identity['repoze.who.userid'] = userid |
|
326 |
rank = (auth_rank, identifier_rank) |
|
327 |
results.append( |
|
328 |
(rank, plugin, identifier, identity, userid) |
|
329 |
) |
|
330 |
else: |
|
331 |
logger and logger.debug( |
|
332 |
'no userid returned from %s: (%s)' % ( |
|
333 |
plugin, userid)) |
|
334 |
identifier_rank += 1 |
|
335 |
auth_rank += 1 |
|
336 |
|
|
337 |
logger and logger.debug('identities authenticated: %s' % (results,)) |
|
338 |
return results |
|
339 |
|
b482a1
|
340 |
def _add_metadata(self, identity): |
TS |
341 |
""" See IAPI. |
|
342 |
""" |
993216
|
343 |
candidates = self.interface_registry.get(IMetadataProvider, ()) |
TS |
344 |
plugins = match_classification(IMetadataProvider, candidates, |
b482a1
|
345 |
self.classification) |
993216
|
346 |
for plugin in plugins: |
b482a1
|
347 |
plugin.add_metadata(self.environ, identity) |
993216
|
348 |
|
b482a1
|
349 |
class Identity(dict): |
TS |
350 |
""" dict subclass: prevent members from being rendered during print |
|
351 |
""" |
|
352 |
def __repr__(self): |
|
353 |
return '<repoze.who identity (hidden, dict-like) at %s>' % id(self) |
|
354 |
__str__ = __repr__ |