Merge lp:~maxiberta/canonical-identity-provider/drop-account-registration-captcha-web into lp:canonical-identity-provider/release

Proposed by Maximiliano Bertacchini
Status: Merged
Merged at revision: 1668
Proposed branch: lp:~maxiberta/canonical-identity-provider/drop-account-registration-captcha-web
Merge into: lp:canonical-identity-provider/release
Prerequisite: lp:~maxiberta/canonical-identity-provider/drop-account-registration-captcha-api-v1
Diff against target: 448 lines (+2/-258)
7 files modified
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-web
Reviewer Review Type Date Requested Status
Matias Bordese (community) Approve
Review via email: mp+356877@code.launchpad.net

Commit message

Drop captcha from account registration via 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
Matias Bordese (matiasb) wrote :

LGTM

review: Approve
Revision history for this message
Otto Co-Pilot (otto-copilot) wrote :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/webui/templates/registration/_create_account_form.html'
--- src/webui/templates/registration/_create_account_form.html 2018-07-04 17:51:43 +0000
+++ src/webui/templates/registration/_create_account_form.html 2018-10-22 16:30:41 +0000
@@ -50,17 +50,6 @@
5050
51 {% include "widgets/passwords.html" with fields=create_form %}51 {% include "widgets/passwords.html" with fields=create_form %}
5252
53 {% if captcha_required %}
54 <div class="captcha" id="captcha">
55 {% if captcha_error_message %}
56 <span class="error">
57 {{ captcha_error_message }}
58 </span>
59 {% endif %}
60 {% include "widgets/recaptcha.html" %}
61 </div>
62 {% endif %}
63
64 <div class="input-row{% if create_form.accept_tos.errors %} haserrors{% endif %} accept-tos-input">53 <div class="input-row{% if create_form.accept_tos.errors %} haserrors{% endif %} accept-tos-input">
6554
66 {% if create_form.accept_tos.errors %}55 {% if create_form.accept_tos.errors %}
6756
=== removed file 'src/webui/templates/widgets/recaptcha.html'
--- src/webui/templates/widgets/recaptcha.html 2014-12-11 17:44:29 +0000
+++ src/webui/templates/widgets/recaptcha.html 1970-01-01 00:00:00 +0000
@@ -1,43 +0,0 @@
1{% comment %}
2Copyright 2010 Canonical Ltd. This software is licensed under the
3GNU Affero General Public License version 3 (see the file LICENSE).
4{% endcomment %}
5
6{% load i18n %}
7{% load static_url %}
8<script type="text/javascript">
9 var RecaptchaOptions = {
10 theme: 'white',
11 custom_translations: {
12 visual_challenge : "{% trans "Get a visual challenge" %}",
13 audio_challenge : "{% trans "Get an audio challenge" %}",
14 refresh_btn : "{% trans "Get a new challenge" %}",
15 instructions_visual : "{% trans "Type the two words:" %}",
16 instructions_audio : "{% trans "Type what you hear:" %}",
17 help_btn : "{% trans "Help" %}",
18 play_again : "{% trans "Play sound again" %}",
19 cant_hear_this : "{% trans "Download sound as MP3" %}",
20 incorrect_try_again : "{% trans "Incorrect. Try again." %}"
21 },
22 };
23</script>
24<div {% if captcha_error %}class='captchaError'{% endif %}>
25{% ifequal captcha_error "&error=no-challenge" %}
26<p>
27{% blocktrans with "support_form"|static_url as support_form_url %}
28It appears that our captcha service was unable to load on this page.
29This may be caused by a plugin on your browser.
30Please correct this and try again. If the problem persists, please <a href="{{ support_form_url }}">contact support</a>
31{% endblocktrans %}
32</p>
33{% endifequal %}
34<script type="text/javascript" src="{{ CAPTCHA_API_URL_SECURE }}/challenge?k={{ CAPTCHA_PUBLIC_KEY }}{{ captcha_error }}">
35</script>
36<noscript>
37 <iframe src="{{ CAPTCHA_API_URL_SECURE }}/noscript?k={{ CAPTCHA_PUBLIC_KEY }}" height="300" width="500" frameborder="0" class="recaptcha-noscript">
38 </iframe>
39 <textarea class="recaptcha-challenge-field" name="recaptcha_challenge_field" rows="3" cols="40">
40 </textarea>
41 <input type="hidden" name="recaptcha_response_field" value="manual_challenge">
42</noscript>
43</div>
440
=== modified file 'src/webui/tests/test_views_registration.py'
--- src/webui/tests/test_views_registration.py 2018-05-28 20:15:33 +0000
+++ src/webui/tests/test_views_registration.py 2018-10-22 16:30:41 +0000
@@ -161,16 +161,6 @@
161 ctx = response.context_data161 ctx = response.context_data
162 self.assertEqual(ctx['form']['email'].value(), 'test@test.com')162 self.assertEqual(ctx['form']['email'].value(), 'test@test.com')
163163
164 @switches(CAPTCHA=False)
165 def test_get_optional_captcha_switch_off(self):
166 response = self.get()
167 self.assertEqual(response.context_data['captcha_required'], False)
168
169 @switches(CAPTCHA=True, CAPTCHA_NEW_ACCOUNT=True)
170 def test_get_optional_captcha_switch_on(self):
171 response = self.get()
172 self.assertEqual(response.context_data['captcha_required'], True)
173
174 def test_post_required_fields(self):164 def test_post_required_fields(self):
175 response = self.post()165 response = self.post()
176 self.assert_form_displayed(166 self.assert_form_displayed(
@@ -213,9 +203,6 @@
213 email=self.TESTDATA['email'],203 email=self.TESTDATA['email'],
214 password=self.TESTDATA['password'],204 password=self.TESTDATA['password'],
215 displayname=self.TESTDATA['displayname'],205 displayname=self.TESTDATA['displayname'],
216 captcha_id=None,
217 captcha_solution=None,
218 create_captcha=False,
219 creation_source=WEB_CREATION_SOURCE,206 creation_source=WEB_CREATION_SOURCE,
220 )207 )
221208
@@ -242,9 +229,6 @@
242 password=self.TESTDATA['password'],229 password=self.TESTDATA['password'],
243 displayname=self.TESTDATA['displayname'],230 displayname=self.TESTDATA['displayname'],
244 username=self.TESTDATA['username'],231 username=self.TESTDATA['username'],
245 captcha_id=None,
246 captcha_solution=None,
247 create_captcha=False,
248 creation_source=WEB_CREATION_SOURCE,232 creation_source=WEB_CREATION_SOURCE,
249 )233 )
250 self.TESTDATA.pop('username')234 self.TESTDATA.pop('username')
@@ -337,9 +321,6 @@
337 expected_args = dict(email=self.TESTDATA['email'],321 expected_args = dict(email=self.TESTDATA['email'],
338 password=self.TESTDATA['password'],322 password=self.TESTDATA['password'],
339 displayname=self.TESTDATA['displayname'],323 displayname=self.TESTDATA['displayname'],
340 create_captcha=False,
341 captcha_solution=None,
342 captcha_id=None,
343 creation_source='web-flow',324 creation_source='web-flow',
344 oid_token=token)325 oid_token=token)
345 self.mock_api_register.assert_called_once_with(**expected_args)326 self.mock_api_register.assert_called_once_with(**expected_args)
@@ -351,38 +332,6 @@
351 self.assert_form_displayed(response, email=VERIFY_EMAIL_MESSAGE)332 self.assert_form_displayed(response, email=VERIFY_EMAIL_MESSAGE)
352 self.assert_stat_calls(['error.email'])333 self.assert_stat_calls(['error.email'])
353334
354 def test_post_captcha_required(self):
355 exc = api_errors.CaptchaRequired(Mock())
356 self.mock_api_register.side_effect = exc
357 response = self.post(**self.TESTDATA)
358 self.assert_form_displayed(response)
359 self.assertEqual(response.context_data['captcha_required'], True)
360
361 def test_post_captcha_failure(self):
362 mock_response = Mock()
363 body = {'extra': {'captcha_message': 'XXX'}}
364 exc = api_errors.CaptchaFailure(mock_response, body)
365 self.mock_api_register.side_effect = exc
366
367 response = self.post(**self.TESTDATA)
368 self.assert_form_displayed(response)
369 self.assertEqual(response.context_data['captcha_required'], True)
370 self.assertEqual(
371 response.context_data['captcha_error'],
372 '&error=XXX')
373 self.assert_stat_calls(['error.captcha'])
374
375 def test_post_captcha_error(self):
376 mock_response = Mock()
377 body = {}
378 exc = api_errors.CaptchaError(mock_response, body)
379 self.mock_api_register.side_effect = exc
380
381 response = self.post(**self.TESTDATA)
382 self.assert_form_displayed(response)
383 self.assertEqual(response.context_data['captcha_required'], True)
384 self.assert_stat_calls(['error.captcha'])
385
386335
387class RegisterTimelineTestCase(336class RegisterTimelineTestCase(
388 SSOBaseTestCase, RegisterTestMixin, TimelineActionMixin):337 SSOBaseTestCase, RegisterTestMixin, TimelineActionMixin):
389338
=== modified file 'src/webui/tests/test_views_ui.py'
--- src/webui/tests/test_views_ui.py 2018-09-07 21:54:44 +0000
+++ src/webui/tests/test_views_ui.py 2018-10-22 16:30:41 +0000
@@ -10,7 +10,6 @@
10import urllib210import urllib2
11from datetime import date11from datetime import date
12from functools import partial12from functools import partial
13from StringIO import StringIO
14from urlparse import urlsplit13from urlparse import urlsplit
1514
16from django.conf import settings15from django.conf import settings
@@ -29,7 +28,6 @@
29from django.test.utils import override_settings28from django.test.utils import override_settings
30from django.urls import reverse29from django.urls import reverse
31from django.utils.html import escape30from django.utils.html import escape
32from gargoyle import gargoyle
33from gargoyle.testutils import switches31from gargoyle.testutils import switches
34from mock import Mock, patch32from mock import Mock, patch
35from pyquery import PyQuery33from pyquery import PyQuery
@@ -50,11 +48,7 @@
50 OpenIDRPConfig,48 OpenIDRPConfig,
51 twofactor,49 twofactor,
52)50)
53from identityprovider.models.captcha import (51from identityprovider.models.captcha import CaptchaV2
54 Captcha,
55 CaptchaResponse,
56 CaptchaV2,
57)
58from identityprovider.models.const import (52from identityprovider.models.const import (
59 AccountStatus,53 AccountStatus,
60 AuthLogType,54 AuthLogType,
@@ -65,7 +59,6 @@
65from identityprovider.tests import DEFAULT_USER_PASSWORD59from identityprovider.tests import DEFAULT_USER_PASSWORD
66from identityprovider.tests.test_auth import AuthLogTestCaseMixin60from identityprovider.tests.test_auth import AuthLogTestCaseMixin
67from identityprovider.tests.utils import (61from identityprovider.tests.utils import (
68 MockHandler,
69 SSOBaseTestCase,62 SSOBaseTestCase,
70 TimelineActionMixin,63 TimelineActionMixin,
71)64)
@@ -115,9 +108,6 @@
115 'passwordconfirm': 'Testing123',108 'passwordconfirm': 'Testing123',
116 'accept_tos': True109 'accept_tos': True
117 }110 }
118 if gargoyle.is_active('CAPTCHA'):
119 data['recaptcha_challenge_field'] = 'ignored'
120 data['recaptcha_response_field'] = 'ignored'
121111
122 return self.client.post(url, data, follow=follow)112 return self.client.post(url, data, follow=follow)
123113
@@ -132,24 +122,6 @@
132 assert self.client.login(122 assert self.client.login(
133 username=self.data['email'], password=self.data['password'])123 username=self.data['email'], password=self.data['password'])
134124
135 def request_when_captcha_fails(self, url, data):
136 class MockCaptcha(object):
137 def __init__(self, *args):
138 pass
139
140 def verify(self, solution, ip_addr, email):
141 self.message = 'no-challenge'
142 return False
143
144 @classmethod
145 def new(cls, env):
146 return cls()
147
148 with patch.object(ui, 'Captcha', MockCaptcha):
149 r = self.client.post(url, data)
150
151 return r
152
153125
154@override_settings(LANGUAGE_CODE='es')126@override_settings(LANGUAGE_CODE='es')
155class SpanishUIViewsTestCase(BaseTestCase):127class SpanishUIViewsTestCase(BaseTestCase):
@@ -1348,62 +1320,6 @@
1348 self.assertFalse(email.is_verified)1320 self.assertFalse(email.is_verified)
13491321
13501322
1351@override_settings(CAPTCHA_PRIVATE_KEY='some-private-key')
1352class CaptchaVerificationTestCase(BaseTestCase):
1353
1354 success_status = 302
1355
1356 def setUp(self):
1357 super(CaptchaVerificationTestCase, self).setUp()
1358 mock_handler = MockHandler()
1359 mock_handler.set_next_response(200, 'false\nno-challenge')
1360 self.patch(Captcha, 'opener', new=urllib2.build_opener(mock_handler))
1361
1362 p = switches(CAPTCHA=True)
1363 p.patch()
1364 self.addCleanup(p.unpatch)
1365
1366 def test_new_account_when_form_validation_fails(self):
1367 r = self.post_new_account()
1368 self.assertTemplateUsed(r, 'registration/new_account.html')
1369 msg = 'It appears that our captcha service was unable to load'
1370 self.assertContains(r, msg)
1371
1372 def test_new_account_captcha_whitelist(self):
1373 email = 'canonicaltest@gmail.com'
1374 pattern = '^canonicaltest(?:\+.+)?@gmail\.com$'
1375 with self.settings(EMAIL_WHITELIST_REGEXP_LIST=[pattern]):
1376 response = self.post_new_account(email=email)
1377 self.assertEqual(response.status_code, self.success_status)
1378
1379 def test_new_account_captcha_whitelist_with_uuid(self):
1380 email = 'canonicaltest+something@gmail.com'
1381 pattern = '^canonicaltest(?:\+.+)?@gmail\.com$'
1382 with self.settings(EMAIL_WHITELIST_REGEXP_LIST=[pattern]):
1383 response = self.post_new_account(email=email)
1384 self.assertEqual(response.status_code, self.success_status)
1385
1386 def test_new_account_captcha_whitelist_fail(self):
1387 email = 'notcanonicaltest@gmail.com'
1388 pattern = '^canonicaltest(?:\+.+)?@gmail\.com$'
1389 with self.settings(EMAIL_WHITELIST_REGEXP_LIST=[pattern]):
1390 response = self.post_new_account(email=email)
1391 msg = 'It appears that our captcha service was unable to load'
1392 self.assertContains(response, msg)
1393
1394 @patch.object(Captcha, '_open')
1395 def test_uses_timeline_from_request(self, mock_open):
1396 mock_open.return_value = CaptchaResponse(200, StringIO('true\na'))
1397 request = Mock()
1398 timeline = Timeline()
1399 request.META = {'timeline.timeline': timeline}
1400 request.POST = {'recaptcha_challenge_field': 'captcha-id'}
1401 request.environ = {'REMOTE_ADDR': '127.0.0.1'}
1402 ui._verify_captcha_response(None, request, None)
1403 self.assertEqual(1, len(timeline.actions))
1404 self.assertEqual('captcha-verify', timeline.actions[0].category)
1405
1406
1407class CookiesTestCase(SSOBaseTestCase):1323class CookiesTestCase(SSOBaseTestCase):
14081324
1409 def setUp(self):1325 def setUp(self):
14101326
=== modified file 'src/webui/views/registration.py'
--- src/webui/views/registration.py 2018-05-28 20:15:33 +0000
+++ src/webui/views/registration.py 2018-10-22 16:30:41 +0000
@@ -53,7 +53,6 @@
53 requires_cookies,53 requires_cookies,
54)54)
55from webui.views.utils import (55from webui.views.utils import (
56 add_captcha_settings,
57 display_email_sent,56 display_email_sent,
58 set_session_email,57 set_session_email,
59)58)
@@ -87,10 +86,6 @@
87@requires_cookies86@requires_cookies
88@require_http_methods(['GET', 'POST'])87@require_http_methods(['GET', 'POST'])
89def new_account(request, token=None):88def new_account(request, token=None):
90 captcha_required = (gargoyle.is_active('CAPTCHA', request) and
91 gargoyle.is_active('CAPTCHA_NEW_ACCOUNT', request))
92 captcha_error = ''
93 captcha_error_message = None
94 rpconfig = get_rpconfig_from_request(request, token)89 rpconfig = get_rpconfig_from_request(request, token)
9590
96 def collect_stats(key):91 def collect_stats(key):
@@ -108,14 +103,7 @@
108 data = dict((k, v) for k, v in form.cleaned_data.items()103 data = dict((k, v) for k, v in form.cleaned_data.items()
109 if k in ('email', 'password', 'displayname',104 if k in ('email', 'password', 'displayname',
110 'username'))105 'username'))
111 data['captcha_id'] = request.POST.get(
112 'recaptcha_challenge_field'
113 )
114 data['captcha_solution'] = request.POST.get(
115 'recaptcha_response_field'
116 )
117 # we'll handle our own capture generation106 # we'll handle our own capture generation
118 data['create_captcha'] = False
119 data['creation_source'] = WEB_CREATION_SOURCE107 data['creation_source'] = WEB_CREATION_SOURCE
120 if token:108 if token:
121 data['oid_token'] = token109 data['oid_token'] = token
@@ -136,15 +124,6 @@
136 collect_stats('error.email')124 collect_stats('error.email')
137 form._errors['email'] = [VERIFY_EMAIL_MESSAGE]125 form._errors['email'] = [VERIFY_EMAIL_MESSAGE]
138126
139 except api_errors.CaptchaRequired as e:
140 captcha_required = True
141 collect_stats('captcha_required')
142
143 except (api_errors.CaptchaFailure, api_errors.CaptchaError) as e:
144 captcha_required = True
145 captcha_error = '&error=' + e.extra.get('captcha_message', '')
146 captcha_error_message = _('Incorrect captcha solution')
147 collect_stats('error.captcha')
148 except Exception as e:127 except Exception as e:
149 return HttpResponseServerError("exception: " + str(e))128 return HttpResponseServerError("exception: " + str(e))
150 else:129 else:
@@ -175,12 +154,7 @@
175 'form': form,154 'form': form,
176 'rpconfig': rpconfig,155 'rpconfig': rpconfig,
177 'token': token,156 'token': token,
178 'captcha_required': captcha_required,
179 'captcha_error': captcha_error,
180 'captcha_error_message': captcha_error_message,
181 }157 }
182 if captcha_required:
183 context = add_captcha_settings(context)
184158
185 if form.errors:159 if form.errors:
186 err = form.errors.get('email', [''])[0]160 err = form.errors.get('email', [''])[0]
187161
=== modified file 'src/webui/views/ui.py'
--- src/webui/views/ui.py 2018-08-24 15:30:53 +0000
+++ src/webui/views/ui.py 2018-10-22 16:30:41 +0000
@@ -56,11 +56,7 @@
5656
57)57)
58from identityprovider.models import twofactor58from identityprovider.models import twofactor
59from identityprovider.models.captcha import (59from identityprovider.models.captcha import CaptchaV2
60 Captcha,
61 CaptchaV2,
62 VerifyCaptchaError
63)
64from identityprovider.models.const import AccountStatus, AuthTokenType60from identityprovider.models.const import AccountStatus, AuthTokenType
65from identityprovider.signals import login_failed, login_succeeded61from identityprovider.signals import login_failed, login_succeeded
66from identityprovider.signed import BadSignedValue62from identityprovider.signed import BadSignedValue
@@ -94,7 +90,6 @@
94 requires_cookies,90 requires_cookies,
95)91)
96from webui.views import registration92from webui.views import registration
97from webui.views.utils import add_captcha_settings
9893
9994
100ACCOUNT_CREATED = _("Your account was created successfully")95ACCOUNT_CREATED = _("Your account was created successfully")
@@ -150,11 +145,6 @@
150 self, request, token, rpconfig, form, create_account_form=None):145 self, request, token, rpconfig, form, create_account_form=None):
151 context = super(LoginView, self).get_context(146 context = super(LoginView, self).get_context(
152 request, token=token, rpconfig=rpconfig, form=form)147 request, token=token, rpconfig=rpconfig, form=form)
153 # add captcha and account creation form
154 context['captcha_required'] = (
155 gargoyle.is_active('CAPTCHA', request) and
156 gargoyle.is_active('CAPTCHA_NEW_ACCOUNT', request))
157 context = add_captcha_settings(context)
158 context['create_account_form'] = create_account_form148 context['create_account_form'] = create_account_form
159 return context149 return context
160150
@@ -503,30 +493,6 @@
503 return registration.new_account(request, token)493 return registration.new_account(request, token)
504494
505495
506def _verify_captcha_response(template, request, form):
507 captcha = Captcha(request.POST.get('recaptcha_challenge_field'))
508 captcha_solution = request.POST.get('recaptcha_response_field')
509 email = request.POST.get('email', '')
510 ip_addr = request.environ["REMOTE_ADDR"]
511 try:
512 timer_fn = get_request_timing_function(request)
513 verified = captcha.verify(captcha_solution, ip_addr, email,
514 timer=timer_fn)
515 if verified:
516 return None
517 except VerifyCaptchaError:
518 logger.exception("reCaptcha connection error")
519
520 # not verified
521 return render(
522 request,
523 template,
524 add_captcha_settings({
525 'form': form,
526 'captcha_error': ('&error=%s' % captcha.message),
527 'captcha_required': True}))
528
529
530@require_twofactor_authenticated(496@require_twofactor_authenticated(
531 _("Please log in to use this confirmation code"))497 _("Please log in to use this confirmation code"))
532def confirm_email(request, authtoken, email_address, token=None):498def confirm_email(request, authtoken, email_address, token=None):
533499
=== modified file 'src/webui/views/utils.py'
--- src/webui/views/utils.py 2015-05-08 19:25:27 +0000
+++ src/webui/views/utils.py 2018-10-22 16:30:41 +0000
@@ -43,10 +43,3 @@
43def set_session_email(session, email):43def set_session_email(session, email):
44 """Place information about the current token's email in the session"""44 """Place information about the current token's email in the session"""
45 session['token_email'] = email45 session['token_email'] = email
46
47
48def add_captcha_settings(context):
49 d = {'CAPTCHA_PUBLIC_KEY': settings.CAPTCHA_PUBLIC_KEY,
50 'CAPTCHA_API_URL_SECURE': settings.CAPTCHA_API_URL_SECURE}
51 d.update(context)
52 return d