Skip to content

Commit c4040af

Browse files
authored
Catch a generic CaptchaError and make captcha service usage more generic (#18651)
* Adjust configuration to avoid circular import We shouldn't default to a ReCaptcha service either * Use a generic form field * Return a generic error message * Use a generic CaptchaError instead
1 parent aa048cb commit c4040af

File tree

10 files changed

+27
-42
lines changed

10 files changed

+27
-42
lines changed

dev/environment

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ HCAPTCHA_SECRET_KEY=0x0000000000000000000000000000000000000000
8181
# HCAPTCHA_SITE_KEY=30000000-ffff-ffff-ffff-000000000003
8282

8383
# Example of Captcha backend configuration
84-
# CAPTCHA_BACKEND=warehouse.captcha.hcaptcha.Service
84+
CAPTCHA_BACKEND=warehouse.captcha.hcaptcha.Service
8585

8686
# Example of HelpScout configuration
8787
# HELPSCOUT_WAREHOUSE_APP_ID="an insecure helpscout app id"

tests/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ def get_app_config(database, nondefaults=None):
346346
"oidc.jwk_cache_url": "redis://localhost:0/",
347347
"warehouse.oidc.audience": "pypi",
348348
"oidc.backend": "warehouse.oidc.services.NullOIDCPublisherService",
349+
"captcha.backend": "warehouse.captcha.hcaptcha.Service",
349350
}
350351

351352
if nondefaults:

tests/unit/accounts/test_forms.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ def test_validate(self, metrics):
433433
"new_password": "mysupersecurepassword1!",
434434
"password_confirm": "mysupersecurepassword1!",
435435
"email": "[email protected]",
436-
"g_recaptcha_reponse": "",
436+
"captcha_reponse": "",
437437
}
438438
),
439439
user_service=user_service,
@@ -634,12 +634,12 @@ def test_recaptcha_disabled(self):
634634
assert not form.validate()
635635
# there shouldn't be any errors for the recaptcha field if it's
636636
# disabled
637-
assert not form.g_recaptcha_response.errors
637+
assert not form.captcha_response.errors
638638

639639
def test_recaptcha_required_error(self):
640640
form = forms.RegistrationForm(
641641
request=pretend.stub(),
642-
formdata=MultiDict({"g_recaptcha_response": ""}),
642+
formdata=MultiDict({"captcha_response": ""}),
643643
user_service=pretend.stub(),
644644
captcha_service=pretend.stub(
645645
enabled=True,
@@ -648,12 +648,12 @@ def test_recaptcha_required_error(self):
648648
breach_service=pretend.stub(check_password=lambda pw, tags=None: False),
649649
)
650650
assert not form.validate()
651-
assert form.g_recaptcha_response.errors.pop() == "Recaptcha error."
651+
assert form.captcha_response.errors.pop() == "Captcha error."
652652

653653
def test_recaptcha_error(self):
654654
form = forms.RegistrationForm(
655655
request=pretend.stub(),
656-
formdata=MultiDict({"g_recaptcha_response": "asd"}),
656+
formdata=MultiDict({"captcha_response": "asd"}),
657657
user_service=pretend.stub(),
658658
captcha_service=pretend.stub(
659659
verify_response=pretend.raiser(recaptcha.RecaptchaError),
@@ -662,7 +662,7 @@ def test_recaptcha_error(self):
662662
breach_service=pretend.stub(check_password=lambda pw, tags=None: False),
663663
)
664664
assert not form.validate()
665-
assert form.g_recaptcha_response.errors.pop() == "Recaptcha error."
665+
assert form.captcha_response.errors.pop() == "Captcha error."
666666

667667
def test_username_exists(self, pyramid_config):
668668
form = forms.RegistrationForm(

tests/unit/accounts/test_views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1752,7 +1752,7 @@ def _find_service(service=None, name=None, context=None):
17521752
"password_confirm": "MyStr0ng!shP455w0rd",
17531753
"email": "[email protected]",
17541754
"full_name": "full_name",
1755-
"g_recaptcha_response": "captchavalue",
1755+
"captcha_response": "captchavalue",
17561756
}
17571757
)
17581758

tests/unit/captcha/test_init.py

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,7 @@
22

33
import pretend
44

5-
from warehouse.captcha import includeme, interfaces, recaptcha
6-
7-
8-
def test_includeme_defaults_to_recaptcha():
9-
config = pretend.stub(
10-
registry=pretend.stub(settings={}),
11-
maybe_dotted=lambda i: i,
12-
register_service_factory=pretend.call_recorder(
13-
lambda factory, iface, name: None
14-
),
15-
)
16-
includeme(config)
17-
18-
assert config.register_service_factory.calls == [
19-
pretend.call(
20-
recaptcha.Service.create_service,
21-
interfaces.ICaptchaService,
22-
name="captcha",
23-
),
24-
]
5+
from warehouse.captcha import includeme, interfaces
256

267

278
def test_include_with_custom_backend():

warehouse/accounts/forms.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
)
3030
from warehouse.accounts.models import DisableReason, ProhibitedEmailDomain
3131
from warehouse.accounts.services import RECOVERY_CODE_BYTES
32-
from warehouse.captcha import recaptcha
32+
from warehouse.captcha import CaptchaError
3333
from warehouse.constants import MAX_PASSWORD_SIZE
3434
from warehouse.email import (
3535
send_password_compromised_email_hibp,
@@ -407,24 +407,24 @@ class RegistrationForm( # type: ignore[misc]
407407
PreventNullBytesValidator(),
408408
]
409409
)
410-
g_recaptcha_response = wtforms.StringField()
410+
captcha_response = wtforms.StringField()
411411

