Skip to content

Commit 7a0409a

Browse files
authored
refactor: make captcha a real service (#15137)
1 parent 2082a12 commit 7a0409a

File tree

16 files changed

+296
-165
lines changed

16 files changed

+296
-165
lines changed

dev/environment

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,6 @@ OIDC_AUDIENCE=pypi
7070
# Default to the reCAPTCHA testing keys from https://developers.google.com/recaptcha/docs/faq
7171
RECAPTCHA_SITE_KEY=6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI
7272
RECAPTCHA_SECRET_KEY=6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe
73+
74+
# Example of Captcha backend configuration
75+
# CAPTCHA_BACKEND=warehouse.captcha.recaptcha.Service

tests/unit/accounts/test_forms.py

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121

2222
import warehouse.utils.otp as otp
2323

24-
from warehouse import recaptcha
2524
from warehouse.accounts import forms
2625
from warehouse.accounts.interfaces import (
2726
BurnedRecoveryCode,
@@ -30,6 +29,7 @@
3029
TooManyFailedLogins,
3130
)
3231
from warehouse.accounts.models import DisableReason
32+
from warehouse.captcha import recaptcha
3333
from warehouse.events.tags import EventTag
3434
from warehouse.utils.webauthn import AuthenticationRejectedError
3535

@@ -374,7 +374,7 @@ def test_validate_password_notok_ip_banned(self, db_session):
374374

375375
class TestRegistrationForm:
376376
def test_validate(self):
377-
recaptcha_service = pretend.stub(
377+
captcha_service = pretend.stub(
378378
enabled=False,
379379
verify_response=pretend.call_recorder(lambda _: None),
380380
)
@@ -400,12 +400,12 @@ def test_validate(self):
400400
}
401401
),
402402
user_service=user_service,
403-
recaptcha_service=recaptcha_service,
403+
captcha_service=captcha_service,
404404
breach_service=breach_service,
405405
)
406406

407407
assert form.user_service is user_service
408-
assert form.recaptcha_service is recaptcha_service
408+
assert form.captcha_service is captcha_service
409409
assert form.validate(), str(form.errors)
410410

411411
def test_password_confirm_required_error(self):
@@ -414,7 +414,7 @@ def test_password_confirm_required_error(self):
414414
user_service=pretend.stub(
415415
find_userid_by_email=pretend.call_recorder(lambda _: pretend.stub())
416416
),
417-
recaptcha_service=pretend.stub(enabled=True),
417+
captcha_service=pretend.stub(enabled=True),
418418
breach_service=pretend.stub(check_password=lambda pw: False),
419419
)
420420

@@ -430,7 +430,7 @@ def test_passwords_mismatch_error(self, pyramid_config):
430430
{"new_password": "password", "password_confirm": "mismatch"}
431431
),
432432
user_service=user_service,
433-
recaptcha_service=pretend.stub(enabled=True),
433+
captcha_service=pretend.stub(enabled=True),
434434
breach_service=pretend.stub(check_password=lambda pw, tags=None: False),
435435
)
436436

@@ -452,7 +452,7 @@ def test_passwords_match_success(self):
452452
}
453453
),
454454
user_service=user_service,
455-
recaptcha_service=pretend.stub(enabled=True),
455+
captcha_service=pretend.stub(enabled=True),
456456
breach_service=pretend.stub(check_password=lambda pw, tags=None: False),
457457
)
458458

@@ -466,7 +466,7 @@ def test_email_required_error(self):
466466
user_service=pretend.stub(
467467
find_userid_by_email=pretend.call_recorder(lambda _: pretend.stub())
468468
),
469-
recaptcha_service=pretend.stub(enabled=True),
469+
captcha_service=pretend.stub(enabled=True),
470470
breach_service=pretend.stub(check_password=lambda pw, tags=None: False),
471471
)
472472

