Merge lp:~canonical-isd-hackers/canonical-identity-provider/email-whitelist into lp:canonical-identity-provider/release
- email-whitelist
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Michael Nelson |
Approved revision: | no longer in the source branch. |
Merged at revision: | 194 |
Proposed branch: | lp:~canonical-isd-hackers/canonical-identity-provider/email-whitelist |
Merge into: | lp:canonical-identity-provider/release |
Diff against target: |
498 lines (+170/-48) 12 files modified
identityprovider/api10/forms.py (+2/-2) identityprovider/models/captcha.py (+14/-4) identityprovider/schema.py (+3/-0) identityprovider/tests/test_auth.py (+5/-6) identityprovider/tests/test_captcha.py (+2/-2) identityprovider/tests/test_command_cleanup.py (+2/-2) identityprovider/tests/test_forms.py (+18/-0) identityprovider/tests/test_models_captcha.py (+67/-26) identityprovider/tests/test_views_ui.py (+53/-3) identityprovider/views/ui.py (+2/-1) requirements.txt (+1/-1) setup.py (+1/-1) |
To merge this branch: | bzr merge lp:~canonical-isd-hackers/canonical-identity-provider/email-whitelist |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Nelson (community) | code | Approve | |
Review via email:
|
Commit message
Email addresses can be whitelisted to bypass captcha verification. Whitelist emails by providing a list of regular expressions to match emails in the EMAIL_WHITELIST
Description of the change
Email addresses can be whitelisted to bypass captcha verification. Whitelist emails by providing a list of regular expressions to match emails in the EMAIL_WHITELIST
This mp adds an EMAIL_WHITELIST
captcha.verify now takes an email address and all patterns will be checked against the supplied email. If a match is found then captcha verification is bypassed.
For our test email(s) I suggest the following pattern:
^canonicalt
So in local.cfg add:
[captcha]
email_whitelist
^canonicalt
All tests pass in this branch.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Michael Foord (mfoord) wrote : | # |
I agree with you about the danger of unsafe regexps. An accidental ".*" and captcha verification is always bypassed. :-o
> The only thought I had was whether it should be *outside* of captcha.verify(),
> rather than being hidden within, but that's just a small point - nothing to
> delay the branch landing over :)
Well, captcha.verify(...) is called from multiple places, so putting this into the verification seemed both "correct" (it is part of the verification) *and* simplest (only one place to change and update).
> Great to have the above, but I couldn't see why it was necessary given the
two tests below.
That was the test I wrote first. I think I was thinking that I could then just test .check_whitelist directly as I had already tested that verify used it. It didn't work out like that though.
I'm not entirely sure why we have dependencies in setup.py *and* in requirements.txt. I think it is because setup.py is used to build packages, but requirements.txt isn't.
> I don't see why you don't just use `with patch.object(
Yeah, that would be better. I was just fixing some code that was a bit "odd". Just using patch.object would have been better...
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ricardo Kirkner (ricardokirkner) wrote : | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
>> === modified file 'setup.py'
>> --- setup.py 2011-07-06 17:36:56 +0000
>> +++ setup.py 2011-07-26 07:31:31 +0000
>> @@ -39,7 +39,7 @@
>> 'NoseDjango=
>> 'nosexcover',
>> 'coverage==3.4',
>> - 'mock==0.6.0',
>> + 'mock==0.7.2',
>
> Why is this even in the setup.py? I thought (probably wrongly) that mock or
> nose or wsgi-intercept needed to be in the requirements.txt for setting up the dev environment,
> but not in the setup.py, as there's no need for them to be installed? I assume
> that's wrong then (on my part)? Do python packages install their test
> dependencies so they can be tested after install? (is that possible with our
> packages?)
This I can explain. These dependencies are test dependencies, and marked
so (using tests_require).
Having them marked as test dependencies, does not require you to have
them installed for running/installing your product, but allows
setuptools to look for them/install them if necessary when running your
tests via "python setup.py test".
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.11 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://
iEYEARECAAYFAk4
xTUAnR+
=niKk
-----END PGP SIGNATURE-----
Preview Diff
1 | === modified file 'identityprovider/api10/forms.py' |
2 | --- identityprovider/api10/forms.py 2011-07-06 21:14:28 +0000 |
3 | +++ identityprovider/api10/forms.py 2011-07-25 20:09:28 +0000 |
4 | @@ -59,8 +59,8 @@ |
5 | remote_ip = cleaned_data['remote_ip'] |
6 | |
7 | captcha = Captcha(captcha_id) |
8 | - |
9 | - if captcha.verify(captcha_solution, remote_ip): |
10 | + email = cleaned_data.get('email', '') |
11 | + if captcha.verify(captcha_solution, remote_ip, email): |
12 | return cleaned_data |
13 | # not verified |
14 | raise forms.ValidationError(_("Wrong captcha solution.")) |
15 | |
16 | === modified file 'identityprovider/models/captcha.py' |
17 | --- identityprovider/models/captcha.py 2011-06-30 04:55:13 +0000 |
18 | +++ identityprovider/models/captcha.py 2011-07-25 20:09:28 +0000 |
19 | @@ -66,14 +66,15 @@ |
20 | |
21 | As is verifying received solution: |
22 | |
23 | - >>> captcha = Captcha('captcha-id-received-from-client') |
24 | - >>> captcha.verify("this-is-invalid-solution", ip_addr) |
25 | + >>> email = 'foo@email.com' |
26 | + >>> captcha = Captcha('captcha-id-received-from-client', email) |
27 | + >>> captcha.verify("this-is-invalid-solution", ip_addr, email) |
28 | False |
29 | |
30 | Once verified solution is cached, so calling again to .verify() method is |
31 | very cheap (and returns same result): |
32 | |
33 | - >>> captcha.verify("this-is-invalid-solution", ip_addr) |
34 | + >>> captcha.verify("this-is-invalid-solution", ip_addr, email) |
35 | False |
36 | |
37 | You can also get original response from reCaptcha: |
38 | @@ -159,7 +160,7 @@ |
39 | |
40 | return CaptchaResponse(response.code, response, None) |
41 | |
42 | - def verify(self, captcha_solution, remote_ip): |
43 | + def verify(self, captcha_solution, remote_ip, email): |
44 | if self._verified is not None: |
45 | return self._verified |
46 | |
47 | @@ -167,6 +168,9 @@ |
48 | self.response = None |
49 | return True |
50 | |
51 | + if self.check_whitelist(email): |
52 | + return True |
53 | + |
54 | if isinstance(captcha_solution, unicode): |
55 | captcha_solution = captcha_solution.encode('utf-8') |
56 | |
57 | @@ -190,3 +194,9 @@ |
58 | self._verified = False |
59 | raise VerifyCaptchaError(self.response.traceback) |
60 | return self._verified |
61 | + |
62 | + def check_whitelist(self, email): |
63 | + for pattern in settings.EMAIL_WHITELIST_REGEXP_LIST: |
64 | + if re.match(pattern, email): |
65 | + return True |
66 | + return False |
67 | |
68 | === modified file 'identityprovider/schema.py' |
69 | --- identityprovider/schema.py 2011-07-12 09:41:08 +0000 |
70 | +++ identityprovider/schema.py 2011-07-25 20:09:28 +0000 |
71 | @@ -78,6 +78,9 @@ |
72 | captcha.captcha_public_key = StringConfigOption() |
73 | captcha.captcha_private_key = StringConfigOption() |
74 | captcha.disable_captcha_verification = BoolConfigOption() |
75 | + captcha.email_whitelist_regexp_list = LinesConfigOption( |
76 | + item=StringConfigOption(raw=True) |
77 | + ) |
78 | |
79 | # debug |
80 | debug = ConfigSection() |
81 | |
82 | === modified file 'identityprovider/tests/test_auth.py' |
83 | --- identityprovider/tests/test_auth.py 2011-07-15 15:17:19 +0000 |
84 | +++ identityprovider/tests/test_auth.py 2011-07-25 20:09:28 +0000 |
85 | @@ -5,7 +5,6 @@ |
86 | from mock import ( |
87 | Mock, |
88 | patch, |
89 | - patch_object, |
90 | ) |
91 | from oauth_backend.models import Consumer |
92 | from piston.oauth import OAuthError as PistonOAuthError |
93 | @@ -171,19 +170,19 @@ |
94 | |
95 | def test_is_authenticated_invalid_request(self): |
96 | mock_is_valid_request = Mock(return_value=False) |
97 | - with patch_object(self.auth, 'is_valid_request', mock_is_valid_request): |
98 | + with patch.object(self.auth, 'is_valid_request', mock_is_valid_request): |
99 | self.assertEqual(self.auth.is_authenticated(self.request), False) |
100 | |
101 | def test_is_authenticated_piston_oauth_error(self): |
102 | mock_validate_token = Mock(side_effect=PistonOAuthError) |
103 | - with patch_object(self.auth, 'validate_token', mock_validate_token): |
104 | + with patch.object(self.auth, 'validate_token', mock_validate_token): |
105 | self.assertEqual(self.auth.is_authenticated(self.request), False) |
106 | |
107 | def test_is_authenticated_no_consumer_token(self): |
108 | consumer, token, params = None, None, None |
109 | mock_validate_token = Mock(return_value=(consumer, token, params)) |
110 | # authentication fails as there is no consumer and token in the request |
111 | - with patch_object(self.auth, 'validate_token', mock_validate_token): |
112 | + with patch.object(self.auth, 'validate_token', mock_validate_token): |
113 | self.assertEqual(self.auth.is_authenticated(self.request), False) |
114 | |
115 | @patch('identityprovider.auth.Account.objects.get') |
116 | @@ -194,7 +193,7 @@ |
117 | consumer, token, params = Mock(), Mock(), None |
118 | # make sure validate_token returns a consumer and a token |
119 | mock_validate_token = Mock(return_value=(consumer, token, params)) |
120 | - with patch_object(self.auth, 'validate_token', mock_validate_token): |
121 | + with patch.object(self.auth, 'validate_token', mock_validate_token): |
122 | self.assertEqual(self.auth.is_authenticated(self.request), False) |
123 | |
124 | @patch('identityprovider.auth.Account.objects.get') |
125 | @@ -206,7 +205,7 @@ |
126 | consumer, params = Mock(), None |
127 | # make sure validate_token returns a consumer and a token |
128 | mock_validate_token = Mock(return_value=(consumer, mock_token, params)) |
129 | - with patch_object(self.auth, 'validate_token', mock_validate_token): |
130 | + with patch.object(self.auth, 'validate_token', mock_validate_token): |
131 | response = self.auth.is_authenticated(self.request) |
132 | |
133 | self.assertEqual(response, True) |
134 | |
135 | === modified file 'identityprovider/tests/test_captcha.py' |
136 | --- identityprovider/tests/test_captcha.py 2011-07-21 16:08:06 +0000 |
137 | +++ identityprovider/tests/test_captcha.py 2011-07-25 20:09:28 +0000 |
138 | @@ -27,7 +27,7 @@ |
139 | captcha = Captcha(None) |
140 | captcha._open = Mock() |
141 | captcha._open.return_value.is_error = True |
142 | - captcha.verify(captcha_solution, 'foobar') |
143 | + captcha.verify(captcha_solution, 'foobar', '') |
144 | |
145 | captcha_response = mock_urlencode.call_args[0][0]['response'] |
146 | self.assertEqual(captcha_response, encoded_solution) |
147 | @@ -59,7 +59,7 @@ |
148 | def test_verify_is_short_circuited_when_disabled_in_settings(self): |
149 | with patch_settings(DISABLE_CAPTCHA_VERIFICATION=True): |
150 | captcha = Captcha('test') |
151 | - verify_result = captcha.verify('solution', '127.0.0.1') |
152 | + verify_result = captcha.verify('solution', '127.0.0.1', '') |
153 | |
154 | self.assertTrue(verify_result) |
155 | self.assertTrue(captcha.response is None) |
156 | |
157 | === modified file 'identityprovider/tests/test_command_cleanup.py' |
158 | --- identityprovider/tests/test_command_cleanup.py 2011-07-15 11:50:53 +0000 |
159 | +++ identityprovider/tests/test_command_cleanup.py 2011-07-25 20:09:28 +0000 |
160 | @@ -8,7 +8,7 @@ |
161 | from django.core.management import call_command |
162 | from django.db import connection |
163 | from django.test import TestCase |
164 | -from mock import patch_object |
165 | +from mock import patch |
166 | |
167 | from identityprovider.models import OpenIDNonce |
168 | |
169 | @@ -34,7 +34,7 @@ |
170 | self.populate() |
171 | |
172 | mock_stdout = StringIO() |
173 | - with patch_object(sys, 'stdout', mock_stdout): |
174 | + with patch.object(sys, 'stdout', mock_stdout): |
175 | call_command('cleanup', verbosity=0) |
176 | mock_stdout.seek(0) |
177 | self.assertTrue('No items selected to clean up.' in mock_stdout.read()) |
178 | |
179 | === modified file 'identityprovider/tests/test_forms.py' |
180 | --- identityprovider/tests/test_forms.py 2011-07-15 11:50:53 +0000 |
181 | +++ identityprovider/tests/test_forms.py 2011-07-25 20:09:28 +0000 |
182 | @@ -18,6 +18,7 @@ |
183 | ) |
184 | from identityprovider.api10.forms import WebserviceCreateAccountForm |
185 | from identityprovider.models.openidmodels import OpenIDRPConfig |
186 | +from identityprovider.tests.utils import patch_settings |
187 | |
188 | from .utils import SSOBaseTestCase |
189 | |
190 | @@ -114,6 +115,23 @@ |
191 | self.assertTrue(form.is_valid()) |
192 | self.assertEqual(form.cleaned_data['platform'], 'desktop') |
193 | |
194 | + def test_captcha_checked_for_whitelist(self): |
195 | + data = { |
196 | + 'email': 'canonicaltest@gmail.com', |
197 | + 'password': 'password1A', |
198 | + 'captcha_id': '1', |
199 | + 'captcha_solution': '2', |
200 | + 'remote_ip': '127.0.0.1', |
201 | + } |
202 | + pattern = '^canonicaltest(?:\+.+)?@gmail\.com$' |
203 | + overrides = dict( |
204 | + DISABLE_CAPTCHA_VERIFICATION=False, |
205 | + EMAIL_WHITELIST_REGEXP_LIST=[pattern], |
206 | + ) |
207 | + with patch_settings(**overrides): |
208 | + form = WebserviceCreateAccountForm(data) |
209 | + self.assertTrue(form.is_valid()) |
210 | + |
211 | def test_default_cleaned_validate_redirect_to(self): |
212 | data = { |
213 | 'email': 'some@email.com', |
214 | |
215 | === modified file 'identityprovider/tests/test_models_captcha.py' |
216 | --- identityprovider/tests/test_models_captcha.py 2011-01-20 21:48:43 +0000 |
217 | +++ identityprovider/tests/test_models_captcha.py 2011-07-25 20:09:28 +0000 |
218 | @@ -4,7 +4,10 @@ |
219 | import unittest |
220 | import urllib2 |
221 | |
222 | -from mock import patch_object |
223 | +from mock import ( |
224 | + Mock, |
225 | + patch, |
226 | +) |
227 | |
228 | from cStringIO import StringIO |
229 | from django.conf import settings |
230 | @@ -16,6 +19,8 @@ |
231 | VerifyCaptchaError, |
232 | ) |
233 | |
234 | +from identityprovider.tests.utils import patch_settings |
235 | + |
236 | |
237 | class CaptchaResponseTestCase(unittest.TestCase): |
238 | |
239 | @@ -37,11 +42,11 @@ |
240 | |
241 | class CaptchaTestCase(unittest.TestCase): |
242 | |
243 | - @patch_object(Captcha, '_open') |
244 | + @patch.object(Captcha, '_open') |
245 | def test_new_captcha_error(self, mock_open): |
246 | self.assertRaises(NewCaptchaError, Captcha.new) |
247 | |
248 | - @patch_object(Captcha, '_open') |
249 | + @patch.object(Captcha, '_open') |
250 | def test_new_captcha_no_challenge(self, mock_open): |
251 | try: |
252 | Captcha.new() |
253 | @@ -70,23 +75,56 @@ |
254 | def tearDown(self): |
255 | settings.DISABLE_CAPTCHA_VERIFICATION = self.old_DISABLE_CAPTCHA_VERIFICATION |
256 | |
257 | - @patch_object(Captcha, '_open') |
258 | + @patch.object(Captcha, '_open') |
259 | + def test_verify_calls_check_whitelist(self, mock_open): |
260 | + captcha = Captcha(None) |
261 | + captcha.check_whitelist = Mock(return_value=False) |
262 | + |
263 | + result = captcha.verify(None, 'localhost', 'foo') |
264 | + self.assertFalse(result) |
265 | + self.assertTrue(mock_open.called) |
266 | + |
267 | + captcha.check_whitelist.assert_called_with('foo') |
268 | + |
269 | + @patch.object(Captcha, '_open') |
270 | + def test_check_whitelist_match(self, mock_open): |
271 | + captcha = Captcha(None) |
272 | + |
273 | + regexps = ['not a match', '^foo$'] |
274 | + with patch_settings( |
275 | + EMAIL_WHITELIST_REGEXP_LIST=regexps |
276 | + ): |
277 | + result = captcha.verify(None, 'localhost', 'foo') |
278 | + self.assertTrue(result) |
279 | + self.assertFalse(mock_open.called) |
280 | + |
281 | + @patch.object(Captcha, '_open') |
282 | + def test_check_whitelist_no_match(self, mock_open): |
283 | + captcha = Captcha(None) |
284 | + |
285 | + regexps = ['not a match', '^bar$'] |
286 | + with patch_settings( |
287 | + EMAIL_WHITELIST_REGEXP_LIST=regexps |
288 | + ): |
289 | + result = captcha.verify(None, 'localhost', 'foo') |
290 | + self.assertFalse(result) |
291 | + self.assertTrue(mock_open.called) |
292 | + |
293 | + @patch.object(Captcha, '_open') |
294 | def test_verify_already_verified(self, mock_open): |
295 | c = Captcha(None) |
296 | c._verified = True |
297 | - r = c.verify(None, None) |
298 | + r = c.verify(None, None, '') |
299 | self.assertTrue(r) |
300 | |
301 | - @patch_object(Captcha, '_open') |
302 | + @patch.object(Captcha, '_open') |
303 | def test_verify_no_verification(self, mock_open): |
304 | - settings.DISABLE_CAPTCHA_VERIFICATION = True |
305 | - |
306 | - c = Captcha(None) |
307 | - r = c.verify(None, None) |
308 | - self.assertTrue(r) |
309 | - |
310 | - @patch_object(Captcha, '_open') |
311 | - def test_verify_response_ok(self, mock_open): |
312 | + with patch_settings(DISABLE_CAPTCHA_VERIFICATION=True): |
313 | + c = Captcha(None) |
314 | + r = c.verify(None, None, '') |
315 | + self.assertTrue(r) |
316 | + |
317 | + def test_verify_response_ok(self): |
318 | @classmethod |
319 | def mock_open(cls, request): |
320 | r = CaptchaResponse(200, StringIO('true\nok')) |
321 | @@ -94,25 +132,28 @@ |
322 | old_open = Captcha._open |
323 | Captcha._open = mock_open |
324 | |
325 | - c = Captcha.new() |
326 | - r = c.verify(None, "localhost") |
327 | - |
328 | - self.assertTrue(r) |
329 | - self.assertEqual(c.message, 'ok') |
330 | - |
331 | - Captcha._open = old_open |
332 | - |
333 | - @patch_object(Captcha, '_open') |
334 | + try: |
335 | + c = Captcha.new() |
336 | + r = c.verify(None, "localhost", '') |
337 | + |
338 | + self.assertTrue(r) |
339 | + self.assertEqual(c.message, 'ok') |
340 | + finally: |
341 | + Captcha._open = old_open |
342 | + |
343 | + @patch.object(Captcha, '_open') |
344 | def test_verify_no_captcha_id(self, mock_open): |
345 | c = Captcha(None) |
346 | - r = c.verify(None, 'localhost') |
347 | + r = c.verify(None, 'localhost', '') |
348 | |
349 | self.assertFalse(r) |
350 | self.assertEqual(c.message, 'no-challenge') |
351 | |
352 | - @patch_object(Captcha, '_open') |
353 | + @patch.object(Captcha, '_open') |
354 | def test_verify_error(self, mock_open): |
355 | c = Captcha(None) |
356 | c.captcha_id = 'challenge-id' |
357 | |
358 | - self.assertRaises(VerifyCaptchaError, c.verify, None, 'localhost') |
359 | + self.assertRaises( |
360 | + VerifyCaptchaError, c.verify, None, 'localhost', '' |
361 | + ) |
362 | |
363 | === modified file 'identityprovider/tests/test_views_ui.py' |
364 | --- identityprovider/tests/test_views_ui.py 2011-07-19 14:21:42 +0000 |
365 | +++ identityprovider/tests/test_views_ui.py 2011-07-25 20:09:28 +0000 |
366 | @@ -7,7 +7,7 @@ |
367 | import urllib2 |
368 | from urlparse import urlsplit |
369 | |
370 | -from mock import patch_object, patch |
371 | +from mock import patch |
372 | |
373 | from django.conf import settings |
374 | from django.core import mail |
375 | @@ -30,6 +30,7 @@ |
376 | from identityprovider.tests.utils import ( |
377 | LPAccountTestCase, |
378 | MockHandler, |
379 | + patch_settings, |
380 | ) |
381 | from identityprovider.utils import is_django_13 |
382 | |
383 | @@ -592,7 +593,7 @@ |
384 | def __init__(self, *args): |
385 | pass |
386 | |
387 | - def verify(self, solution, ip_addr): |
388 | + def verify(self, solution, ip_addr, email): |
389 | self.message = 'no-challenge' |
390 | return False |
391 | |
392 | @@ -635,7 +636,7 @@ |
393 | self.assertEqual(len(mail.outbox), mails_sent) |
394 | mail.outbox = [] |
395 | |
396 | - @patch_object(logging.Logger, 'debug') |
397 | + @patch.object(logging.Logger, 'debug') |
398 | def test_new_account_when_person_exists_and_account_not(self, mock_debug): |
399 | person = Person.objects.create(displayname='Person') |
400 | EmailAddress.objects.create(email='person@example.com', |
401 | @@ -815,6 +816,55 @@ |
402 | msg = 'It appears that our captcha service was unable to load' |
403 | self.assertContains(r, msg) |
404 | |
405 | + def test_new_account_captcha_whitelist(self): |
406 | + email = 'canonicaltest@gmail.com' |
407 | + pattern = '^canonicaltest(?:\+.+)?@gmail\.com$' |
408 | + overrides = dict( |
409 | + DISABLE_CAPTCHA_VERIFICATION=False, |
410 | + EMAIL_WHITELIST_REGEXP_LIST=[pattern], |
411 | + ) |
412 | + with patch_settings(**overrides): |
413 | + response = _post_new_account( |
414 | + self.client, |
415 | + email=email |
416 | + ) |
417 | + # should redirect to '/+email-sent' |
418 | + self.assertEqual(response.status_code, 302) |
419 | + redirect = response['location'] |
420 | + self.assertTrue(redirect.endswith('/+email-sent')) |
421 | + |
422 | + def test_new_account_captcha_whitelist_with_uuid(self): |
423 | + email = 'canonicaltest+something@gmail.com' |
424 | + pattern = '^canonicaltest(?:\+.+)?@gmail\.com$' |
425 | + overrides = dict( |
426 | + DISABLE_CAPTCHA_VERIFICATION=False, |
427 | + EMAIL_WHITELIST_REGEXP_LIST=[pattern], |
428 | + ) |
429 | + with patch_settings(**overrides): |
430 | + response = _post_new_account( |
431 | + self.client, |
432 | + email=email |
433 | + ) |
434 | + # should redirect to '/+email-sent' |
435 | + self.assertEqual(response.status_code, 302) |
436 | + redirect = response['location'] |
437 | + self.assertTrue(redirect.endswith('/+email-sent')) |
438 | + |
439 | + def test_new_account_captcha_whitelist_fail(self): |
440 | + email = 'notcanonicaltest@gmail.com' |
441 | + pattern = '^canonicaltest(?:\+.+)?@gmail\.com$' |
442 | + overrides = dict( |
443 | + DISABLE_CAPTCHA_VERIFICATION=False, |
444 | + EMAIL_WHITELIST_REGEXP_LIST=[pattern], |
445 | + ) |
446 | + with patch_settings(**overrides): |
447 | + response = _post_new_account( |
448 | + self.client, |
449 | + email=email |
450 | + ) |
451 | + msg = 'It appears that our captcha service was unable to load' |
452 | + self.assertContains(response, msg) |
453 | + |
454 | |
455 | class LanguagesTestCase(SSOBaseTestCase): |
456 | |
457 | |
458 | === modified file 'identityprovider/views/ui.py' |
459 | --- identityprovider/views/ui.py 2011-07-21 16:08:06 +0000 |
460 | +++ identityprovider/views/ui.py 2011-07-25 20:09:28 +0000 |
461 | @@ -464,9 +464,10 @@ |
462 | def _verify_captcha_response(template, request, form): |
463 | captcha = Captcha(request.POST.get('recaptcha_challenge_field')) |
464 | captcha_solution = request.POST.get('recaptcha_response_field') |
465 | + email = request.POST.get('email', '') |
466 | ip_addr = request.environ["REMOTE_ADDR"] |
467 | try: |
468 | - verified = captcha.verify(captcha_solution, ip_addr) |
469 | + verified = captcha.verify(captcha_solution, ip_addr, email) |
470 | if verified: |
471 | return None |
472 | except VerifyCaptchaError, e: |
473 | |
474 | === modified file 'requirements.txt' |
475 | --- requirements.txt 2011-07-12 14:38:02 +0000 |
476 | +++ requirements.txt 2011-07-25 20:09:28 +0000 |
477 | @@ -6,7 +6,7 @@ |
478 | NoseDjango==0.8.1 |
479 | nosexcover |
480 | coverage==3.4 |
481 | -mock==0.6.0 |
482 | +mock==0.7.2 |
483 | wsgiref==0.1.2 |
484 | wsgi-intercept==0.4 |
485 | zope.testbrowser==3.5.1 |
486 | |
487 | === modified file 'setup.py' |
488 | --- setup.py 2011-07-06 17:36:56 +0000 |
489 | +++ setup.py 2011-07-25 20:09:28 +0000 |
490 | @@ -39,7 +39,7 @@ |
491 | 'NoseDjango==0.8.1', |
492 | 'nosexcover', |
493 | 'coverage==3.4', |
494 | - 'mock==0.6.0', |
495 | + 'mock==0.7.2', |
496 | 'wsgiref==0.1.2', |
497 | 'wsgi-intercept==0.4', |
498 | 'zope.testbrowser==3.5.1', |
Hi Michael. Excellent branch... the implementation and tests are all very clear.
The only potential danger I can see with this branch is the ability to add unsafe regexes, but I don't see anyway around that unless we restricted ourselves to gmail accounts (ie. usesd settings. GMAIL_IDENTIFIE R_WHITELIST and automatically calculated the regex to allow identifier+ whatever) .
The only questions I've got below are for my own understanding or tiny style questions. I verified that all 611 tests pass.
> === modified file 'identityprovid er/models/ captcha. py' r/models/ captcha. py 2011-06-30 04:55:13 +0000 r/models/ captcha. py 2011-07-26 07:31:31 +0000 ror(self. response. traceback) (self, email): EMAIL_WHITELIST _REGEXP_ LIST:
> --- identityprovide
> +++ identityprovide
> @@ -190,3 +194,9 @@
> self._verified = False
> raise VerifyCaptchaEr
> return self._verified
> +
> + def check_whitelist
> + for pattern in settings.
> + if re.match(pattern, email):
> + return True
> + return False
So that's the crux of the branch right? Nice and simple :)
The only thought I had was whether it should be *outside* of captcha.verify(),
rather than being hidden within, but that's just a small point - nothing to
delay the branch landing over :)
> er/schema. py' r/schema. py 2011-07-12 09:41:08 +0000 r/schema. py 2011-07-26 07:31:31 +0000 captcha_ public_ key = StringConfigOpt ion() captcha_ private_ key = StringConfigOpt ion() disable_ captcha_ verification = BoolConfigOption() email_whitelist _regexp_ list = LinesConfigOption( igOption( raw=True)
> === modified file 'identityprovid
> --- identityprovide
> +++ identityprovide
> @@ -78,6 +78,9 @@
> captcha.
> captcha.
> captcha.
> + captcha.
> + item=StringConf
I wasn't sure at first why the raw=True was necessary, until I tried canonicaltest( ?:\+.+) ?@gmail\ .com$') .
repr('^
> + ) er/tests/ test_auth. py' r/tests/ test_auth. py 2011-07-15 15:17:19 +0000 r/tests/ test_auth. py 2011-07-26 07:31:31 +0000
>
> # debug
> debug = ConfigSection()
>
> === modified file 'identityprovid
> --- identityprovide
> +++ identityprovide
Great to fix up all the patch_object's :)
> === modified file 'identityprovid er/tests/ test_captcha. py' r/tests/ test_captcha. py 2011-07-21 16:08:06 +0000 r/tests/ test_captcha. py 2011-07-26 07:31:31 +0000 er/tests/ test_command_ cleanup. py' r/tests/ test_command_ cleanup. py 2011-07-15 11:50:53 +0000 r/tests/ test_command_ cleanup. py 2011-07-26 07:31:31 +0000 er/tests/ test_forms. py' r/tests/ test_forms. py 2011-07-15 11:50:53 +0000 r/tests/ test_forms. py 2011-07-26 07:31:31 +0000 r.api10. forms import WebserviceCreat eAccountForm r.models. openidmodels import OpenIDRPConfig r.tests. utils import patch_settings (form.is_ valid() ) l(form. cleaned_ da...
> --- identityprovide
> +++ identityprovide
> === modified file 'identityprovid
> --- identityprovide
> +++ identityprovide
> === modified file 'identityprovid
> --- identityprovide
> +++ identityprovide
> @@ -18,6 +18,7 @@
> )
> from identityprovide
> from identityprovide
> +from identityprovide
>
> from .utils import SSOBaseTestCase
>
> @@ -114,6 +115,23 @@
> self.assertTrue
> self.assertEqua