1 import logging
2 from StringIO import StringIO
3 import sys
4
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 _STARTED = '-- repoze.who request started (%s) --'
11 _ENDED = '-- repoze.who request ended (%s) --'
12
14 - def __init__(self, app,
15 identifiers,
16 authenticators,
17 challengers,
18 mdproviders,
19 classifier,
20 challenge_decider,
21 log_stream = None,
22 log_level = logging.INFO,
23 remote_user_key = 'REMOTE_USER',
24 ):
25 iregistry, nregistry = make_registries(identifiers, authenticators,
26 challengers, mdproviders)
27 self.registry = iregistry
28 self.name_registry = nregistry
29 self.app = app
30 self.classifier = classifier
31 self.challenge_decider = challenge_decider
32 self.remote_user_key = remote_user_key
33 self.logger = None
34 if isinstance(log_stream, logging.Logger):
35 self.logger = log_stream
36 elif log_stream:
37 handler = logging.StreamHandler(log_stream)
38 fmt = '%(asctime)s %(message)s'
39 formatter = logging.Formatter(fmt)
40 handler.setFormatter(formatter)
41 self.logger = logging.Logger('repoze.who')
42 self.logger.addHandler(handler)
43 self.logger.setLevel(log_level)
44
45 - def __call__(self, environ, start_response):
46 if self.remote_user_key in environ:
47
48
49 return self.app(environ, start_response)
50
51 path_info = environ.get('PATH_INFO', None)
52
53 environ['repoze.who.plugins'] = self.name_registry
54 environ['repoze.who.logger'] = self.logger
55 environ['repoze.who.application'] = self.app
56
57 logger = self.logger
58 logger and logger.info(_STARTED % path_info)
59 classification = self.classifier(environ)
60 logger and logger.info('request classification: %s' % classification)
61 userid = None
62 identity = None
63 identifier = None
64
65 ids = self.identify(environ, classification)
66
67
68 if ids:
69 auth_ids = self.authenticate(environ, classification, ids)
70
71
72
73
74
75
76
77
78 if auth_ids:
79 auth_ids.sort()
80 best = auth_ids[0]
81 rank, authenticator, identifier, identity, userid = best
82 identity = Identity(identity)
83
84
85 self.add_metadata(environ, classification, identity)
86
87
88
89
90
91 environ['repoze.who.identity'] = identity
92
93 environ[self.remote_user_key] = userid
94
95 else:
96 logger and logger.info('no identities found, not authenticating')
97
98
99
100
101 app = environ.pop('repoze.who.application')
102 if app is not self.app:
103 logger and logger.info(
104 'static downstream application replaced with %s' % app)
105
106 wrapper = StartResponseWrapper(start_response)
107 app_iter = app(environ, wrapper.wrap_start_response)
108
109
110
111
112
113
114 if not wrapper.called:
115 app_iter = wrap_generator(app_iter)
116
117 if self.challenge_decider(environ, wrapper.status, wrapper.headers):
118 logger and logger.info('challenge required')
119
120 challenge_app = self.challenge(
121 environ,
122 classification,
123 wrapper.status,
124 wrapper.headers,
125 identifier,
126 identity
127 )
128 if challenge_app is not None:
129 logger and logger.info('executing challenge app')
130 if app_iter:
131 list(app_iter)
132
133 app_iter = challenge_app(environ, start_response)
134 else:
135 logger and logger.info('configuration error: no challengers')
136 raise RuntimeError('no challengers found')
137 else:
138 logger and logger.info('no challenge required')
139 remember_headers = []
140 if identifier:
141 remember_headers = identifier.remember(environ, identity)
142 if remember_headers:
143 logger and logger.info('remembering via headers from %s: %s'
144 % (identifier, remember_headers))
145 wrapper.finish_response(remember_headers)
146
147 logger and logger.info(_ENDED % path_info)
148 return app_iter
149
150 - def identify(self, environ, classification):
151 logger = self.logger
152 candidates = self.registry.get(IIdentifier, ())
153 logger and self.logger.info('identifier plugins registered %s' %
154 (candidates,))
155 plugins = match_classification(IIdentifier, candidates, classification)
156 logger and self.logger.info(
157 'identifier plugins matched for '
158 'classification "%s": %s' % (classification, plugins))
159
160 results = []
161 for plugin in plugins:
162 identity = plugin.identify(environ)
163 if identity is not None:
164 logger and logger.debug(
165 'identity returned from %s: %s' % (plugin, identity))
166 results.append((plugin, identity))
167 else:
168 logger and logger.debug(
169 'no identity returned from %s (%s)' % (plugin, identity))
170
171 logger and logger.debug('identities found: %s' % (results,))
172 return results
173
180
181 - def authenticate(self, environ, classification, identities):
182 logger = self.logger
183 candidates = self.registry.get(IAuthenticator, [])
184 logger and self.logger.info('authenticator plugins registered %s' %
185 candidates)
186 plugins = match_classification(IAuthenticator, candidates,
187 classification)
188 logger and self.logger.info(
189 'authenticator plugins matched for '
190 'classification "%s": %s' % (classification, plugins))
191
192
193 identities, results, id_rank_start =self._filter_preauthenticated(
194 identities)
195
196 auth_rank = 0
197
198 for plugin in plugins:
199 identifier_rank = id_rank_start
200 for identifier, identity in identities:
201 userid = plugin.authenticate(environ, identity)
202 if userid is not None:
203 logger and logger.debug(
204 'userid returned from %s: "%s"' % (plugin, userid))
205
206
207 identity['repoze.who.userid'] = userid
208 rank = (auth_rank, identifier_rank)
209 results.append(
210 (rank, plugin, identifier, identity, userid)
211 )
212 else:
213 logger and logger.debug(
214 'no userid returned from %s: (%s)' % (
215 plugin, userid))
216 identifier_rank += 1
217 auth_rank += 1
218
219 logger and logger.debug('identities authenticated: %s' % (results,))
220 return results
221
223 logger = self.logger
224 results = []
225 new_identities = identities[:]
226
227 identifier_rank = 0
228 for thing in identities:
229 identifier, identity = thing
230 userid = identity.get('repoze.who.userid')
231 if userid is not None:
232
233
234 logger and logger.info(
235 'userid preauthenticated by %s: "%s" '
236 '(repoze.who.userid set)' % (identifier, userid)
237 )
238 rank = (0, identifier_rank)
239 results.append(
240 (rank, None, identifier, identity, userid)
241 )
242 identifier_rank += 1
243 new_identities.remove(thing)
244 return new_identities, results, identifier_rank
245
246 - def challenge(self, environ, classification, status, app_headers,
247 identifier, identity):
248
249 logger = self.logger
250
251 forget_headers = []
252
253 if identifier:
254 forget_headers = identifier.forget(environ, identity)
255 if forget_headers is None:
256 forget_headers = []
257 else:
258 logger and logger.info('forgetting via headers from %s: %s'
259 % (identifier, forget_headers))
260
261 candidates = self.registry.get(IChallenger, ())
262 logger and logger.info('challengers registered: %s' % candidates)
263 plugins = match_classification(IChallenger,
264 candidates, classification)
265 logger and logger.info('challengers matched for '
266 'classification "%s": %s' % (classification,
267 plugins))
268 for plugin in plugins:
269 app = plugin.challenge(environ, status, app_headers,
270 forget_headers)
271 if app is not None:
272
273 logger and logger.info(
274 'challenger plugin %s "challenge" returned an app' % (
275 plugin))
276 return app
277
278
279 logger and logger.info('no challenge app returned')
280 return None
281
283 """\
284 This function returns a generator that behaves exactly the same as the
285 original. It's only difference is it pulls the first iteration off and
286 caches it to trigger any immediate side effects (in a WSGI world, this
287 ensures start_response is called).
288 """
289
290
291 for iter in result:
292 first = iter
293 break
294
295
296
297 def wrapper():
298 yield first
299 for iter in result:
300
301 yield iter
302 return wrapper()
303
305 result = []
306 for plugin in plugins:
307
308 plugin_classifications = getattr(plugin, 'classifications', {})
309 iface_classifications = plugin_classifications.get(iface)
310 if not iface_classifications:
311 result.append(plugin)
312 continue
313 if classification in iface_classifications:
314 result.append(plugin)
315
316 return result
317
320 self.start_response = start_response
321 self.status = None
322 self.headers = []
323 self.exc_info = None
324 self.buffer = StringIO()
325
326
327
328 self.called = False
329
331 self.headers = headers
332 self.status = status
333 self.exc_info = exc_info
334
335 self.called = True
336 return self.buffer.write
337
339 if not extra_headers:
340 extra_headers = []
341 headers = self.headers + extra_headers
342 write = self.start_response(self.status, headers, self.exc_info)
343 if write:
344 self.buffer.seek(0)
345 value = self.buffer.getvalue()
346 if value:
347 write(value)
348 if hasattr(write, 'close'):
349 write.close()
350
352 """ Functionally equivalent to
353
354 [plugin:form]
355 use = repoze.who.plugins.form.FormPlugin
356 rememberer_name = cookie
357 login_form_qs=__do_login
358
359 [plugin:cookie]
360 use = repoze.who.plugins.cookie:InsecureCookiePlugin
361 cookie_name = oatmeal
362
363 [plugin:basicauth]
364 use = repoze.who.plugins.basicauth.BasicAuthPlugin
365 realm = repoze.who
366
367 [plugin:htpasswd]
368 use = repoze.who.plugins.htpasswd.HTPasswdPlugin
369 filename = <...>
370 check_fn = repoze.who.plugins.htpasswd:crypt_check
371
372 [general]
373 request_classifier = repoze.who.classifiers:default_request_classifier
374 challenge_decider = repoze.who.classifiers:default_challenge_decider
375
376 [identifiers]
377 plugins = form:browser cookie basicauth
378
379 [authenticators]
380 plugins = htpasswd
381
382 [challengers]
383 plugins = form:browser basicauth
384 """
385
386 from repoze.who.plugins.basicauth import BasicAuthPlugin
387 from repoze.who.plugins.auth_tkt import AuthTktCookiePlugin
388 from repoze.who.plugins.cookie import InsecureCookiePlugin
389 from repoze.who.plugins.form import FormPlugin
390 from repoze.who.plugins.htpasswd import HTPasswdPlugin
391 io = StringIO()
392 salt = 'aa'
393 for name, password in [ ('admin', 'admin'), ('chris', 'chris') ]:
394 io.write('%s:%s\n' % (name, password))
395 io.seek(0)
396 def cleartext_check(password, hashed):
397 return password == hashed
398 htpasswd = HTPasswdPlugin(io, cleartext_check)
399 basicauth = BasicAuthPlugin('repoze.who')
400 auth_tkt = AuthTktCookiePlugin('secret', 'auth_tkt')
401 cookie = InsecureCookiePlugin('oatmeal')
402 form = FormPlugin('__do_login', rememberer_name='auth_tkt')
403 form.classifications = { IIdentifier:['browser'],
404 IChallenger:['browser'] }
405 identifiers = [('form', form),('auth_tkt',auth_tkt),('basicauth',basicauth)]
406 authenticators = [('htpasswd', htpasswd)]
407 challengers = [('form',form), ('basicauth',basicauth)]
408 mdproviders = []
409 from repoze.who.classifiers import default_request_classifier
410 from repoze.who.classifiers import default_challenge_decider
411 log_stream = None
412 import os
413 if os.environ.get('WHO_LOG'):
414 log_stream = sys.stdout
415 middleware = PluggableAuthenticationMiddleware(
416 app,
417 identifiers,
418 authenticators,
419 challengers,
420 mdproviders,
421 default_request_classifier,
422 default_challenge_decider,
423 log_stream = log_stream,
424 log_level = logging.DEBUG
425 )
426 return middleware
427
429 from zope.interface.verify import verifyObject
430 verifyObject(iface, plugin, tentative=True)
431
432 -def make_registries(identifiers, authenticators, challengers, mdproviders):
433 from zope.interface.verify import BrokenImplementation
434 interface_registry = {}
435 name_registry = {}
436
437 for supplied, iface in [ (identifiers, IIdentifier),
438 (authenticators, IAuthenticator),
439 (challengers, IChallenger),
440 (mdproviders, IMetadataProvider)]:
441
442 for name, value in supplied:
443 try:
444 verify(value, iface)
445 except BrokenImplementation, why:
446 why = str(why)
447 raise ValueError(str(name) + ': ' + why)
448 L = interface_registry.setdefault(iface, [])
449 L.append(value)
450 name_registry[name] = value
451
452 return interface_registry, name_registry
453
455 """ dict subclass that prevents its members from being rendered
456 during print """
458 return '<repoze.who identity (hidden, dict-like) at %s>' % id(self)
459 __str__ = __repr__
460