@@ -480,7 +480,7 @@ def test_invalid_email_error(self, pyramid_config, email):
480480
user_service=pretend.stub(
481481
find_userid_by_email=pretend.call_recorder(lambda _: None)
482482
),
483-
recaptcha_service=pretend.stub(enabled=True),
483+
captcha_service=pretend.stub(enabled=True),
484484
breach_service=pretend.stub(check_password=lambda pw, tags=None: False),
485485
)
486486

@@ -495,7 +495,7 @@ def test_exotic_email_success(self):
495495
user_service=pretend.stub(
496496
find_userid_by_email=pretend.call_recorder(lambda _: None)
497497
),
498-
recaptcha_service=pretend.stub(enabled=True),
498+
captcha_service=pretend.stub(enabled=True),
499499
breach_service=pretend.stub(check_password=lambda pw, tags=None: False),
500500
)
501501

@@ -508,7 +508,7 @@ def test_email_exists_error(self, pyramid_config):
508508
user_service=pretend.stub(
509509
find_userid_by_email=pretend.call_recorder(lambda _: pretend.stub())
510510
),
511-
recaptcha_service=pretend.stub(enabled=True),
511+
captcha_service=pretend.stub(enabled=True),
512512
breach_service=pretend.stub(check_password=lambda pw, tags=None: False),
513513
)
514514

@@ -525,7 +525,7 @@ def test_prohibited_email_error(self, pyramid_config):
525525
user_service=pretend.stub(
526526
find_userid_by_email=pretend.call_recorder(lambda _: None)
527527
),
528-
recaptcha_service=pretend.stub(enabled=True),
528+
captcha_service=pretend.stub(enabled=True),
529529
breach_service=pretend.stub(check_password=lambda pw, tags=None: False),
530530
)
531531

