Skip to content

Commit 3c1f45e

Browse files
authored
Merge pull request #2 from mtb-beta/add-debug-log
Adjust recaptcha score threshold. Thanks @mtb-beta for your work!
2 parents 5d865b2 + 27b4544 commit 3c1f45e

File tree

5 files changed

+127
-10
lines changed

5 files changed

+127
-10
lines changed

README.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,13 @@ INSTALLED_APPS = (
2525
)
2626
```
2727

28-
And add your reCaptcha private and public key to your django settings.py and the default action name:
28+
And add your reCaptcha private and public key to your django settings.py and the default action name, recaptcha score threshold:
2929

3030
```python
3131
RECAPTCHA_PRIVATE_KEY = 'your private key'
3232
RECAPTCHA_PUBLIC_KEY = 'your public key'
3333
RECAPTCHA_DEFAULT_ACTION = 'generic'
34+
RECAPTCHA_SCORE_THRESHOLD = 0.5
3435

3536
```
3637

@@ -166,6 +167,22 @@ You can use the plain javascript, just remember to set the correct value for the
166167
</html>
167168
```
168169

170+
171+
## Settings
172+
173+
If you want to use recaptcha's score you need to adjust the bot score threshold.
174+
175+
django-recaptcha3 can adjust the bot score threshold as follows. The default value for the threshold is 0.
176+
177+
```python
178+
from snowpenguin.django.recaptcha3.fields import ReCaptchaField
179+
180+
class ExampleForm(forms.Form):
181+
[...]
182+
captcha = ReCaptchaField(score_threshold=0.5)
183+
[...]
184+
```
185+
169186
## Testing
170187
### Test unit support
171188
You can't simulate api calls in your test, but you can disable the recaptcha field and let your test works.

snowpenguin/django/recaptcha3/fields.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
class ReCaptchaField(forms.CharField):
1717
def __init__(self, attrs=None, *args, **kwargs):
1818
self._private_key = kwargs.pop('private_key', settings.RECAPTCHA_PRIVATE_KEY)
19+
self._score_threshold = kwargs.pop('score_threshold', settings.RECAPTCHA_SCORE_THRESHOLD)
1920

2021
if 'widget' not in kwargs:
2122
kwargs['widget'] = ReCaptchaHiddenInput()
@@ -49,7 +50,12 @@ def clean(self, values):
4950

5051
json_response = r.json()
5152

53+
logger.debug("Recieved response from reCaptcha server: %s", json_response)
5254
if bool(json_response['success']):
55+
if self._score_threshold is not None and self._score_threshold > json_response['score']:
56+
raise ValidationError(
57+
_('reCaptcha score is too low. score:%s', json_response['score'])
58+
)
5359
return values[0]
5460
else:
5561
if 'error-codes' in json_response:
Lines changed: 101 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import mock
23

34
from django.forms import Form
45
from django.test import TestCase
@@ -12,18 +13,109 @@ class RecaptchaTestForm(Form):
1213

1314

1415
class TestRecaptchaForm(TestCase):
15-
def setUp(self):
16-
os.environ['RECAPTCHA_DISABLE'] = 'True'
17-
1816
def test_dummy_validation(self):
17+
os.environ['RECAPTCHA_DISABLE'] = 'True'
1918
form = RecaptchaTestForm({})
2019
self.assertTrue(form.is_valid())
21-
22-
def test_dummy_error(self):
2320
del os.environ['RECAPTCHA_DISABLE']
24-
form = RecaptchaTestForm({})
21+
22+
@mock.patch('requests.post')
23+
def test_validate_error_invalid_token(self, requests_post):
24+
25+
recaptcha_response = {'success': False}
26+
requests_post.return_value.json = lambda: recaptcha_response
27+
28+
form = RecaptchaTestForm({"g-recaptcha-response": "dummy token"})
2529
self.assertFalse(form.is_valid())
2630

