diff --git a/rowers/courses.py b/rowers/courses.py index 43a3b8ea..3d41a95d 100644 --- a/rowers/courses.py +++ b/rowers/courses.py @@ -419,7 +419,10 @@ def createcourse( longitude = point['longitude'] g = geocoder.arcgis([latitude, longitude], method='reverse') if g.ok: - country = g.json['country'] + try: + country = g.json['country'] + except KeyError: # pragma: no cover + country = 'unknown' else: # pragma: no cover country = 'unknown' diff --git a/rowers/forms.py b/rowers/forms.py index f5716641..aba62313 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -20,6 +20,7 @@ from django.contrib.admin.widgets import AdminDateWidget from django.forms.widgets import SelectDateWidget, HiddenInput from django_recaptcha.fields import ReCaptchaField from django_recaptcha.widgets import ReCaptchaV3 +from django.core.exceptions import ValidationError from django.utils import timezone, translation from django.forms import ModelForm, Select @@ -1015,6 +1016,25 @@ class RegistrationFormUniqueEmail(RegistrationFormTermsOfService): "This email address is already in use. Please supply a different email address.") return self.cleaned_data['email'] +class HoneypotField(forms.CharField): + """ + A honeypot field that should be left empty by humans + """ + def __init__(self, *args, **kwargs): + kwargs.setdefault('required', False) + kwargs.setdefault('widget', forms.TextInput(attrs={ + 'style': 'display:none !important', + 'tabindex': '-1', + 'autocomplete': 'off', + 'aria-hidden': 'true', + })) + super().__init__(*args, **kwargs) + + def clean(self, value): + if value: + # If the field has a value, it's likely a bot + raise ValidationError("Please leave this field empty.") + return value class RegistrationFormSex(RegistrationFormUniqueEmail): sexcategories = ( @@ -1037,7 +1057,9 @@ class RegistrationFormSex(RegistrationFormUniqueEmail): initial=datetime.date(year=1970, month=4, day=15)) - + + url = HoneypotField(label="URL") + def clean_birthdate(self): dob = self.cleaned_data['birthdate'] age = (timezone.now() - dob).days/365 diff --git a/rowers/tests/test_newusers.py b/rowers/tests/test_newusers.py index 6207d920..5a098885 100644 --- a/rowers/tests/test_newusers.py +++ b/rowers/tests/test_newusers.py @@ -30,6 +30,7 @@ class NewUserRegistrationTest(TestCase): 'username':'janderoeiert', 'password1':'Aapindewei2', 'password2':'Aapindewei2', + 'url': '', 'tos':True, 'weightcategory':'hwt', 'adaptiveclass': 'None', @@ -38,7 +39,7 @@ class NewUserRegistrationTest(TestCase): 'birthdate':datetime.datetime(year=1970,month=4,day=2) } - form = RegistrationFormUniqueEmail(form_data) + form = RegistrationFormSex(form_data) self.assertTrue(form.is_valid()) response = self.c.post('/rowers/register/', form_data, follow=True) @@ -117,6 +118,7 @@ class NewUserRegistrationTest(TestCase): 'password1':'aapindewei2', 'password2':'aapindewei2', 'tos':True, + 'url': '', 'weightcategory':'hwt', 'adaptiveclass': 'None', 'sex':'male', @@ -124,5 +126,34 @@ class NewUserRegistrationTest(TestCase): 'birthdate':datetime.datetime(year=1970,month=4,day=2) } - form = RegistrationFormUniqueEmail(form_data) + form = RegistrationFormSex(form_data) self.assertFalse(form.is_valid()) + + @patch('rowers.dataprep.workout_summary_to_df',side_effect=mock_workout_summaries) + def test_newuser_honeypot(self,mock_workout_summaries): + form_data = { + 'first_name':'Jan', + 'last_name':'Roeiert', + 'email':'jan@loop.nl', + 'username':'janderoeiert', + 'password1':'Aapindewei2', + 'password2':'Aapindewei2', + 'url': 'http://example.com', + 'tos':True, + 'weightcategory':'hwt', + 'adaptiveclass': 'None', + 'sex':'male', + 'next':'/rowers/list-workouts', + 'birthdate':datetime.datetime(year=1970,month=4,day=2) + } + + form = RegistrationFormSex(form_data) + self.assertFalse(form.is_valid()) + + # still post it, should redirect to the registration page + response = self.c.post('/rowers/register/', form_data, follow=True) + self.assertEqual(response.status_code,200) + self.assertRedirects(response, + expected_url='/rowers/register/', + status_code=302,target_status_code=200) + diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 027f5eae..5a3b8623 100644 Binary files a/rowers/tests/testdata/testdata.tcx.gz and b/rowers/tests/testdata/testdata.tcx.gz differ diff --git a/rowers/views/paymentviews.py b/rowers/views/paymentviews.py index 40831540..fc04b4bc 100644 --- a/rowers/views/paymentviews.py +++ b/rowers/views/paymentviews.py @@ -895,6 +895,14 @@ def rower_register_view(request): nextpage = '/rowers/list-workouts/' if request.method == 'POST': + # Check if honeypot was triggered (optional logging) + honeypot_value = request.POST.get('url', '') + if honeypot_value: + # bot user, do not register + messages.error(request, "Registration failed. Please try again.") + url = reverse('rower_register_view') + return HttpResponseRedirect(url) + form = RegistrationFormSex(request.POST) if form.is_valid(): first_name = form.cleaned_data['first_name']