@@ -540,7 +540,7 @@ def test_recaptcha_disabled(self):
540540
form = forms.RegistrationForm(
541541
formdata=MultiDict({"g_recpatcha_response": ""}),
542542
user_service=pretend.stub(),
543-
recaptcha_service=pretend.stub(
543+
captcha_service=pretend.stub(
544544
enabled=False,
545545
verify_response=pretend.call_recorder(lambda _: None),
546546
),
@@ -555,7 +555,7 @@ def test_recaptcha_required_error(self):
555555
form = forms.RegistrationForm(
556556
formdata=MultiDict({"g_recaptcha_response": ""}),
557557
user_service=pretend.stub(),
558-
recaptcha_service=pretend.stub(
558+
captcha_service=pretend.stub(
559559
enabled=True,
560560
verify_response=pretend.call_recorder(lambda _: None),
561561
),
@@ -568,7 +568,7 @@ def test_recaptcha_error(self):
568568
form = forms.RegistrationForm(
569569
formdata=MultiDict({"g_recaptcha_response": "asd"}),
570570
user_service=pretend.stub(),
571-
recaptcha_service=pretend.stub(
571+
captcha_service=pretend.stub(
572572
verify_response=pretend.raiser(recaptcha.RecaptchaError),
573573
enabled=True,
574574
),
@@ -584,7 +584,7 @@ def test_username_exists(self, pyramid_config):
584584
find_userid=pretend.call_recorder(lambda name: 1),
585585
username_is_prohibited=lambda a: False,
586586
),
587-
recaptcha_service=pretend.stub(
587+
captcha_service=pretend.stub(
588588
enabled=False,
589589
verify_response=pretend.call_recorder(lambda _: None),
590590
),
@@ -603,7 +603,7 @@ def test_username_prohibted(self, pyramid_config):
603603
user_service=pretend.stub(
604604
username_is_prohibited=lambda a: True,
605605
),
606-
recaptcha_service=pretend.stub(
606+
captcha_service=pretend.stub(
607607
enabled=False,
608608
verify_response=pretend.call_recorder(lambda _: None),
609609
),
@@ -624,7 +624,7 @@ def test_username_is_valid(self, username, pyramid_config):
624624
find_userid=pretend.call_recorder(lambda _: None),
625625
username_is_prohibited=lambda a: False,
626626
),
627-
recaptcha_service=pretend.stub(
627+
captcha_service=pretend.stub(
628628
enabled=False,
629629
verify_response=pretend.call_recorder(lambda _: None),
630630
),
@@ -649,7 +649,7 @@ def test_password_strength(self):
649649
form = forms.RegistrationForm(
650650
formdata=MultiDict({"new_password": pwd, "password_confirm": pwd}),
651651
user_service=pretend.stub(),
652-
recaptcha_service=pretend.stub(
652+
captcha_service=pretend.stub(
653653
enabled=False,
654654
verify_response=pretend.call_recorder(lambda _: None),
655655
),
@@ -664,7 +664,7 @@ def test_password_breached(self):
664664
user_service=pretend.stub(
665665
find_userid=pretend.call_recorder(lambda _: None)
666666
),
667-
recaptcha_service=pretend.stub(
667+
captcha_service=pretend.stub(
668668
enabled=False,
669669
verify_response=pretend.call_recorder(lambda _: None),
670670
),
@@ -688,7 +688,7 @@ def test_name_too_long(self, pyramid_config):
688688
user_service=pretend.stub(
689689
find_userid=pretend.call_recorder(lambda _: None)
690690
),
691-
recaptcha_service=pretend.stub(
691+
captcha_service=pretend.stub(
692692
enabled=False,
693693
verify_response=pretend.call_recorder(lambda _: None),
694694
),

tests/unit/accounts/test_views.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
two_factor_and_totp_validate,
5454
)
5555
from warehouse.admin.flags import AdminFlag, AdminFlagValue
56+
from warehouse.captcha.interfaces import ICaptchaService
5657
from warehouse.events.tags import EventTag
5758
from warehouse.metrics.interfaces import IMetricsService
5859
from warehouse.oidc.interfaces import TooManyOIDCRegistrations
@@ -1570,7 +1571,7 @@ def _find_service(service=None, name=None, context=None):
15701571
),
15711572
IRateLimiter: pretend.stub(hit=lambda user_id: None),
15721573
"csp": pretend.stub(merge=lambda *a, **kw: {}),
1573-
"recaptcha": pretend.stub(
1574+
ICaptchaService: pretend.stub(
15741575
csp_policy={}, enabled=True, verify_response=lambda a: True
15751576
),
15761577
}[key]

tests/unit/captcha/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License");
2+
# you may not use this file except in compliance with the License.
3+
# You may obtain a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS,
9+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
# See the License for the specific language governing permissions and
11+
# limitations under the License.

tests/unit/captcha/test_init.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License");
2+
# you may not use this file except in compliance with the License.
3+
# You may obtain a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS,
9+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
# See the License for the specific language governing permissions and
11+
# limitations under the License.
12+
13+
import pretend
14+
15+
from warehouse.captcha import includeme, interfaces, recaptcha
16+
17+
18+
def test_includeme_defaults_to_recaptcha():
19+
config = pretend.stub(
20+
registry=pretend.stub(settings={}),
21+
maybe_dotted=lambda i: i,
22+
register_service_factory=pretend.call_recorder(
23+
lambda factory, iface, name: None
24+
),
25+
)
26+
includeme(config)
27+
28+
assert config.register_service_factory.calls == [
29+
pretend.call(
30+
recaptcha.Service.create_service,
31+
interfaces.ICaptchaService,
32+
name="captcha",
33+
),
34+
]
35+
36+
37+
def test_include_with_custom_backend():
38+
cache_class = pretend.stub(create_service=pretend.stub())
39+
config = pretend.stub(
40+
registry=pretend.stub(settings={"captcha.backend": "tests.CustomBackend"}),
41+
maybe_dotted=pretend.call_recorder(lambda n: cache_class),
42+
register_service_factory=pretend.call_recorder(
43+
lambda factory, iface, name: None
44+
),
45+
)
46+
includeme(config)
47+
48+
assert config.maybe_dotted.calls == [pretend.call("tests.CustomBackend")]
49+
assert config.register_service_factory.calls == [
50+
pretend.call(
51+
cache_class.create_service,
52+
interfaces.ICaptchaService,
53+
name="captcha",
54+
)
55+
]

0 commit comments

Comments
 (0)