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