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