Merge lp:~maxiberta/canonical-identity-provider/drop-account-registration-captcha into lp:canonical-identity-provider/release
- drop-account-registration-captcha
- Merge into trunk
Proposed by
Maximiliano Bertacchini
Status: | Rejected |
---|---|
Rejected by: | Maximiliano Bertacchini |
Proposed branch: | lp:~maxiberta/canonical-identity-provider/drop-account-registration-captcha |
Merge into: | lp:canonical-identity-provider/release |
Prerequisite: | lp:~maxiberta/canonical-identity-provider/disable-user-registration-api |
Diff against target: |
901 lines (+13/-561) 13 files modified
django_project/settings_devel.py (+0/-1) src/api/v10/forms.py (+0/-23) src/api/v10/handlers.py (+10/-20) src/api/v10/tests/test_forms.py (+0/-24) src/api/v20/handlers.py (+0/-40) src/api/v20/tests/test_handlers.py (+1/-195) src/webui/templates/registration/_create_account_form.html (+0/-11) src/webui/templates/widgets/recaptcha.html (+0/-43) src/webui/tests/test_views_registration.py (+0/-51) src/webui/tests/test_views_ui.py (+1/-85) src/webui/views/registration.py (+0/-26) src/webui/views/ui.py (+1/-35) src/webui/views/utils.py (+0/-7) |
To merge this branch: | bzr merge lp:~maxiberta/canonical-identity-provider/drop-account-registration-captcha |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu One hackers | Pending | ||
Review via email: mp+356870@code.launchpad.net |
Commit message
Drop captcha from account registration (API & web).
Description of the change
Account registration captcha has been disabled for a long time (years?). And the implementation depends on reCaptcha v1 which is dead since March 2018. So, let's just drop all captcha bits from there.
To post a comment you must log in.
Revision history for this message
Maximiliano Bertacchini (maxiberta) wrote : | # |
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'django_project/settings_devel.py' |
2 | --- django_project/settings_devel.py 2018-02-15 13:17:06 +0000 |
3 | +++ django_project/settings_devel.py 2018-10-16 20:30:58 +0000 |
4 | @@ -22,7 +22,6 @@ |
5 | 'TWOFACTOR': {'is_active': True}, |
6 | 'CAN_VIEW_SUPPORT_PHONE': {'is_active': True}, |
7 | 'CAPTCHA': {'is_active': False}, |
8 | - 'CAPTCHA_NEW_ACCOUNT': {'is_active': False}, |
9 | 'PREFLIGHT': {'is_active': True}, |
10 | 'LOGIN_BY_TOKEN': {'is_active': True}, |
11 | 'SSH_KEY_INTEGRATION': {'is_active': False}, |
12 | |
13 | === modified file 'src/api/v10/forms.py' |
14 | --- src/api/v10/forms.py 2015-05-08 19:25:27 +0000 |
15 | +++ src/api/v10/forms.py 2018-10-16 20:30:58 +0000 |
16 | @@ -5,17 +5,12 @@ |
17 | |
18 | from __future__ import unicode_literals |
19 | |
20 | -from django import forms |
21 | from django.forms import fields |
22 | -from django.utils.translation import ugettext_lazy as _ |
23 | |
24 | from identityprovider.forms import NewAccountForm |
25 | -from identityprovider.models.captcha import Captcha |
26 | |
27 | |
28 | class WebserviceCreateAccountForm(NewAccountForm): |
29 | - captcha_id = fields.CharField(max_length=1024) |
30 | - captcha_solution = fields.CharField(max_length=256) |
31 | remote_ip = fields.CharField(max_length=256) |
32 | platform = fields.TypedChoiceField(choices=[ |
33 | ('web', 'Web'), ('desktop', 'Desktop'), ('mobile', 'Mobile')], |
34 | @@ -38,21 +33,3 @@ |
35 | if not validate_redirect_to: |
36 | validate_redirect_to = None |
37 | return validate_redirect_to |
38 | - |
39 | - def clean(self): |
40 | - cleaned_data = super(WebserviceCreateAccountForm, self).clean() |
41 | - captcha_id = cleaned_data.get('captcha_id') |
42 | - captcha_solution = cleaned_data.get('captcha_solution') |
43 | - |
44 | - # The remote IP address is absolutely required, and comes from |
45 | - # SSO itself, not from the client. If it's missing, it's a |
46 | - # programming error, and should not be returned to the client |
47 | - # as a validation error. So, we use a normal key lookup here. |
48 | - remote_ip = cleaned_data['remote_ip'] |
49 | - |
50 | - captcha = Captcha(captcha_id) |
51 | - email = cleaned_data.get('email', '') |
52 | - if captcha.verify(captcha_solution, remote_ip, email): |
53 | - return cleaned_data |
54 | - # not verified |
55 | - raise forms.ValidationError(_("Wrong captcha solution.")) |
56 | |
57 | === modified file 'src/api/v10/handlers.py' |
58 | --- src/api/v10/handlers.py 2018-10-16 20:30:58 +0000 |
59 | +++ src/api/v10/handlers.py 2018-10-16 20:30:58 +0000 |
60 | @@ -39,7 +39,6 @@ |
61 | from identityprovider.models.captcha import ( |
62 | Captcha, |
63 | NewCaptchaError, |
64 | - VerifyCaptchaError, |
65 | ) |
66 | from identityprovider.models.const import AuthTokenType, EmailStatus |
67 | from identityprovider.signals import ( |
68 | @@ -194,25 +193,16 @@ |
69 | data = request.data |
70 | data['remote_ip'] = remote_addr |
71 | form = WebserviceCreateAccountForm(data) |
72 | - try: |
73 | - if not form.is_valid(): |
74 | - errors = dict((k, map(unicode, v)) |
75 | - for (k, v) in form.errors.items()) |
76 | - # XXX: cope with client trimming error messages |
77 | - if unicode(PASSWORD_LEAKED) in errors.get('password', ''): |
78 | - errors['password'] = unicode(_( |
79 | - 'unsafe: leaked by security breach ' |
80 | - 'on another website')) |
81 | - result = {'status': 'error', 'errors': errors} |
82 | - return result |
83 | - except VerifyCaptchaError: |
84 | - logger.exception("reCaptcha connection error") |
85 | - msg = unicode( |
86 | - _('Unable to verify captcha. Please try again shortly.')) |
87 | - return { |
88 | - 'status': 'error', |
89 | - 'errors': {'captcha_solution': [msg]} |
90 | - } |
91 | + if not form.is_valid(): |
92 | + errors = dict((k, map(unicode, v)) |
93 | + for (k, v) in form.errors.items()) |
94 | + # XXX: cope with client trimming error messages |
95 | + if unicode(PASSWORD_LEAKED) in errors.get('password', ''): |
96 | + errors['password'] = unicode(_( |
97 | + 'unsafe: leaked by security breach ' |
98 | + 'on another website')) |
99 | + result = {'status': 'error', 'errors': errors} |
100 | + return result |
101 | |
102 | cleaned_data = form.cleaned_data |
103 | requested_email = cleaned_data['email'] |
104 | |
105 | === modified file 'src/api/v10/tests/test_forms.py' |
106 | --- src/api/v10/tests/test_forms.py 2015-11-24 14:38:04 +0000 |
107 | +++ src/api/v10/tests/test_forms.py 2018-10-16 20:30:58 +0000 |
108 | @@ -5,23 +5,12 @@ |
109 | |
110 | from __future__ import unicode_literals |
111 | |
112 | -from django.test.utils import override_settings |
113 | - |
114 | from api.v10.forms import WebserviceCreateAccountForm |
115 | from identityprovider.tests.utils import SSOBaseTestCase |
116 | |
117 | |
118 | -@override_settings(CAPTCHA_PUBLIC_KEY='public', CAPTCHA_PRIVATE_KEY='private') |
119 | class WebServiceCreateAccountFormTestCase(SSOBaseTestCase): |
120 | |
121 | - def setUp(self): |
122 | - super(WebServiceCreateAccountFormTestCase, self).setUp() |
123 | - # patch Captcha so it never hits the real network |
124 | - self.mock_captcha_open = self.patch( |
125 | - 'identityprovider.models.captcha.Captcha._open') |
126 | - self.mock_captcha_open.return_value.is_error = False |
127 | - self.mock_captcha_open.return_value.data.return_value = 'true\nyey' |
128 | - |
129 | def test_nonascii_password(self): |
130 | data = {'password': 'Curuzú Cuatiá', |
131 | 'remote_ip': '127.0.0.1'} |
132 | @@ -46,19 +35,6 @@ |
133 | self.assertTrue(form.is_valid()) |
134 | self.assertEqual(form.cleaned_data['platform'], 'desktop') |
135 | |
136 | - def test_captcha_checked_for_whitelist(self): |
137 | - data = { |
138 | - 'email': 'canonicaltest@gmail.com', |
139 | - 'password': 'password1A', |
140 | - 'captcha_id': '1', |
141 | - 'captcha_solution': '2', |
142 | - 'remote_ip': '127.0.0.1', |
143 | - } |
144 | - pattern = '^canonicaltest(?:\+.+)?@gmail\.com$' |
145 | - with self.settings(EMAIL_WHITELIST_REGEXP_LIST=[pattern]): |
146 | - form = WebserviceCreateAccountForm(data) |
147 | - self.assertTrue(form.is_valid()) |
148 | - |
149 | def test_default_cleaned_validate_redirect_to(self): |
150 | data = { |
151 | 'email': 'some@email.com', |
152 | |
153 | === modified file 'src/api/v20/handlers.py' |
154 | --- src/api/v20/handlers.py 2018-10-16 20:30:58 +0000 |
155 | +++ src/api/v20/handlers.py 2018-10-16 20:30:58 +0000 |
156 | @@ -52,7 +52,6 @@ |
157 | Token, |
158 | twofactor, |
159 | ) |
160 | -from identityprovider.models.captcha import Captcha, VerifyCaptchaError |
161 | from identityprovider.models.const import ( |
162 | AccountStatus, |
163 | AuthLogType, |
164 | @@ -61,7 +60,6 @@ |
165 | ) |
166 | from identityprovider.signals import login_failed, login_succeeded |
167 | from identityprovider.stats import stats |
168 | -from identityprovider.timeline_helpers import get_request_timing_function |
169 | from identityprovider.utils import redirection_url_for_token |
170 | from webservices.launchpad import get_lp_ssh_keys |
171 | |
172 | @@ -298,42 +296,6 @@ |
173 | return errors.INVALID_DATA(**missing) |
174 | |
175 | username = data.get('username') |
176 | - captcha_required = (gargoyle.is_active('CAPTCHA', request) and |
177 | - gargoyle.is_active('CAPTCHA_NEW_ACCOUNT', request)) |
178 | - |
179 | - captcha_id = data.get('captcha_id') |
180 | - captcha_solution = data.get('captcha_solution') |
181 | - if not (captcha_solution and captcha_id) and captcha_required: |
182 | - extra = {} |
183 | - if data.get('create_captcha', True): |
184 | - timer = get_request_timing_function(request) |
185 | - try: |
186 | - extra = Captcha.new(timer=timer).serialize() |
187 | - except Exception: |
188 | - logger.exception('failed to create reCaptcha') |
189 | - return errors.CAPTCHA_REQUIRED(**extra) |
190 | - |
191 | - elif captcha_id: |
192 | - captcha = Captcha(captcha_id) |
193 | - verified = False |
194 | - try: |
195 | - timer = get_request_timing_function(request) |
196 | - verified = captcha.verify( |
197 | - captcha_solution, remote_addr, data['email'], |
198 | - timer=timer) |
199 | - except VerifyCaptchaError as e: |
200 | - # only expose HTTP error codes to client |
201 | - code = e.response.code if e.response.code > 200 else None |
202 | - return errors.CAPTCHA_ERROR( |
203 | - recaptcha_reason=e.response.reason, |
204 | - recaptcha_status_code=code, |
205 | - recaptcha_body=e.response.body) |
206 | - except Exception: |
207 | - logger.exception("reCaptcha error") |
208 | - |
209 | - if not verified: |
210 | - message = getattr(captcha, 'message', '') |
211 | - return errors.CAPTCHA_FAILURE(captcha_message=message) |
212 | |
213 | root_url = request.build_absolute_uri('/') |
214 | try: |
215 | @@ -368,8 +330,6 @@ |
216 | The newly created account will be verified and passwordless, |
217 | and the owner will not be able to login until a password reset is done. |
218 | |
219 | - Captcha is completely ignored. |
220 | - |
221 | """ |
222 | data = request.data |
223 | |
224 | |
225 | === modified file 'src/api/v20/tests/test_handlers.py' |
226 | --- src/api/v20/tests/test_handlers.py 2018-10-16 20:30:58 +0000 |
227 | +++ src/api/v20/tests/test_handlers.py 2018-10-16 20:30:58 +0000 |
228 | @@ -7,9 +7,8 @@ |
229 | import time |
230 | import uuid |
231 | |
232 | -from StringIO import StringIO |
233 | from urllib import quote, urlencode |
234 | -from urlparse import parse_qsl, urlparse, urlunparse |
235 | +from urlparse import urlparse, urlunparse |
236 | |
237 | from django.conf import settings |
238 | from django.contrib.auth.hashers import make_password |
239 | @@ -35,7 +34,6 @@ |
240 | AuthToken, |
241 | EmailAddress, |
242 | Token, |
243 | - captcha, |
244 | ) |
245 | from identityprovider.models.const import ( |
246 | AccountCreationRationale, |
247 | @@ -646,196 +644,6 @@ |
248 | self.assert_correct_account_information(json_body) |
249 | |
250 | |
251 | -@override_settings(CAPTCHA_PUBLIC_KEY='public', CAPTCHA_PRIVATE_KEY='private') |
252 | -class AnonymousAccountRegistrationWithCaptchaHandlerTestCase(BaseTestCase): |
253 | - |
254 | - url = reverse('api-registration') |
255 | - |
256 | - def setUp(self): |
257 | - super(AnonymousAccountRegistrationWithCaptchaHandlerTestCase, |
258 | - self).setUp() |
259 | - p = switches(CAPTCHA=True, CAPTCHA_NEW_ACCOUNT=True) |
260 | - p.patch() |
261 | - self.addCleanup(p.unpatch) |
262 | - |
263 | - self.captcha_response = captcha.CaptchaResponse( |
264 | - code=42, response=StringIO("challenge: '999'")) |
265 | - self.mock_captcha_open = self.patch( |
266 | - 'api.v20.handlers.Captcha._open', |
267 | - return_value=self.captcha_response) |
268 | - |
269 | - self.data = { |
270 | - 'email': self.factory.make_email_address(), |
271 | - 'password': 'asdfASDF1', |
272 | - 'displayname': 'Ricardo the Magnificent', |
273 | - 'captcha_id': '999', |
274 | - 'captcha_solution': 'foo bar', |
275 | - } |
276 | - |
277 | - def test_register_captcha_required(self): |
278 | - captcha_data = { |
279 | - 'captcha_id': '999', |
280 | - 'image_url': settings.CAPTCHA_IMAGE_URL_PATTERN % '999'} |
281 | - del self.data['captcha_id'] |
282 | - del self.data['captcha_solution'] |
283 | - |
284 | - json_body = self.do_post(self.data, status_code=401) |
285 | - |
286 | - self.assertEqual(json_body['code'], "CAPTCHA_REQUIRED") |
287 | - self.assertIn('A captcha challenge is required', json_body['message']) |
288 | - self.assertIsNone(Account.objects.get_by_email(self.data['email'])) |
289 | - self.assertEqual(json_body['extra'], captcha_data) |
290 | - |
291 | - def test_register_captcha_success(self): |
292 | - self.captcha_response.response = StringIO('true\nok') |
293 | - json_body = self.do_post(self.data, status_code=201) |
294 | - |
295 | - url_request = self.mock_captcha_open.call_args[0][0] |
296 | - self.assertEqual( |
297 | - url_request.get_full_url(), settings.CAPTCHA_VERIFY_URL) |
298 | - data = dict(parse_qsl(url_request.data)) |
299 | - expected = { |
300 | - 'challenge': '999', 'privatekey': settings.CAPTCHA_PRIVATE_KEY, |
301 | - 'remoteip': '127.0.0.1', 'response': 'foo bar'} |
302 | - self.assertEqual(data, expected) |
303 | - |
304 | - self.assertIn('openid', json_body) |
305 | - self.assertIn('href', json_body) |
306 | - self.assertEqual(json_body['email'], self.data['email']) |
307 | - self.assertEqual(json_body['displayname'], self.data['displayname']) |
308 | - self.assertEqual(json_body['status'], 'Active') |
309 | - self.assertEqual(len(json_body['emails']), 1) |
310 | - self.assertIn(quote(self.data['email'], safe='@'), |
311 | - json_body['emails'][0]['href']) |
312 | - |
313 | - def test_register_captcha_failure(self): |
314 | - self.captcha_response.is_error = False |
315 | - |
316 | - self.data['captcha_id'] = '999' |
317 | - self.data['captcha_solution'] = 'foo bar' |
318 | - |
319 | - json_body = self.do_post(self.data, status_code=403) |
320 | - |
321 | - url_request = self.mock_captcha_open.call_args[0][0] |
322 | - self.assertEqual( |
323 | - url_request.get_full_url(), settings.CAPTCHA_VERIFY_URL) |
324 | - data = dict(parse_qsl(url_request.data)) |
325 | - expected = { |
326 | - 'challenge': '999', 'privatekey': settings.CAPTCHA_PRIVATE_KEY, |
327 | - 'remoteip': '127.0.0.1', 'response': 'foo bar'} |
328 | - self.assertEqual(data, expected) |
329 | - |
330 | - self.assertEqual(json_body['code'], "CAPTCHA_FAILURE") |
331 | - self.assertIn( |
332 | - 'Failed response to captcha challenge.', json_body['message']) |
333 | - self.assertIsNone(Account.objects.get_by_email(self.data['email'])) |
334 | - |
335 | - def test_register_captcha_whitelist(self): |
336 | - self.data['email'] = 'canonicaltest@gmail.com' |
337 | - self.data['captcha_id'] = '999' |
338 | - self.data['captcha_solution'] = 'foo bar' |
339 | - |
340 | - with self.settings(**OVERRIDES): |
341 | - self.do_post(self.data, status_code=201) |
342 | - |
343 | - self.assertIsNotNone(Account.objects.get_by_email(self.data['email'])) |
344 | - self.assertFalse(self.mock_captcha_open.called) |
345 | - |
346 | - def test_register_captcha_whitelist_with_uuid(self): |
347 | - self.data['email'] = 'canonicaltest+something@gmail.com' |
348 | - self.data['captcha_id'] = '999' |
349 | - self.data['captcha_solution'] = 'foo bar' |
350 | - |
351 | - with self.settings(**OVERRIDES): |
352 | - self.do_post(self.data, status_code=201) |
353 | - |
354 | - self.assertIsNotNone(Account.objects.get_by_email(self.data['email'])) |
355 | - self.assertFalse(self.mock_captcha_open.called) |
356 | - |
357 | - def test_register_captcha_whitelist_fail(self): |
358 | - self.data['captcha_id'] = '999' |
359 | - self.data['captcha_solution'] = 'foo bar' |
360 | - self.data['email'] = 'notcanonicaltest@gmail.com' |
361 | - |
362 | - self.captcha_response.is_error = False |
363 | - self.captcha_response.response = StringIO('false\nmessage') |
364 | - |
365 | - with self.settings(**OVERRIDES): |
366 | - self.do_post(self.data, status_code=403) |
367 | - |
368 | - self.assertIsNone(Account.objects.get_by_email(self.data['email'])) |
369 | - self.assertTrue(self.mock_captcha_open.called) |
370 | - |
371 | - def test_register_captcha_http_error(self): |
372 | - response = captcha.CaptchaResponse( |
373 | - 500, None, "", "Server error", "Unexpected recaptcha error") |
374 | - error = captcha.VerifyCaptchaError(response) |
375 | - self.mock_captcha_open.side_effect = error |
376 | - |
377 | - self.data['captcha_id'] = '999' |
378 | - self.data['captcha_solution'] = 'foo bar' |
379 | - |
380 | - json_body = self.do_post(self.data, status_code=502) |
381 | - |
382 | - self.assertEqual(json_body['code'], "CAPTCHA_ERROR") |
383 | - self.assertIn('Unable to get a valid response from reCaptcha service', |
384 | - json_body['message']) |
385 | - extra = json_body['extra'] |
386 | - self.assertEqual(extra['recaptcha_reason'], "Server error") |
387 | - self.assertEqual(extra['recaptcha_status_code'], 500) |
388 | - self.assertEqual(extra['recaptcha_body'], "Unexpected recaptcha error") |
389 | - self.assertIsNone(Account.objects.get_by_email(self.data['email'])) |
390 | - |
391 | - def test_register_captcha_network_error(self): |
392 | - response = captcha.CaptchaResponse(111, None, "", "Connection refused") |
393 | - error = captcha.VerifyCaptchaError(response) |
394 | - self.mock_captcha_open.side_effect = error |
395 | - |
396 | - self.data['captcha_id'] = '999' |
397 | - self.data['captcha_solution'] = 'foo bar' |
398 | - |
399 | - json_body = self.do_post(self.data, status_code=502) |
400 | - |
401 | - self.assertEqual(json_body['code'], "CAPTCHA_ERROR") |
402 | - self.assertIn('Unable to get a valid response from reCaptcha service', |
403 | - json_body['message']) |
404 | - extra = json_body['extra'] |
405 | - self.assertEqual(extra['recaptcha_reason'], "Connection refused") |
406 | - self.assertEqual(extra['recaptcha_status_code'], None) |
407 | - self.assertEqual(extra['recaptcha_body'], None) |
408 | - self.assertIsNone(Account.objects.get_by_email(self.data['email'])) |
409 | - |
410 | - def test_timeline_for_captcha_generation(self): |
411 | - timeline = Timeline() |
412 | - meta = { |
413 | - 'timeline.timeline': timeline, |
414 | - } |
415 | - data = self.data.copy() |
416 | - data.pop('captcha_id') |
417 | - data.pop('captcha_solution') |
418 | - data['create_captcha'] = True |
419 | - |
420 | - self.do_post(data, status_code=401, **meta) |
421 | - |
422 | - self.assertEqual(1, len(timeline.actions)) |
423 | - self.assertEqual('captcha-new', timeline.actions[0].category) |
424 | - self.assertEqual(self.mock_captcha_open.call_args[0][0], |
425 | - timeline.actions[0].detail) |
426 | - |
427 | - def test_timeline_for_captcha_verification(self): |
428 | - self.mock_captcha_open.return_value = captcha.CaptchaResponse( |
429 | - 500, StringIO('false\nsomething')) |
430 | - timeline = Timeline() |
431 | - meta = { |
432 | - 'timeline.timeline': timeline, |
433 | - } |
434 | - |
435 | - self.do_post(self.data, status_code=403, **meta) |
436 | - |
437 | - self.assertEqual(1, len(timeline.actions)) |
438 | - self.assertEqual('captcha-verify', timeline.actions[0].category) |
439 | - |
440 | - |
441 | class AccountRegistrationHandlerTestCase(BaseTestCase): |
442 | url = reverse('api-registration') |
443 | |
444 | @@ -2297,8 +2105,6 @@ |
445 | email = 'foo@foo.com' |
446 | |
447 | data = { |
448 | - 'captcha_id': 'some-id', |
449 | - 'captcha_solution': 'some solution', |
450 | 'email': 'foo@foo.com', |
451 | 'password': 'some-password-123', |
452 | 'displayname': 'Some User', |
453 | |
454 | === modified file 'src/webui/templates/registration/_create_account_form.html' |
455 | --- src/webui/templates/registration/_create_account_form.html 2018-07-04 17:51:43 +0000 |
456 | +++ src/webui/templates/registration/_create_account_form.html 2018-10-16 20:30:58 +0000 |
457 | @@ -50,17 +50,6 @@ |
458 | |
459 | {% include "widgets/passwords.html" with fields=create_form %} |
460 | |
461 | - {% if captcha_required %} |
462 | - <div class="captcha" id="captcha"> |
463 | - {% if captcha_error_message %} |
464 | - <span class="error"> |
465 | - {{ captcha_error_message }} |
466 | - </span> |
467 | - {% endif %} |
468 | - {% include "widgets/recaptcha.html" %} |
469 | - </div> |
470 | - {% endif %} |
471 | - |
472 | <div class="input-row{% if create_form.accept_tos.errors %} haserrors{% endif %} accept-tos-input"> |
473 | |
474 | {% if create_form.accept_tos.errors %} |
475 | |
476 | === removed file 'src/webui/templates/widgets/recaptcha.html' |
477 | --- src/webui/templates/widgets/recaptcha.html 2014-12-11 17:44:29 +0000 |
478 | +++ src/webui/templates/widgets/recaptcha.html 1970-01-01 00:00:00 +0000 |
479 | @@ -1,43 +0,0 @@ |
480 | -{% comment %} |
481 | -Copyright 2010 Canonical Ltd. This software is licensed under the |
482 | -GNU Affero General Public License version 3 (see the file LICENSE). |
483 | -{% endcomment %} |
484 | - |
485 | -{% load i18n %} |
486 | -{% load static_url %} |
487 | -<script type="text/javascript"> |
488 | - var RecaptchaOptions = { |
489 | - theme: 'white', |
490 | - custom_translations: { |
491 | - visual_challenge : "{% trans "Get a visual challenge" %}", |
492 | - audio_challenge : "{% trans "Get an audio challenge" %}", |
493 | - refresh_btn : "{% trans "Get a new challenge" %}", |
494 | - instructions_visual : "{% trans "Type the two words:" %}", |
495 | - instructions_audio : "{% trans "Type what you hear:" %}", |
496 | - help_btn : "{% trans "Help" %}", |
497 | - play_again : "{% trans "Play sound again" %}", |
498 | - cant_hear_this : "{% trans "Download sound as MP3" %}", |
499 | - incorrect_try_again : "{% trans "Incorrect. Try again." %}" |
500 | - }, |
501 | - }; |
502 | -</script> |
503 | -<div {% if captcha_error %}class='captchaError'{% endif %}> |
504 | -{% ifequal captcha_error "&error=no-challenge" %} |
505 | -<p> |
506 | -{% blocktrans with "support_form"|static_url as support_form_url %} |
507 | -It appears that our captcha service was unable to load on this page. |
508 | -This may be caused by a plugin on your browser. |
509 | -Please correct this and try again. If the problem persists, please <a href="{{ support_form_url }}">contact support</a> |
510 | -{% endblocktrans %} |
511 | -</p> |
512 | -{% endifequal %} |
513 | -<script type="text/javascript" src="{{ CAPTCHA_API_URL_SECURE }}/challenge?k={{ CAPTCHA_PUBLIC_KEY }}{{ captcha_error }}"> |
514 | -</script> |
515 | -<noscript> |
516 | - <iframe src="{{ CAPTCHA_API_URL_SECURE }}/noscript?k={{ CAPTCHA_PUBLIC_KEY }}" height="300" width="500" frameborder="0" class="recaptcha-noscript"> |
517 | - </iframe> |
518 | - <textarea class="recaptcha-challenge-field" name="recaptcha_challenge_field" rows="3" cols="40"> |
519 | - </textarea> |
520 | - <input type="hidden" name="recaptcha_response_field" value="manual_challenge"> |
521 | -</noscript> |
522 | -</div> |
523 | |
524 | === modified file 'src/webui/tests/test_views_registration.py' |
525 | --- src/webui/tests/test_views_registration.py 2018-05-28 20:15:33 +0000 |
526 | +++ src/webui/tests/test_views_registration.py 2018-10-16 20:30:58 +0000 |
527 | @@ -161,16 +161,6 @@ |
528 | ctx = response.context_data |
529 | self.assertEqual(ctx['form']['email'].value(), 'test@test.com') |
530 | |
531 | - @switches(CAPTCHA=False) |
532 | - def test_get_optional_captcha_switch_off(self): |
533 | - response = self.get() |
534 | - self.assertEqual(response.context_data['captcha_required'], False) |
535 | - |
536 | - @switches(CAPTCHA=True, CAPTCHA_NEW_ACCOUNT=True) |
537 | - def test_get_optional_captcha_switch_on(self): |
538 | - response = self.get() |
539 | - self.assertEqual(response.context_data['captcha_required'], True) |
540 | - |
541 | def test_post_required_fields(self): |
542 | response = self.post() |
543 | self.assert_form_displayed( |
544 | @@ -213,9 +203,6 @@ |
545 | email=self.TESTDATA['email'], |
546 | password=self.TESTDATA['password'], |
547 | displayname=self.TESTDATA['displayname'], |
548 | - captcha_id=None, |
549 | - captcha_solution=None, |
550 | - create_captcha=False, |
551 | creation_source=WEB_CREATION_SOURCE, |
552 | ) |
553 | |
554 | @@ -242,9 +229,6 @@ |
555 | password=self.TESTDATA['password'], |
556 | displayname=self.TESTDATA['displayname'], |
557 | username=self.TESTDATA['username'], |
558 | - captcha_id=None, |
559 | - captcha_solution=None, |
560 | - create_captcha=False, |
561 | creation_source=WEB_CREATION_SOURCE, |
562 | ) |
563 | self.TESTDATA.pop('username') |
564 | @@ -337,9 +321,6 @@ |
565 | expected_args = dict(email=self.TESTDATA['email'], |
566 | password=self.TESTDATA['password'], |
567 | displayname=self.TESTDATA['displayname'], |
568 | - create_captcha=False, |
569 | - captcha_solution=None, |
570 | - captcha_id=None, |
571 | creation_source='web-flow', |
572 | oid_token=token) |
573 | self.mock_api_register.assert_called_once_with(**expected_args) |
574 | @@ -351,38 +332,6 @@ |
575 | self.assert_form_displayed(response, email=VERIFY_EMAIL_MESSAGE) |
576 | self.assert_stat_calls(['error.email']) |
577 | |
578 | - def test_post_captcha_required(self): |
579 | - exc = api_errors.CaptchaRequired(Mock()) |
580 | - self.mock_api_register.side_effect = exc |
581 | - response = self.post(**self.TESTDATA) |
582 | - self.assert_form_displayed(response) |
583 | - self.assertEqual(response.context_data['captcha_required'], True) |
584 | - |
585 | - def test_post_captcha_failure(self): |
586 | - mock_response = Mock() |
587 | - body = {'extra': {'captcha_message': 'XXX'}} |
588 | - exc = api_errors.CaptchaFailure(mock_response, body) |
589 | - self.mock_api_register.side_effect = exc |
590 | - |
591 | - response = self.post(**self.TESTDATA) |
592 | - self.assert_form_displayed(response) |
593 | - self.assertEqual(response.context_data['captcha_required'], True) |
594 | - self.assertEqual( |
595 | - response.context_data['captcha_error'], |
596 | - '&error=XXX') |
597 | - self.assert_stat_calls(['error.captcha']) |
598 | - |
599 | - def test_post_captcha_error(self): |
600 | - mock_response = Mock() |
601 | - body = {} |
602 | - exc = api_errors.CaptchaError(mock_response, body) |
603 | - self.mock_api_register.side_effect = exc |
604 | - |
605 | - response = self.post(**self.TESTDATA) |
606 | - self.assert_form_displayed(response) |
607 | - self.assertEqual(response.context_data['captcha_required'], True) |
608 | - self.assert_stat_calls(['error.captcha']) |
609 | - |
610 | |
611 | class RegisterTimelineTestCase( |
612 | SSOBaseTestCase, RegisterTestMixin, TimelineActionMixin): |
613 | |
614 | === modified file 'src/webui/tests/test_views_ui.py' |
615 | --- src/webui/tests/test_views_ui.py 2018-09-07 21:54:44 +0000 |
616 | +++ src/webui/tests/test_views_ui.py 2018-10-16 20:30:58 +0000 |
617 | @@ -10,7 +10,6 @@ |
618 | import urllib2 |
619 | from datetime import date |
620 | from functools import partial |
621 | -from StringIO import StringIO |
622 | from urlparse import urlsplit |
623 | |
624 | from django.conf import settings |
625 | @@ -29,7 +28,6 @@ |
626 | from django.test.utils import override_settings |
627 | from django.urls import reverse |
628 | from django.utils.html import escape |
629 | -from gargoyle import gargoyle |
630 | from gargoyle.testutils import switches |
631 | from mock import Mock, patch |
632 | from pyquery import PyQuery |
633 | @@ -50,11 +48,7 @@ |
634 | OpenIDRPConfig, |
635 | twofactor, |
636 | ) |
637 | -from identityprovider.models.captcha import ( |
638 | - Captcha, |
639 | - CaptchaResponse, |
640 | - CaptchaV2, |
641 | -) |
642 | +from identityprovider.models.captcha import CaptchaV2 |
643 | from identityprovider.models.const import ( |
644 | AccountStatus, |
645 | AuthLogType, |
646 | @@ -65,7 +59,6 @@ |
647 | from identityprovider.tests import DEFAULT_USER_PASSWORD |
648 | from identityprovider.tests.test_auth import AuthLogTestCaseMixin |
649 | from identityprovider.tests.utils import ( |
650 | - MockHandler, |
651 | SSOBaseTestCase, |
652 | TimelineActionMixin, |
653 | ) |
654 | @@ -115,9 +108,6 @@ |
655 | 'passwordconfirm': 'Testing123', |
656 | 'accept_tos': True |
657 | } |
658 | - if gargoyle.is_active('CAPTCHA'): |
659 | - data['recaptcha_challenge_field'] = 'ignored' |
660 | - data['recaptcha_response_field'] = 'ignored' |
661 | |
662 | return self.client.post(url, data, follow=follow) |
663 | |
664 | @@ -132,24 +122,6 @@ |
665 | assert self.client.login( |
666 | username=self.data['email'], password=self.data['password']) |
667 | |
668 | - def request_when_captcha_fails(self, url, data): |
669 | - class MockCaptcha(object): |
670 | - def __init__(self, *args): |
671 | - pass |
672 | - |
673 | - def verify(self, solution, ip_addr, email): |
674 | - self.message = 'no-challenge' |
675 | - return False |
676 | - |
677 | - @classmethod |
678 | - def new(cls, env): |
679 | - return cls() |
680 | - |
681 | - with patch.object(ui, 'Captcha', MockCaptcha): |
682 | - r = self.client.post(url, data) |
683 | - |
684 | - return r |
685 | - |
686 | |
687 | @override_settings(LANGUAGE_CODE='es') |
688 | class SpanishUIViewsTestCase(BaseTestCase): |
689 | @@ -1348,62 +1320,6 @@ |
690 | self.assertFalse(email.is_verified) |
691 | |
692 | |
693 | -@override_settings(CAPTCHA_PRIVATE_KEY='some-private-key') |
694 | -class CaptchaVerificationTestCase(BaseTestCase): |
695 | - |
696 | - success_status = 302 |
697 | - |
698 | - def setUp(self): |
699 | - super(CaptchaVerificationTestCase, self).setUp() |
700 | - mock_handler = MockHandler() |
701 | - mock_handler.set_next_response(200, 'false\nno-challenge') |
702 | - self.patch(Captcha, 'opener', new=urllib2.build_opener(mock_handler)) |
703 | - |
704 | - p = switches(CAPTCHA=True) |
705 | - p.patch() |
706 | - self.addCleanup(p.unpatch) |
707 | - |
708 | - def test_new_account_when_form_validation_fails(self): |
709 | - r = self.post_new_account() |
710 | - self.assertTemplateUsed(r, 'registration/new_account.html') |
711 | - msg = 'It appears that our captcha service was unable to load' |
712 | - self.assertContains(r, msg) |
713 | - |
714 | - def test_new_account_captcha_whitelist(self): |
715 | - email = 'canonicaltest@gmail.com' |
716 | - pattern = '^canonicaltest(?:\+.+)?@gmail\.com$' |
717 | - with self.settings(EMAIL_WHITELIST_REGEXP_LIST=[pattern]): |
718 | - response = self.post_new_account(email=email) |
719 | - self.assertEqual(response.status_code, self.success_status) |
720 | - |
721 | - def test_new_account_captcha_whitelist_with_uuid(self): |
722 | - email = 'canonicaltest+something@gmail.com' |
723 | - pattern = '^canonicaltest(?:\+.+)?@gmail\.com$' |
724 | - with self.settings(EMAIL_WHITELIST_REGEXP_LIST=[pattern]): |
725 | - response = self.post_new_account(email=email) |
726 | - self.assertEqual(response.status_code, self.success_status) |
727 | - |
728 | - def test_new_account_captcha_whitelist_fail(self): |
729 | - email = 'notcanonicaltest@gmail.com' |
730 | - pattern = '^canonicaltest(?:\+.+)?@gmail\.com$' |
731 | - with self.settings(EMAIL_WHITELIST_REGEXP_LIST=[pattern]): |
732 | - response = self.post_new_account(email=email) |
733 | - msg = 'It appears that our captcha service was unable to load' |
734 | - self.assertContains(response, msg) |
735 | - |
736 | - @patch.object(Captcha, '_open') |
737 | - def test_uses_timeline_from_request(self, mock_open): |
738 | - mock_open.return_value = CaptchaResponse(200, StringIO('true\na')) |
739 | - request = Mock() |
740 | - timeline = Timeline() |
741 | - request.META = {'timeline.timeline': timeline} |
742 | - request.POST = {'recaptcha_challenge_field': 'captcha-id'} |
743 | - request.environ = {'REMOTE_ADDR': '127.0.0.1'} |
744 | - ui._verify_captcha_response(None, request, None) |
745 | - self.assertEqual(1, len(timeline.actions)) |
746 | - self.assertEqual('captcha-verify', timeline.actions[0].category) |
747 | - |
748 | - |
749 | class CookiesTestCase(SSOBaseTestCase): |
750 | |
751 | def setUp(self): |
752 | |
753 | === modified file 'src/webui/views/registration.py' |
754 | --- src/webui/views/registration.py 2018-05-28 20:15:33 +0000 |
755 | +++ src/webui/views/registration.py 2018-10-16 20:30:58 +0000 |
756 | @@ -53,7 +53,6 @@ |
757 | requires_cookies, |
758 | ) |
759 | from webui.views.utils import ( |
760 | - add_captcha_settings, |
761 | display_email_sent, |
762 | set_session_email, |
763 | ) |
764 | @@ -87,10 +86,6 @@ |
765 | @requires_cookies |
766 | @require_http_methods(['GET', 'POST']) |
767 | def new_account(request, token=None): |
768 | - captcha_required = (gargoyle.is_active('CAPTCHA', request) and |
769 | - gargoyle.is_active('CAPTCHA_NEW_ACCOUNT', request)) |
770 | - captcha_error = '' |
771 | - captcha_error_message = None |
772 | rpconfig = get_rpconfig_from_request(request, token) |
773 | |
774 | def collect_stats(key): |
775 | @@ -108,14 +103,7 @@ |
776 | data = dict((k, v) for k, v in form.cleaned_data.items() |
777 | if k in ('email', 'password', 'displayname', |
778 | 'username')) |
779 | - data['captcha_id'] = request.POST.get( |
780 | - 'recaptcha_challenge_field' |
781 | - ) |
782 | - data['captcha_solution'] = request.POST.get( |
783 | - 'recaptcha_response_field' |
784 | - ) |
785 | # we'll handle our own capture generation |
786 | - data['create_captcha'] = False |
787 | data['creation_source'] = WEB_CREATION_SOURCE |
788 | if token: |
789 | data['oid_token'] = token |
790 | @@ -136,15 +124,6 @@ |
791 | collect_stats('error.email') |
792 | form._errors['email'] = [VERIFY_EMAIL_MESSAGE] |
793 | |
794 | - except api_errors.CaptchaRequired as e: |
795 | - captcha_required = True |
796 | - collect_stats('captcha_required') |
797 | - |
798 | - except (api_errors.CaptchaFailure, api_errors.CaptchaError) as e: |
799 | - captcha_required = True |
800 | - captcha_error = '&error=' + e.extra.get('captcha_message', '') |
801 | - captcha_error_message = _('Incorrect captcha solution') |
802 | - collect_stats('error.captcha') |
803 | except Exception as e: |
804 | return HttpResponseServerError("exception: " + str(e)) |
805 | else: |
806 | @@ -175,12 +154,7 @@ |
807 | 'form': form, |
808 | 'rpconfig': rpconfig, |
809 | 'token': token, |
810 | - 'captcha_required': captcha_required, |
811 | - 'captcha_error': captcha_error, |
812 | - 'captcha_error_message': captcha_error_message, |
813 | } |
814 | - if captcha_required: |
815 | - context = add_captcha_settings(context) |
816 | |
817 | if form.errors: |
818 | err = form.errors.get('email', [''])[0] |
819 | |
820 | === modified file 'src/webui/views/ui.py' |
821 | --- src/webui/views/ui.py 2018-08-24 15:30:53 +0000 |
822 | +++ src/webui/views/ui.py 2018-10-16 20:30:58 +0000 |
823 | @@ -56,11 +56,7 @@ |
824 | |
825 | ) |
826 | from identityprovider.models import twofactor |
827 | -from identityprovider.models.captcha import ( |
828 | - Captcha, |
829 | - CaptchaV2, |
830 | - VerifyCaptchaError |
831 | -) |
832 | +from identityprovider.models.captcha import CaptchaV2 |
833 | from identityprovider.models.const import AccountStatus, AuthTokenType |
834 | from identityprovider.signals import login_failed, login_succeeded |
835 | from identityprovider.signed import BadSignedValue |
836 | @@ -94,7 +90,6 @@ |
837 | requires_cookies, |
838 | ) |
839 | from webui.views import registration |
840 | -from webui.views.utils import add_captcha_settings |
841 | |
842 | |
843 | ACCOUNT_CREATED = _("Your account was created successfully") |
844 | @@ -150,11 +145,6 @@ |
845 | self, request, token, rpconfig, form, create_account_form=None): |
846 | context = super(LoginView, self).get_context( |
847 | request, token=token, rpconfig=rpconfig, form=form) |
848 | - # add captcha and account creation form |
849 | - context['captcha_required'] = ( |
850 | - gargoyle.is_active('CAPTCHA', request) and |
851 | - gargoyle.is_active('CAPTCHA_NEW_ACCOUNT', request)) |
852 | - context = add_captcha_settings(context) |
853 | context['create_account_form'] = create_account_form |
854 | return context |
855 | |
856 | @@ -503,30 +493,6 @@ |
857 | return registration.new_account(request, token) |
858 | |
859 | |
860 | -def _verify_captcha_response(template, request, form): |
861 | - captcha = Captcha(request.POST.get('recaptcha_challenge_field')) |
862 | - captcha_solution = request.POST.get('recaptcha_response_field') |
863 | - email = request.POST.get('email', '') |
864 | - ip_addr = request.environ["REMOTE_ADDR"] |
865 | - try: |
866 | - timer_fn = get_request_timing_function(request) |
867 | - verified = captcha.verify(captcha_solution, ip_addr, email, |
868 | - timer=timer_fn) |
869 | - if verified: |
870 | - return None |
871 | - except VerifyCaptchaError: |
872 | - logger.exception("reCaptcha connection error") |
873 | - |
874 | - # not verified |
875 | - return render( |
876 | - request, |
877 | - template, |
878 | - add_captcha_settings({ |
879 | - 'form': form, |
880 | - 'captcha_error': ('&error=%s' % captcha.message), |
881 | - 'captcha_required': True})) |
882 | - |
883 | - |
884 | @require_twofactor_authenticated( |
885 | _("Please log in to use this confirmation code")) |
886 | def confirm_email(request, authtoken, email_address, token=None): |
887 | |
888 | === modified file 'src/webui/views/utils.py' |
889 | --- src/webui/views/utils.py 2015-05-08 19:25:27 +0000 |
890 | +++ src/webui/views/utils.py 2018-10-16 20:30:58 +0000 |
891 | @@ -43,10 +43,3 @@ |
892 | def set_session_email(session, email): |
893 | """Place information about the current token's email in the session""" |
894 | session['token_email'] = email |
895 | - |
896 | - |
897 | -def add_captcha_settings(context): |
898 | - d = {'CAPTCHA_PUBLIC_KEY': settings.CAPTCHA_PUBLIC_KEY, |
899 | - 'CAPTCHA_API_URL_SECURE': settings.CAPTCHA_API_URL_SECURE} |
900 | - d.update(context) |
901 | - return d |
Will split in 3 MPs.