27-
def tearDown(self):
28-
if 'RECAPTCHA_DISABLE' in os.environ.keys():
29-
del os.environ['RECAPTCHA_DISABLE']
31+
@mock.patch('requests.post')
32+
def test_validate_error_lower_score(self, requests_post):
33+
34+
recaptcha_response = {
35+
'success': True,
36+
'score': 0.5
37+
}
38+
requests_post.return_value.json = lambda: recaptcha_response
39+
40+
class RecaptchaTestForm(Form):
41+
recaptcha = ReCaptchaField(score_threshold=0.7)
42+
form = RecaptchaTestForm({"g-recaptcha-response": "dummy token"})
43+
self.assertFalse(form.is_valid())
44+
45+
@mock.patch('requests.post')
46+
def test_validate_success_highter_score(self, requests_post):
47+
48+
recaptcha_response = {
49+
'success': True,
50+
'score': 0.7
51+
}
52+
requests_post.return_value.json = lambda: recaptcha_response
53+
54+
class RecaptchaTestForm(Form):
55+
recaptcha = ReCaptchaField(score_threshold=0.4)
56+
form = RecaptchaTestForm({"g-recaptcha-response": "dummy token"})
57+
self.assertTrue(form.is_valid())
58+
59+
@mock.patch('requests.post')
60+
def test_settings_score_threshold(self, requests_post):
61+
62+
recaptcha_response = {
63+
'success': True,
64+
'score': 0.6
65+
}
66+
requests_post.return_value.json = lambda: recaptcha_response
67+
68+
class RecaptchaTestForm(Form):
69+
recaptcha = ReCaptchaField()
70+
form = RecaptchaTestForm({"g-recaptcha-response": "dummy token"})
71+
self.assertTrue(form.is_valid())
72+
73+
@mock.patch('requests.post')
74+
def test_settings_score_threshold_override_fields(self, requests_post):
75+
76+
recaptcha_response = {
77+
'success': True,
78+
'score': 0.6
79+
}
80+
requests_post.return_value.json = lambda: recaptcha_response
81+
82+
with self.settings(RECAPTCHA_SCORE_THRESHOLD=0.7):
83+
class RecaptchaTestForm(Form):
84+
recaptcha = ReCaptchaField()
85+
86+
form = RecaptchaTestForm({"g-recaptcha-response": "dummy token"})
87+
self.assertFalse(form.is_valid())
88+
89+
@mock.patch('requests.post')
90+
def test_settings_score_threshold_override_each_fields(self, requests_post):
91+
92+
recaptcha_response = {
93+
'success': True,
94+
'score': 0.4
95+
}
96+
requests_post.return_value.json = lambda: recaptcha_response
97+
98+
with self.settings(RECAPTCHA_SCORE_THRESHOLD=0.7):
99+
class RecaptchaTestForm(Form):
100+
recaptcha = ReCaptchaField()
101+
102+
class RecaptchaOverrideTestForm(Form):
103+
recaptcha = ReCaptchaField(score_threshold=0.3)
104+
105+
form1 = RecaptchaTestForm({"g-recaptcha-response": "dummy token"})
106+
self.assertFalse(form1.is_valid())
107+
108+
form2 = RecaptchaOverrideTestForm({"g-recaptcha-response": "dummy token"})
109+
self.assertTrue(form2.is_valid())
110+
111+
@mock.patch('requests.post')
112+
def test_validate_success(self, requests_post):
113+
114+
recaptcha_response = {
115+
'success': True,
116+
'score': 0.5
117+
}
118+
requests_post.return_value.json = lambda: recaptcha_response
119+
120+
form = RecaptchaTestForm({"g-recaptcha-response": "dummy token"})
121+
self.assertTrue(form.is_valid())

test_settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@
1212
RECAPTCHA_PRIVATE_KEY = 'your private key'
1313
RECAPTCHA_PUBLIC_KEY = 'your public key'
1414
RECAPTCHA_DEFAULT_ACTION = 'generic'
15+
RECAPTCHA_SCORE_THRESHOLD = 0.5

tox.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ commands=
1515
pip install git+git://github.com/kbytesys/django-setuptest.git@feature/pep8_config
1616
python setup.py test
1717
deps =
18+
mock
1819
1.8: Django>=1.8,<1.9
1920
1.9: Django>=1.9,<1.10
2021
1.10: Django>=1.10,<1.11

0 commit comments

Comments
 (0)