Tres Seaver
2009-05-08 cc37ce712442521330567196001458992ad2a970
commit | author | age
496fcf 1 import unittest
TS 2
3 class TestFormPlugin(unittest.TestCase):
4
5     def _getTargetClass(self):
6         from repoze.who.plugins.form import FormPlugin
7         return FormPlugin
8
cc37ce 9     def _makeOne(self,
TS 10                  login_form_qs='__do_login',
11                  rememberer_name='cookie',
12                  formbody=None,
13                  formcallable=None,
14                 ):
496fcf 15         plugin = self._getTargetClass()(login_form_qs, rememberer_name,
cc37ce 16                                         formbody, formcallable)
496fcf 17         return plugin
TS 18
19     def _makeEnviron(self, login=None, password=None, do_login=False):
20         from StringIO import StringIO
21         fields = []
22         if login:
23             fields.append(('login', login))
24         if password:
25             fields.append(('password', password))
26         content_type, body = encode_multipart_formdata(fields)
27         credentials = {'login':'chris', 'password':'password'}
28         identifier = DummyIdentifier(credentials)
29
30         environ = {'wsgi.version': (1,0),
31                    'wsgi.input': StringIO(body),
32                    'wsgi.url_scheme': 'http',
33                    'SERVER_NAME': 'localhost',
34                    'SERVER_PORT': '8080',
35                    'CONTENT_TYPE': content_type,
36                    'CONTENT_LENGTH': len(body),
37                    'REQUEST_METHOD': 'POST',
38                    'repoze.who.plugins': {'cookie':identifier},
39                    'PATH_INFO': '/protected',
40                    'QUERY_STRING': '',
41                   }
42         if do_login:
43             environ['QUERY_STRING'] = '__do_login=true'
44         return environ
45     
46     def test_implements(self):
47         from zope.interface.verify import verifyClass
48         from repoze.who.interfaces import IIdentifier
49         from repoze.who.interfaces import IChallenger
50         klass = self._getTargetClass()
51         verifyClass(IIdentifier, klass)
52         verifyClass(IChallenger, klass)
53
54     def test_identify_noqs(self):
55         plugin = self._makeOne()
56         environ = self._makeEnviron()
57         result = plugin.identify(environ)
58         self.assertEqual(result, None)
59         
60     def test_identify_qs_no_values(self):
61         plugin = self._makeOne()
62         environ = self._makeEnviron(do_login=True)
63         result = plugin.identify(environ)
64         self.assertEqual(result, None)
65
66     def test_identify_nologin(self):
67         plugin = self._makeOne()
68         environ = self._makeEnviron(do_login=True, login='chris')
69         result = plugin.identify(environ)
70         self.assertEqual(result, None)
71     
72     def test_identify_nopassword(self):
73         plugin = self._makeOne()
74         environ = self._makeEnviron(do_login=True, password='password')
75         result = plugin.identify(environ)
76         self.assertEqual(result, None)
77
78     def test_identify_success(self):
79         from paste.httpexceptions import HTTPFound
80         plugin = self._makeOne()
81         environ = self._makeEnviron(do_login=True, login='chris',
82                                         password='password')
83         result = plugin.identify(environ)
84         self.assertEqual(result, {'login':'chris', 'password':'password'})
85         app = environ['repoze.who.application']
86         self.failUnless(isinstance(app, HTTPFound))
87         self.assertEqual(app.location(), 'http://localhost:8080/protected')
88
89     def test_remember(self):
90         plugin = self._makeOne()
91         environ = self._makeEnviron()
92         identity = {}
93         result = plugin.remember(environ, identity)
94         self.assertEqual(result, None)
95         self.assertEqual(environ['repoze.who.plugins']['cookie'].remembered,
96                          identity)
97
98     def test_forget(self):
99         plugin = self._makeOne()
100         environ = self._makeEnviron()
101         identity = {}
102         result = plugin.forget(environ, identity)
103         self.assertEqual(result, None)
104         self.assertEqual(environ['repoze.who.plugins']['cookie'].forgotten,
105                          identity
106                          )
107
108     def test_challenge_defaultform(self):
109         from repoze.who.plugins.form import _DEFAULT_FORM
110         plugin = self._makeOne()
111         environ = self._makeEnviron()
112         app = plugin.challenge(environ, '401 Unauthorized', [], [])
113         sr = DummyStartResponse()
114         result = app(environ, sr)
115         self.assertEqual(''.join(result), _DEFAULT_FORM)
116         self.assertEqual(len(sr.headers), 2)
117         cl = str(len(_DEFAULT_FORM))
118         self.assertEqual(sr.headers[0], ('Content-Length', cl))
119         self.assertEqual(sr.headers[1], ('Content-Type', 'text/html'))
120         self.assertEqual(sr.status, '200 OK')
121
122     def test_challenge_customform(self):
123         import os
124         here = os.path.dirname(__file__)
125         fixtures = os.path.join(here, 'fixtures')
126         form = os.path.join(fixtures, 'form.html')
127         formbody = open(form).read()
128         plugin = self._makeOne(formbody=formbody)
129         environ = self._makeEnviron()
130         app = plugin.challenge(environ, '401 Unauthorized', [], [])
131         sr = DummyStartResponse()
132         result = app(environ, sr)
133         self.assertEqual(''.join(result), formbody)
134         self.assertEqual(len(sr.headers), 2)
135         cl = str(len(formbody))
136         self.assertEqual(sr.headers[0], ('Content-Length', cl))
137         self.assertEqual(sr.headers[1], ('Content-Type', 'text/html'))
138         self.assertEqual(sr.status, '200 OK')
139
cc37ce 140     def test_challenge_formcallable(self):
TS 141         def _formcallable(environ):
142             return 'formcallable'
143         plugin = self._makeOne(formcallable=_formcallable)
144         environ = self._makeEnviron()
145         app = plugin.challenge(environ, '401 Unauthorized', [], [])
146         sr = DummyStartResponse()
147         result = app(environ, sr)
148         self.assertEqual(result, ['formcallable'])
149
496fcf 150     def test_challenge_with_location(self):
TS 151         plugin = self._makeOne()
152         environ = self._makeEnviron()
153         app = plugin.challenge(environ, '401 Unauthorized',
154                                [('Location', 'http://foo/bar')],
155                                [('Set-Cookie', 'a=123')])
156         sr = DummyStartResponse()
157         app(environ, sr)
158         headers = sorted(sr.headers)
159         self.assertEqual(len(headers), 3)
160         self.assertEqual(headers[0], ('Location', 'http://foo/bar'))
161         self.assertEqual(headers[1],
162                          ('Set-Cookie', 'a=123'))
163         self.assertEqual(headers[2],
164                          ('content-type', 'text/plain; charset=utf8'))
165         self.assertEqual(sr.status, '302 Found')
cc37ce 166
TS 167     def test_factory_no_rememberer_name_raises(self):
168         from repoze.who.plugins.form import make_plugin
169         self.assertRaises(ValueError, make_plugin)
496fcf 170
TS 171     def test_factory_withform(self):
172         import os
173         from repoze.who.plugins.form import make_plugin
174         here = os.path.dirname(__file__)
175         fixtures = os.path.join(here, 'fixtures')
176         form = os.path.join(fixtures, 'form.html')
177         formbody = open(form).read()
178         plugin = make_plugin('__login', 'cookie', form)
179         self.assertEqual(plugin.login_form_qs, '__login')
180         self.assertEqual(plugin.rememberer_name, 'cookie')
181         self.assertEqual(plugin.formbody, formbody)
182
183     def test_factory_defaultform(self):
184         from repoze.who.plugins.form import make_plugin
185         plugin = make_plugin('__login', 'cookie')
186         self.assertEqual(plugin.login_form_qs, '__login')
187         self.assertEqual(plugin.rememberer_name, 'cookie')
188         self.assertEqual(plugin.formbody, None)
189
190 class TestRedirectingFormPlugin(unittest.TestCase):
191
192     def _getTargetClass(self):
193         from repoze.who.plugins.form import RedirectingFormPlugin
194         return RedirectingFormPlugin
195
196     def _makeOne(self, login_form_url='http://example.com/login.html',
197                  login_handler_path = '/login_handler',
198                  logout_handler_path = '/logout_handler',
199                  rememberer_name='cookie',
200                  reason_param='reason'):
201         plugin = self._getTargetClass()(login_form_url, login_handler_path,
202                                         logout_handler_path,
203                                         rememberer_name, reason_param)
204         return plugin
205
206     def _makeEnviron(self, login=None, password=None, came_from=None,
207                          path_info='/', identifier=None):
208         from StringIO import StringIO
209         fields = []
210         if login:
211             fields.append(('login', login))
212         if password:
213             fields.append(('password', password))
214         if came_from:
215             fields.append(('came_from', came_from))
216         if identifier is None:
217             credentials = {'login':'chris', 'password':'password'}
218             identifier = DummyIdentifier(credentials)
219         content_type, body = encode_multipart_formdata(fields)
220         environ = {'wsgi.version': (1,0),
221                    'wsgi.input': StringIO(body),
222                    'wsgi.url_scheme':'http',
223                    'SERVER_NAME': 'www.example.com',
224                    'SERVER_PORT': '80',
225                    'CONTENT_TYPE': content_type,
226                    'CONTENT_LENGTH': len(body),
227                    'REQUEST_METHOD': 'POST',
228                    'repoze.who.plugins': {'cookie':identifier},
229                    'QUERY_STRING': 'default=1',
230                    'PATH_INFO': path_info,
231                   }
232         return environ
233     
234     def test_implements(self):
235         from zope.interface.verify import verifyClass
236         from repoze.who.interfaces import IIdentifier
237         from repoze.who.interfaces import IChallenger
238         klass = self._getTargetClass()
239         verifyClass(IIdentifier, klass)
240         verifyClass(IChallenger, klass)
241
242     def test_identify_pathinfo_miss(self):
243         plugin = self._makeOne()
244         environ = self._makeEnviron(path_info='/not_login_handler')
245         result = plugin.identify(environ)
246         self.assertEqual(result, None)
247         self.failIf(environ.get('repoze.who.application'))
248         
249     def test_identify_via_login_handler(self):
250         plugin = self._makeOne()
251         environ = self._makeEnviron(path_info='/login_handler',
252                                         login='chris',
253                                         password='password',
254                                         came_from='http://example.com')
255         result = plugin.identify(environ)
256         self.assertEqual(result, {'login':'chris', 'password':'password'})
257         app = environ['repoze.who.application']
258         self.assertEqual(len(app.headers), 1)
259         name, value = app.headers[0]
260         self.assertEqual(name, 'location')
261         self.assertEqual(value, 'http://example.com')
262         self.assertEqual(app.code, 302)
263
264     def test_identify_via_login_handler_no_username_pass(self):
265         plugin = self._makeOne()
266         environ = self._makeEnviron(path_info='/login_handler')
267         result = plugin.identify(environ)
268         self.assertEqual(result, None)
269         app = environ['repoze.who.application']
270         self.assertEqual(len(app.headers), 1)
271         name, value = app.headers[0]
272         self.assertEqual(name, 'location')
273         self.assertEqual(value, '/')
274         self.assertEqual(app.code, 302)
275
276     def test_identify_via_login_handler_no_came_from_no_http_referer(self):
277         plugin = self._makeOne()
278         environ = self._makeEnviron(path_info='/login_handler',
279                                         login='chris',
280                                         password='password')
281         result = plugin.identify(environ)
282         self.assertEqual(result, {'login':'chris', 'password':'password'})
283         app = environ['repoze.who.application']
284         self.assertEqual(len(app.headers), 1)
285         name, value = app.headers[0]
286         self.assertEqual(name, 'location')
287         self.assertEqual(value, '/')
288         self.assertEqual(app.code, 302)
289
290     def test_identify_via_login_handler_no_came_from(self):
291         plugin = self._makeOne()
292         environ = self._makeEnviron(path_info='/login_handler',
293                                         login='chris',
294                                         password='password')
295         environ['HTTP_REFERER'] = 'http://foo.bar'
296         result = plugin.identify(environ)
297         self.assertEqual(result, {'login':'chris', 'password':'password'})
298         app = environ['repoze.who.application']
299         self.assertEqual(len(app.headers), 1)
300         name, value = app.headers[0]
301         self.assertEqual(name, 'location')
302         self.assertEqual(value, 'http://foo.bar')
303         self.assertEqual(app.code, 302)
304
305     def test_identify_via_logout_handler(self):
306         plugin = self._makeOne()
307         environ = self._makeEnviron(path_info='/logout_handler',
308                                         login='chris',
309                                         password='password',
310                                         came_from='http://example.com')
311         result = plugin.identify(environ)
312         self.assertEqual(result, None)
313         app = environ['repoze.who.application']
314         self.assertEqual(len(app.headers), 0)
315         self.assertEqual(app.code, 401)
316         self.assertEqual(environ['came_from'], 'http://example.com')
317
318     def test_identify_via_logout_handler_no_came_from_no_http_referer(self):
319         plugin = self._makeOne()
320         environ = self._makeEnviron(path_info='/logout_handler',
321                                         login='chris',
322                                         password='password')
323         result = plugin.identify(environ)
324         self.assertEqual(result, None)
325         app = environ['repoze.who.application']
326         self.assertEqual(len(app.headers), 0)
327         self.assertEqual(app.code, 401)
328         self.assertEqual(environ['came_from'], '/')
329
330     def test_identify_via_logout_handler_no_came_from(self):
331         plugin = self._makeOne()
332         environ = self._makeEnviron(path_info='/logout_handler',
333                                         login='chris',
334                                         password='password')
335         environ['HTTP_REFERER'] = 'http://example.com/referer'
336         result = plugin.identify(environ)
337         self.assertEqual(result, None)
338         app = environ['repoze.who.application']
339         self.assertEqual(len(app.headers), 0)
340         self.assertEqual(app.code, 401)
341         self.assertEqual(environ['came_from'], 'http://example.com/referer')
342
343     def test_remember(self):
344         plugin = self._makeOne()
345         environ = self._makeEnviron()
346         identity = {}
347         result = plugin.remember(environ, identity)
348         self.assertEqual(result, None)
349         self.assertEqual(environ['repoze.who.plugins']['cookie'].remembered,
350                          identity)
351
352     def test_forget(self):
353         plugin = self._makeOne()
354         environ = self._makeEnviron()
355         identity = {}
356         result = plugin.forget(environ, identity)
357         self.assertEqual(result, None)
358         self.assertEqual(environ['repoze.who.plugins']['cookie'].forgotten,
359                          identity
360                          )
361
362     def test_challenge(self):
363         plugin = self._makeOne()
364         environ = self._makeEnviron()
365         app = plugin.challenge(environ, '401 Unauthorized', [('app', '1')],
366                                [('forget', '1')])
367         sr = DummyStartResponse()
368         result = ''.join(app(environ, sr))
369         self.failUnless(result.startswith('302 Found'))
370         self.assertEqual(len(sr.headers), 3)
371         self.assertEqual(sr.headers[0][0], 'Location')
372         url = sr.headers[0][1]
373         import urlparse
374         import cgi
375         parts = urlparse.urlparse(url)
376         parts_qsl = cgi.parse_qsl(parts[4])
377         self.assertEqual(len(parts_qsl), 1)
378         came_from_key, came_from_value = parts_qsl[0]
379         self.assertEqual(parts[0], 'http')
380         self.assertEqual(parts[1], 'example.com')
381         self.assertEqual(parts[2], '/login.html')
382         self.assertEqual(parts[3], '')
383         self.assertEqual(came_from_key, 'came_from')
384         self.assertEqual(came_from_value, 'http://www.example.com/?default=1')
385         headers = sr.headers
386         self.assertEqual(len(headers), 3)
387         self.assertEqual(sr.headers[1][0], 'forget')
388         self.assertEqual(sr.headers[1][1], '1')
389         self.assertEqual(sr.headers[2][0], 'content-type')
390         self.assertEqual(sr.headers[2][1], 'text/plain; charset=utf8')
391         self.assertEqual(sr.status, '302 Found')
392
393     def test_challenge_came_from_in_environ(self):
394         plugin = self._makeOne()
395         environ = self._makeEnviron()
396         environ['came_from'] = 'http://example.com/came_from'
397         app = plugin.challenge(environ, '401 Unauthorized', [('app', '1')],
398                                [('forget', '1')])
399         sr = DummyStartResponse()
400         result = ''.join(app(environ, sr))
401         self.failUnless(result.startswith('302 Found'))
402         self.assertEqual(len(sr.headers), 3)
403         self.assertEqual(sr.headers[0][0], 'Location')
404         url = sr.headers[0][1]
405         import urlparse
406         import cgi
407         parts = urlparse.urlparse(url)
408         parts_qsl = cgi.parse_qsl(parts[4])
409         self.assertEqual(len(parts_qsl), 1)
410         came_from_key, came_from_value = parts_qsl[0]
411         self.assertEqual(parts[0], 'http')
412         self.assertEqual(parts[1], 'example.com')
413         self.assertEqual(parts[2], '/login.html')
414         self.assertEqual(parts[3], '')
415         self.assertEqual(came_from_key, 'came_from')
416         self.assertEqual(came_from_value, 'http://example.com/came_from')
417
418     def test_challenge_with_reason_header(self):
419         plugin = self._makeOne()
420         environ = self._makeEnviron()
421         environ['came_from'] = 'http://example.com/came_from'
422         app = plugin.challenge(
423             environ, '401 Unauthorized',
424             [('X-Authorization-Failure-Reason', 'you are ugly')],
425             [('forget', '1')])
426         sr = DummyStartResponse()
427         result = ''.join(app(environ, sr))
428         self.failUnless(result.startswith('302 Found'))
429         self.assertEqual(len(sr.headers), 3)
430         self.assertEqual(sr.headers[0][0], 'Location')
431         url = sr.headers[0][1]
432         import urlparse
433         import cgi
434         parts = urlparse.urlparse(url)
435         parts_qsl = cgi.parse_qsl(parts[4])
436         self.assertEqual(len(parts_qsl), 2)
437         parts_qsl.sort()
438         came_from_key, came_from_value = parts_qsl[0]
439         reason_key, reason_value = parts_qsl[1]
440         self.assertEqual(parts[0], 'http')
441         self.assertEqual(parts[1], 'example.com')
442         self.assertEqual(parts[2], '/login.html')
443         self.assertEqual(parts[3], '')
444         self.assertEqual(came_from_key, 'came_from')
445         self.assertEqual(came_from_value, 'http://example.com/came_from')
446         self.assertEqual(reason_key, 'reason')
447         self.assertEqual(reason_value, 'you are ugly')
448
449     def test_challenge_with_reason_and_custom_reason_param(self):
450         plugin = self._makeOne(reason_param='auth_failure')
451         environ = self._makeEnviron()
452         environ['came_from'] = 'http://example.com/came_from'
453         app = plugin.challenge(
454             environ, '401 Unauthorized',
455             [('X-Authorization-Failure-Reason', 'you are ugly')],
456             [('forget', '1')])
457         sr = DummyStartResponse()
458         result = ''.join(app(environ, sr))
459         self.failUnless(result.startswith('302 Found'))
460         self.assertEqual(len(sr.headers), 3)
461         self.assertEqual(sr.headers[0][0], 'Location')
462         url = sr.headers[0][1]
463         import urlparse
464         import cgi
465         parts = urlparse.urlparse(url)
466         parts_qsl = cgi.parse_qsl(parts[4])
467         self.assertEqual(len(parts_qsl), 2)
468         parts_qsl.sort()
469         reason_key, reason_value = parts_qsl[0]
470         came_from_key, came_from_value = parts_qsl[1]
471         self.assertEqual(parts[0], 'http')
472         self.assertEqual(parts[1], 'example.com')
473         self.assertEqual(parts[2], '/login.html')
474         self.assertEqual(parts[3], '')
475         self.assertEqual(came_from_key, 'came_from')
476         self.assertEqual(came_from_value, 'http://example.com/came_from')
477         self.assertEqual(reason_key, 'auth_failure')
478         self.assertEqual(reason_value, 'you are ugly')
479
480     def test_challenge_with_setcookie_from_app(self):
481         plugin = self._makeOne()
482         environ = self._makeEnviron()
483         app = plugin.challenge(
484             environ,
485             '401 Unauthorized',
486             [('app', '1'), ('set-cookie','a'), ('set-cookie','b')],
487             [])
488         sr = DummyStartResponse()
489         result = ''.join(app(environ, sr))
490         self.failUnless(result.startswith('302 Found'))
491         self.assertEqual(sr.headers[1][0], 'set-cookie')
492         self.assertEqual(sr.headers[1][1], 'a')
493         self.assertEqual(sr.headers[2][0], 'set-cookie')
494         self.assertEqual(sr.headers[2][1], 'b')
495
e12f73 496     def test_factory_no_login_form_url_raises(self):
TS 497         from repoze.who.plugins.form import make_redirecting_plugin
498         self.assertRaises(ValueError, make_redirecting_plugin, None)
499
500     def test_factory_no_login_handler_path_raises(self):
501         from repoze.who.plugins.form import make_redirecting_plugin
502         self.assertRaises(ValueError,
503                           make_redirecting_plugin, '/go_there', None)
504
505     def test_factory_no_logout_handler_path_raises(self):
506         from repoze.who.plugins.form import make_redirecting_plugin
507         self.assertRaises(ValueError,
508                           make_redirecting_plugin,
509                           '/go_there', '/logged_in', None)
510
511     def test_factory_no_rememberer_name_raises(self):
512         from repoze.who.plugins.form import make_redirecting_plugin
513         self.assertRaises(ValueError,
514                           make_redirecting_plugin,
cc37ce 515                           '/go_there', '/logged_in', '/logged_out', None)
e12f73 516
TS 517     def test_factory_ok(self):
518         from repoze.who.plugins.form import make_redirecting_plugin
519         plugin = make_redirecting_plugin('/go_there',
520                                          '/logged_in',
521                                          '/logged_out',
522                                          'rememberer')
523         self.assertEqual(plugin.login_form_url, '/go_there')
524         self.assertEqual(plugin.login_handler_path, '/logged_in')
525         self.assertEqual(plugin.logout_handler_path, '/logged_out')
526         self.assertEqual(plugin.rememberer_name, 'rememberer')
527
496fcf 528 class DummyIdentifier:
TS 529     forgotten = False
530     remembered = False
531
532     def __init__(self, credentials=None, remember_headers=None,
533                  forget_headers=None, replace_app=None):
534         self.credentials = credentials
535         self.remember_headers = remember_headers
536         self.forget_headers = forget_headers
537         self.replace_app = replace_app
538
539     def identify(self, environ):
540         if self.replace_app:
541             environ['repoze.who.application'] = self.replace_app
542         return self.credentials
543
544     def forget(self, environ, identity):
545         self.forgotten = identity
546         return self.forget_headers
547
548     def remember(self, environ, identity):
549         self.remembered = identity
550         return self.remember_headers
551
552 class DummyStartResponse:
553     def __call__(self, status, headers, exc_info=None):
554         self.status = status
555         self.headers = headers
556         self.exc_info = exc_info
557         return []
558
559 def encode_multipart_formdata(fields):
560     BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$'
561     CRLF = '\r\n'
562     L = []
563     for (key, value) in fields:
564         L.append('--' + BOUNDARY)
565         L.append('Content-Disposition: form-data; name="%s"' % key)
566         L.append('')
567         L.append(value)
568     L.append('--' + BOUNDARY + '--')
569     L.append('')
570     body = CRLF.join(L)
571     content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
572     return content_type, body