412412
def __init__(self, *args, captcha_service, user_service, **kwargs):
413413
super().__init__(*args, **kwargs)
414414
self.user_service = user_service
415415
self.user_id = None
416416
self.captcha_service = captcha_service
417417

418-
def validate_g_recaptcha_response(self, field):
418+
def validate_captcha_response(self, field):
419419
# do required data validation here due to enabled flag being required
420420
if self.captcha_service.enabled and not field.data:
421-
raise wtforms.validators.ValidationError("Recaptcha error.")
421+
raise wtforms.validators.ValidationError("Captcha error.")
422422
try:
423423
self.captcha_service.verify_response(field.data)
424-
except recaptcha.RecaptchaError:
424+
except CaptchaError:
425425
# TODO: log error
426426
# don't want to provide the user with any detail
427-
raise wtforms.validators.ValidationError("Recaptcha error.")
427+
raise wtforms.validators.ValidationError("Captcha error.")
428428

429429

430430
class LoginForm(PasswordMixin, UsernameMixin, wtforms.Form):

warehouse/captcha/__init__.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
# SPDX-License-Identifier: Apache-2.0
22

33
from .interfaces import ICaptchaService
4-
from .recaptcha import Service
4+
5+
6+
class CaptchaError(ValueError):
7+
pass
58

69

710
def includeme(config):
811
# Register our Captcha service
9-
captcha_class = config.maybe_dotted(
10-
config.registry.settings.get("captcha.backend", Service)
11-
)
12+
captcha_class = config.maybe_dotted(config.registry.settings["captcha.backend"])
1213
config.register_service_factory(
1314
captcha_class.create_service,
1415
ICaptchaService,

warehouse/captcha/hcaptcha.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@
88
from requests.exceptions import Timeout
99
from zope.interface import implementer
1010

11+
from . import CaptchaError
1112
from .interfaces import ChallengeResponse, ICaptchaService
1213

1314
VERIFY_URL = "https://api.hcaptcha.com/siteverify"
1415

1516

16-
class HCaptchaError(ValueError):
17+
class HCaptchaError(CaptchaError):
1718
pass
1819

1920

warehouse/captcha/recaptcha.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66

77
from zope.interface import implementer
88

9+
from . import CaptchaError
910
from .interfaces import ChallengeResponse, ICaptchaService
1011

1112
VERIFY_URL = "https://www.google.com/recaptcha/api/siteverify"
1213

1314

14-
class RecaptchaError(ValueError):
15+
class RecaptchaError(CaptchaError):
1516
pass
1617

1718

warehouse/templates/includes/input-captcha.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
{% if captcha_svc.enabled %}
55
<div class="{{ captcha_svc.class_name }}"
66
data-sitekey="{{ captcha_svc.site_key }}"></div>
7-
{% if form.g_recaptcha_response.errors %}
7+
{% if form.captcha_response.errors %}
88
<ul class="form-errors">
9-
{% for error in form.g_recaptcha_response.errors %}<li>{{ error }}</li>{% endfor %}
9+
{% for error in form.captcha_response.errors %}<li>{{ error }}</li>{% endfor %}
1010
</ul>
1111
{% endif %}
1212
{% endif %}

0 commit comments

Comments
 (0)