Tres Seaver
2014-12-12 d13829ff076df562ea3b0e7f0a982735cec4bc00
commit | author | age
f7efc0 1 import unittest
TS 2
9c1e6f 3 class _Base(unittest.TestCase):
TS 4
5     def failUnless(self, predicate, message=''):
6         self.assertTrue(predicate, message) # Nannies go home!
7
8 class TestWhoConfig(_Base):
f7efc0 9
TS 10     def _getTargetClass(self):
11         from repoze.who.config import WhoConfig
12         return WhoConfig
13
14     def _makeOne(self, here='/', *args, **kw):
15         return self._getTargetClass()(here, *args, **kw)
16
17     def _getDummyPluginClass(self, iface):
18         from zope.interface import classImplements
19         if not iface.implementedBy(DummyPlugin):
20             classImplements(DummyPlugin, iface)
21         return DummyPlugin
22
23     def test_defaults_before_parse(self):
24         config = self._makeOne()
25         self.assertEqual(config.request_classifier, None)
26         self.assertEqual(config.challenge_decider, None)
27         self.assertEqual(config.remote_user_key, 'REMOTE_USER')
28         self.assertEqual(len(config.plugins), 0)
29         self.assertEqual(len(config.identifiers), 0)
30         self.assertEqual(len(config.authenticators), 0)
31         self.assertEqual(len(config.challengers), 0)
32         self.assertEqual(len(config.mdproviders), 0)
33
34     def test_parse_empty_string(self):
35         config = self._makeOne()
36         config.parse('')
37         self.assertEqual(config.request_classifier, None)
38         self.assertEqual(config.challenge_decider, None)
39         self.assertEqual(config.remote_user_key, 'REMOTE_USER')
40         self.assertEqual(len(config.plugins), 0)
41         self.assertEqual(len(config.identifiers), 0)
42         self.assertEqual(len(config.authenticators), 0)
43         self.assertEqual(len(config.challengers), 0)
44         self.assertEqual(len(config.mdproviders), 0)
45
46     def test_parse_empty_file(self):
adef05 47         from repoze.who._compat import StringIO
f7efc0 48         config = self._makeOne()
TS 49         config.parse(StringIO())
50         self.assertEqual(config.request_classifier, None)
51         self.assertEqual(config.challenge_decider, None)
52         self.assertEqual(config.remote_user_key, 'REMOTE_USER')
53         self.assertEqual(len(config.plugins), 0)
54         self.assertEqual(len(config.identifiers), 0)
55         self.assertEqual(len(config.authenticators), 0)
56         self.assertEqual(len(config.challengers), 0)
57         self.assertEqual(len(config.mdproviders), 0)
58
59     def test_parse_plugins(self):
60         config = self._makeOne()
61         config.parse(PLUGINS_ONLY)
62         self.assertEqual(len(config.plugins), 2)
63         self.failUnless(isinstance(config.plugins['foo'],
64                                    DummyPlugin))
65         bar = config.plugins['bar']
66         self.failUnless(isinstance(bar, DummyPlugin))
67         self.assertEqual(bar.credentials, 'qux')
68
69     def test_parse_general_empty(self):
70         config = self._makeOne()
71         config.parse('[general]')
72         self.assertEqual(config.request_classifier, None)
73         self.assertEqual(config.challenge_decider, None)
74         self.assertEqual(config.remote_user_key, 'REMOTE_USER')
75         self.assertEqual(len(config.plugins), 0)
76
77     def test_parse_general_only(self):
78         from repoze.who.interfaces import IRequestClassifier
79         from repoze.who.interfaces import IChallengeDecider
80         class IDummy(IRequestClassifier, IChallengeDecider):
81             pass
82         PLUGIN_CLASS = self._getDummyPluginClass(IDummy)
83         config = self._makeOne()
84         config.parse(GENERAL_ONLY)
85         self.failUnless(isinstance(config.request_classifier, PLUGIN_CLASS))
86         self.failUnless(isinstance(config.challenge_decider, PLUGIN_CLASS))
87         self.assertEqual(config.remote_user_key, 'ANOTHER_REMOTE_USER')
88         self.assertEqual(len(config.plugins), 0)
89
90     def test_parse_general_with_plugins(self):
91         from repoze.who.interfaces import IRequestClassifier
92         from repoze.who.interfaces import IChallengeDecider
93         class IDummy(IRequestClassifier, IChallengeDecider):
94             pass
95         PLUGIN_CLASS = self._getDummyPluginClass(IDummy)
96         config = self._makeOne()
97         config.parse(GENERAL_WITH_PLUGINS)
98         self.failUnless(isinstance(config.request_classifier, PLUGIN_CLASS))
99         self.failUnless(isinstance(config.challenge_decider, PLUGIN_CLASS))
100
101     def test_parse_identifiers_only(self):
102         from repoze.who.interfaces import IIdentifier
103         PLUGIN_CLASS = self._getDummyPluginClass(IIdentifier)
104         config = self._makeOne()
105         config.parse(IDENTIFIERS_ONLY)
106         identifiers = config.identifiers
107         self.assertEqual(len(identifiers), 2)
108         first, second = identifiers
109         self.assertEqual(first[0], 'repoze.who.tests.test_config:DummyPlugin')
110         self.failUnless(isinstance(first[1], PLUGIN_CLASS))
111         self.assertEqual(len(first[1].classifications), 1)
112         self.assertEqual(first[1].classifications[IIdentifier], 'klass1')
113         self.assertEqual(second[0], 'repoze.who.tests.test_config:DummyPlugin')
114         self.failUnless(isinstance(second[1], PLUGIN_CLASS))
115
116     def test_parse_identifiers_with_plugins(self):
117         from repoze.who.interfaces import IIdentifier
118         PLUGIN_CLASS = self._getDummyPluginClass(IIdentifier)
119         config = self._makeOne()
120         config.parse(IDENTIFIERS_WITH_PLUGINS)
121         identifiers = config.identifiers
122         self.assertEqual(len(identifiers), 2)
123         first, second = identifiers
124         self.assertEqual(first[0], 'foo')
125         self.failUnless(isinstance(first[1], PLUGIN_CLASS))
126         self.assertEqual(len(first[1].classifications), 1)
127         self.assertEqual(first[1].classifications[IIdentifier], 'klass1')
128         self.assertEqual(second[0], 'bar')
129         self.failUnless(isinstance(second[1], PLUGIN_CLASS))
130
131     def test_parse_authenticators_only(self):
132         from repoze.who.interfaces import IAuthenticator
133         PLUGIN_CLASS = self._getDummyPluginClass(IAuthenticator)
134         config = self._makeOne()
135         config.parse(AUTHENTICATORS_ONLY)
136         authenticators = config.authenticators
137         self.assertEqual(len(authenticators), 2)
138         first, second = authenticators
139         self.assertEqual(first[0], 'repoze.who.tests.test_config:DummyPlugin')
140         self.failUnless(isinstance(first[1], PLUGIN_CLASS))
141         self.assertEqual(len(first[1].classifications), 1)
142         self.assertEqual(first[1].classifications[IAuthenticator], 'klass1')
143         self.assertEqual(second[0], 'repoze.who.tests.test_config:DummyPlugin')
144         self.failUnless(isinstance(second[1], PLUGIN_CLASS))
145
146     def test_parse_authenticators_with_plugins(self):
147         from repoze.who.interfaces import IAuthenticator
148         PLUGIN_CLASS = self._getDummyPluginClass(IAuthenticator)
149         config = self._makeOne()
150         config.parse(AUTHENTICATORS_WITH_PLUGINS)
151         authenticators = config.authenticators
152         self.assertEqual(len(authenticators), 2)
153         first, second = authenticators
154         self.assertEqual(first[0], 'foo')
155         self.failUnless(isinstance(first[1], PLUGIN_CLASS))
156         self.assertEqual(len(first[1].classifications), 1)
157         self.assertEqual(first[1].classifications[IAuthenticator], 'klass1')
158         self.assertEqual(second[0], 'bar')
159         self.failUnless(isinstance(second[1], PLUGIN_CLASS))
160
161     def test_parse_challengers_only(self):
162         from repoze.who.interfaces import IChallenger
163         PLUGIN_CLASS = self._getDummyPluginClass(IChallenger)
164         config = self._makeOne()
165         config.parse(CHALLENGERS_ONLY)
166         challengers = config.challengers
167         self.assertEqual(len(challengers), 2)
168         first, second = challengers
169         self.assertEqual(first[0], 'repoze.who.tests.test_config:DummyPlugin')
170         self.failUnless(isinstance(first[1], PLUGIN_CLASS))
171         self.assertEqual(len(first[1].classifications), 1)
172         self.assertEqual(first[1].classifications[IChallenger], 'klass1')
173         self.assertEqual(second[0], 'repoze.who.tests.test_config:DummyPlugin')
174         self.failUnless(isinstance(second[1], PLUGIN_CLASS))
175
176     def test_parse_challengers_with_plugins(self):
177         from repoze.who.interfaces import IChallenger
178         PLUGIN_CLASS = self._getDummyPluginClass(IChallenger)
179         config = self._makeOne()
180         config.parse(CHALLENGERS_WITH_PLUGINS)
181         challengers = config.challengers
182         self.assertEqual(len(challengers), 2)
183         first, second = challengers
184         self.assertEqual(first[0], 'foo')
185         self.failUnless(isinstance(first[1], PLUGIN_CLASS))
186         self.assertEqual(len(first[1].classifications), 1)
187         self.assertEqual(first[1].classifications[IChallenger], 'klass1')
188         self.assertEqual(second[0], 'bar')
189         self.failUnless(isinstance(second[1], PLUGIN_CLASS))
190
191     def test_parse_mdproviders_only(self):
192         from repoze.who.interfaces import IMetadataProvider
193         PLUGIN_CLASS = self._getDummyPluginClass(IMetadataProvider)
194         config = self._makeOne()
195         config.parse(MDPROVIDERS_ONLY)
196         mdproviders = config.mdproviders
197         self.assertEqual(len(mdproviders), 2)
198         first, second = mdproviders
199         self.assertEqual(first[0], 'repoze.who.tests.test_config:DummyPlugin')
200         self.failUnless(isinstance(first[1], PLUGIN_CLASS))
201         self.assertEqual(len(first[1].classifications), 1)
202         self.assertEqual(first[1].classifications[IMetadataProvider], 'klass1')
203         self.assertEqual(second[0], 'repoze.who.tests.test_config:DummyPlugin')
204         self.failUnless(isinstance(second[1], PLUGIN_CLASS))
205
206     def test_parse_mdproviders_with_plugins(self):
207         from repoze.who.interfaces import IMetadataProvider
208         PLUGIN_CLASS = self._getDummyPluginClass(IMetadataProvider)
209         config = self._makeOne()
210         config.parse(MDPROVIDERS_WITH_PLUGINS)
211         mdproviders = config.mdproviders
212         self.assertEqual(len(mdproviders), 2)
213         first, second = mdproviders
214         self.assertEqual(first[0], 'foo')
215         self.failUnless(isinstance(first[1], PLUGIN_CLASS))
216         self.assertEqual(len(first[1].classifications), 1)
217         self.assertEqual(first[1].classifications[IMetadataProvider], 'klass1')
218         self.assertEqual(second[0], 'bar')
219         self.failUnless(isinstance(second[1], PLUGIN_CLASS))
220
21a9c5 221     def test_parse_make_plugin_names(self):
CM 222         # see http://bugs.repoze.org/issue92
223         config = self._makeOne()
224         config.parse(MAKE_PLUGIN_ARG_NAMES)
225         self.assertEqual(len(config.plugins), 1)
226         foo = config.plugins['foo']
227         self.failUnless(isinstance(foo, DummyPlugin))
228         self.assertEqual(foo.iface, 'iface')
229         self.assertEqual(foo.name, 'name')
1d7c19 230         self.assertEqual(foo.template, '%(template)s')
TS 231         self.assertEqual(foo.template_with_eq,
232                          'template_with_eq = %(template_with_eq)s')
21a9c5 233
f7efc0 234 class DummyPlugin:
TS 235     def __init__(self, **kw):
236         self.__dict__.update(kw)
237
238 PLUGINS_ONLY = """\
239 [plugin:foo]
240 use = repoze.who.tests.test_config:DummyPlugin
241
242 [plugin:bar]
243 use = repoze.who.tests.test_config:DummyPlugin
244 credentials = qux
245 """
246
247 GENERAL_ONLY = """\
248 [general]
249 request_classifier = repoze.who.tests.test_config:DummyPlugin
250 challenge_decider = repoze.who.tests.test_config:DummyPlugin
251 remote_user_key = ANOTHER_REMOTE_USER
252 """
253
254 GENERAL_WITH_PLUGINS = """\
255 [general]
256 request_classifier = classifier
257 challenge_decider = decider
258
259 [plugin:classifier]
260 use = repoze.who.tests.test_config:DummyPlugin
261
262 [plugin:decider]
263 use = repoze.who.tests.test_config:DummyPlugin
264 """
265
266 IDENTIFIERS_ONLY = """\
267 [identifiers]
268 plugins =
269     repoze.who.tests.test_config:DummyPlugin;klass1
270     repoze.who.tests.test_config:DummyPlugin
271 """
272
273 IDENTIFIERS_WITH_PLUGINS = """\
274 [identifiers]
275 plugins =
276     foo;klass1
277     bar
278
279 [plugin:foo]
280 use = repoze.who.tests.test_config:DummyPlugin
281
282 [plugin:bar]
283 use = repoze.who.tests.test_config:DummyPlugin
284 """
285
286 AUTHENTICATORS_ONLY = """\
287 [authenticators]
288 plugins =
289     repoze.who.tests.test_config:DummyPlugin;klass1
290     repoze.who.tests.test_config:DummyPlugin
291 """
292
293 AUTHENTICATORS_WITH_PLUGINS = """\
294 [authenticators]
295 plugins =
296     foo;klass1
297     bar
298
299 [plugin:foo]
300 use = repoze.who.tests.test_config:DummyPlugin
301
302 [plugin:bar]
303 use = repoze.who.tests.test_config:DummyPlugin
304 """
305
306 CHALLENGERS_ONLY = """\
307 [challengers]
308 plugins =
309     repoze.who.tests.test_config:DummyPlugin;klass1
310     repoze.who.tests.test_config:DummyPlugin
311 """
312
313 CHALLENGERS_WITH_PLUGINS = """\
314 [challengers]
315 plugins =
316     foo;klass1
317     bar
318
319 [plugin:foo]
320 use = repoze.who.tests.test_config:DummyPlugin
321
322 [plugin:bar]
323 use = repoze.who.tests.test_config:DummyPlugin
324 """
325
326 MDPROVIDERS_ONLY = """\
327 [mdproviders]
328 plugins =
329     repoze.who.tests.test_config:DummyPlugin;klass1
330     repoze.who.tests.test_config:DummyPlugin
331 """
332
333 MDPROVIDERS_WITH_PLUGINS = """\
334 [mdproviders]
335 plugins =
336     foo;klass1
337     bar
338
339 [plugin:foo]
340 use = repoze.who.tests.test_config:DummyPlugin
341
342 [plugin:bar]
343 use = repoze.who.tests.test_config:DummyPlugin
344 """
345
21a9c5 346 MAKE_PLUGIN_ARG_NAMES = """\
CM 347 [plugin:foo]
348 use = repoze.who.tests.test_config:DummyPlugin
349 name = name
350 iface = iface
1d7c19 351 template = %%(template)s
TS 352 template_with_eq = template_with_eq = %%(template_with_eq)s
21a9c5 353 """
CM 354
9c1e6f 355 class TestConfigMiddleware(_Base):
03fba8 356     _tempdir = None
f7efc0 357
TS 358     def setUp(self):
359         pass
360
361     def tearDown(self):
03fba8 362         if self._tempdir is not None:
1810b2 363             import shutil
03fba8 364             shutil.rmtree(self._tempdir)
f7efc0 365
TS 366     def _getFactory(self):
367         from repoze.who.config import make_middleware_with_config
368         return make_middleware_with_config
369
370     def _getTempfile(self, text):
1810b2 371         import os
f7efc0 372         import tempfile
03fba8 373         tempdir = self._tempdir = tempfile.mkdtemp()
1810b2 374         path = os.path.join(tempdir, 'who.ini')
TS 375         config = open(path, 'w')
376         config.write(text)
377         config.flush()
378         config.close()
379         return path
f7efc0 380
TS 381     def test_sample_config(self):
1810b2 382         import logging
TS 383         app = DummyApp()
384         factory = self._getFactory()
385         path = self._getTempfile(SAMPLE_CONFIG)
386         global_conf = {'here': '/'}
387         middleware = factory(app, global_conf, config_file=path,
388                              log_file='STDOUT', log_level='debug')
993216 389         api_factory = middleware.api_factory
d7f613 390         self.assertEqual(len(api_factory.identifiers), 2)
993216 391         self.assertEqual(len(api_factory.authenticators), 1)
TS 392         self.assertEqual(len(api_factory.challengers), 2)
393         self.assertEqual(len(api_factory.mdproviders), 0)
f7efc0 394         self.failUnless(middleware.logger, middleware.logger)
TS 395         self.assertEqual(middleware.logger.getEffectiveLevel(), logging.DEBUG)
396
3b9db8 397     def test_sample_config_no_log_level(self):
1810b2 398         import logging
3b9db8 399         app = DummyApp()
TS 400         factory = self._getFactory()
1810b2 401         path = self._getTempfile(SAMPLE_CONFIG)
30956b 402         global_conf = {'here': '/'}
1810b2 403         middleware = factory(app, global_conf, config_file=path,
3b9db8 404                              log_file='STDOUT')
TS 405         self.assertEqual(middleware.logger.getEffectiveLevel(), logging.INFO)
406
69f663 407     def test_sample_config_w_log_file(self):
1810b2 408         import logging
TS 409         import os
69f663 410         app = DummyApp()
TS 411         factory = self._getFactory()
1810b2 412         path = self._getTempfile(SAMPLE_CONFIG)
03fba8 413         logfile = os.path.join(self._tempdir, 'who.log')
69f663 414         global_conf = {'here': '/'}
1810b2 415         middleware = factory(app, global_conf, config_file=path,
e05ebf 416                              log_file=logfile, log_level=logging.WARN)
TS 417         self.assertEqual(middleware.logger.getEffectiveLevel(), logging.WARN)
d6b53f 418         handlers = middleware.logger.handlers
TS 419         self.assertEqual(len(handlers), 1)
420         self.failUnless(isinstance(handlers[0], logging.StreamHandler))
421         self.assertEqual(handlers[0].stream.name, logfile)
1810b2 422         logging.shutdown()
5bdad5 423         handlers[0].stream.close()
69f663 424
d6b53f 425     def test_sample_config_wo_log_file(self):
TS 426         import logging
427         from repoze.who.config import NullHandler
428         app = DummyApp()
429         factory = self._getFactory()
430         path = self._getTempfile(SAMPLE_CONFIG)
431         global_conf = {'here': '/'}
432         middleware = factory(app, global_conf, config_file=path)
e05ebf 433         self.assertEqual(middleware.logger.getEffectiveLevel(), logging.INFO)
d6b53f 434         handlers = middleware.logger.handlers
TS 435         self.assertEqual(len(handlers), 1)
436         self.failUnless(isinstance(handlers[0], NullHandler))
437         logging.shutdown()
438
9c1e6f 439 class NullHandlerTests(_Base):
d6b53f 440
TS 441     def _getTargetClass(self):
442         from repoze.who.config import NullHandler
443         return NullHandler
444
445     def _makeOne(self):
446         return self._getTargetClass()()
447
448     def test_inheritance(self):
449         import logging
450         handler = self._makeOne()
451         self.failUnless(isinstance(handler, logging.Handler))
452
453     def test_emit_doesnt_raise_NotImplementedError(self):
454         handler = self._makeOne()
455         handler.emit(object())
456
9c1e6f 457 class Test_make_api_factory_with_config(_Base):
03fba8 458     _tempdir = None
a349a2 459
TS 460     def setUp(self):
461         pass
462
463     def tearDown(self):
03fba8 464         if self._tempdir is not None:
a349a2 465             import shutil
03fba8 466             shutil.rmtree(self._tempdir)
TS 467
a349a2 468     def _getFactory(self):
TS 469         from repoze.who.config import make_api_factory_with_config
470         return make_api_factory_with_config
471
472     def _getTempfile(self, text):
473         import os
474         import tempfile
03fba8 475         tempdir = self._tempdir = tempfile.mkdtemp()
a349a2 476         path = os.path.join(tempdir, 'who.ini')
TS 477         config = open(path, 'w')
478         config.write(text)
479         config.flush()
480         config.close()
481         return path
482
03fba8 483     def test_bad_config_filename(self):
679dbc 484         import warnings
TS 485         with warnings.catch_warnings(record=True) as warned:
486             factory = self._getFactory()
487             path = '/nonesuch/file/should/exist'
488             global_conf = {'here': '/'}
489             api_factory = factory(global_conf, config_file=path)
490             self.assertEqual(len(api_factory.identifiers), 0)
491             self.assertEqual(len(api_factory.authenticators), 0)
492             self.assertEqual(len(api_factory.challengers), 0)
493             self.assertEqual(len(api_factory.mdproviders), 0)
494             self.assertEqual(api_factory.remote_user_key, 'REMOTE_USER')
495             self.failUnless(api_factory.logger is None)
496             self.failUnless(warned)
03fba8 497
TS 498     def test_bad_config_content(self):
679dbc 499         import warnings
TS 500         with warnings.catch_warnings(record=True) as warned:
501             factory = self._getFactory()
502             path = self._getTempfile('this is not an INI file')
503             global_conf = {'here': '/'}
504             api_factory = factory(global_conf, config_file=path)
505             self.assertEqual(len(api_factory.identifiers), 0)
506             self.assertEqual(len(api_factory.authenticators), 0)
507             self.assertEqual(len(api_factory.challengers), 0)
508             self.assertEqual(len(api_factory.mdproviders), 0)
509             self.assertEqual(api_factory.remote_user_key, 'REMOTE_USER')
510             self.failUnless(api_factory.logger is None)
511             self.failUnless(warned)
03fba8 512
a349a2 513     def test_sample_config_no_logger(self):
TS 514         factory = self._getFactory()
515         path = self._getTempfile(SAMPLE_CONFIG)
516         global_conf = {'here': '/'}
517         api_factory = factory(global_conf, config_file=path)
d7f613 518         self.assertEqual(len(api_factory.identifiers), 2)
a349a2 519         self.assertEqual(len(api_factory.authenticators), 1)
TS 520         self.assertEqual(len(api_factory.challengers), 2)
521         self.assertEqual(len(api_factory.mdproviders), 0)
522         self.assertEqual(api_factory.remote_user_key, 'REMOTE_USER')
523         self.failUnless(api_factory.logger is None)
524
525     def test_sample_config_w_remote_user_key(self):
526         factory = self._getFactory()
527         path = self._getTempfile(SAMPLE_CONFIG)
528         global_conf = {'here': '/'}
529         api_factory = factory(global_conf, config_file=path,
530                               remote_user_key = 'X-OTHER-USER')
d7f613 531         self.assertEqual(len(api_factory.identifiers), 2)
a349a2 532         self.assertEqual(len(api_factory.authenticators), 1)
TS 533         self.assertEqual(len(api_factory.challengers), 2)
534         self.assertEqual(len(api_factory.mdproviders), 0)
535         self.assertEqual(api_factory.remote_user_key, 'X-OTHER-USER')
536
537     def test_sample_config_w_logger(self):
538         factory = self._getFactory()
539         path = self._getTempfile(SAMPLE_CONFIG)
540         global_conf = {'here': '/'}
541         logger = object()
542         api_factory = factory(global_conf, config_file=path, logger=logger)
d7f613 543         self.assertEqual(len(api_factory.identifiers), 2)
a349a2 544         self.assertEqual(len(api_factory.authenticators), 1)
TS 545         self.assertEqual(len(api_factory.challengers), 2)
546         self.assertEqual(len(api_factory.mdproviders), 0)
547         self.failUnless(api_factory.logger is logger)
548
f7efc0 549 SAMPLE_CONFIG = """\
d7f613 550 [plugin:redirector]
TS 551 use = repoze.who.plugins.redirector:make_plugin
552 login_url = /login.html
f7efc0 553
TS 554 [plugin:auth_tkt]
555 use = repoze.who.plugins.auth_tkt:make_plugin
556 secret = s33kr1t
557 cookie_name = oatmeal
558 secure = False
559 include_ip = True
560
561 [plugin:basicauth]
562 use = repoze.who.plugins.basicauth:make_plugin
563 realm = 'sample'
564
565 [plugin:htpasswd]
566 use = repoze.who.plugins.htpasswd:make_plugin
567 filename = %(here)s/etc/passwd
568 check_fn = repoze.who.plugins.htpasswd:crypt_check
569
570 [general]
571 request_classifier = repoze.who.classifiers:default_request_classifier
572 challenge_decider = repoze.who.classifiers:default_challenge_decider
573
574 [identifiers]
575 plugins =
576     auth_tkt
577     basicauth
578
579 [authenticators]
580 plugins = htpasswd
581
582 [challengers]
583 plugins =
d7f613 584     redirector;browser
f7efc0 585     basicauth
TS 586
587 [mdproviders]
588 plugins =
589
590 """
591
592 class DummyApp:
593     environ = None