# HG changeset patch # User Brian Neal # Date 1523731985 18000 # Node ID b957e4829a03f9a4e722c925d2c17a1b7cbeca5a # Parent b213e4b333bb1feece054832c4066dfafa3fca1a Add reCAPTCHA to contact form diff -r b213e4b333bb -r b957e4829a03 contact/forms.py --- a/contact/forms.py Mon Jan 01 10:39:48 2018 -0600 +++ b/contact/forms.py Sat Apr 14 13:53:05 2018 -0500 @@ -1,12 +1,20 @@ """forms for the contact application""" +import logging + from django import forms from django.conf import settings from django.template.loader import render_to_string from django.contrib.sites.models import Site +import requests + +from core.functions import get_ip from core.functions import send_mail +logger = logging.getLogger(__name__) + + class ContactForm(forms.Form): """Form used to contact the website admins""" name=forms.CharField(label="Your Name") @@ -20,12 +28,34 @@ recipient_list = [mail_tuple[1] for mail_tuple in settings.MANAGERS] + def __init__(self, *args, **kwargs): + self.request = kwargs.pop('request', None) + super(ContactForm, self).__init__(*args, **kwargs) + def clean_honeypot(self): value = self.cleaned_data['honeypot'] if value: raise forms.ValidationError(self.fields['honeypot'].label) return value + def clean(self): + super(ContactForm, self).clean() + captcha_response = self.request.POST.get('g-recaptcha-response') + if not captcha_response: + raise forms.ValidationError('Missing reCAPTCHA response') + r = requests.post(settings.RECAPTCHA_URL, data={ + 'secret': settings.RECAPTCHA_SECRET_KEY, + 'response': captcha_response, + 'remoteip': get_ip(self.request), + }) + result = r.json() + logger.info("Contact Form captcha response: %s %s", + result.get('success', ''), + result.get('error-codes', '')) + success = result.get('success', False) + if not success: + raise forms.ValidationError('reCAPTCHA failure') + def save(self): # Send the feedback message email diff -r b213e4b333bb -r b957e4829a03 contact/tests/test_views.py --- a/contact/tests/test_views.py Mon Jan 01 10:39:48 2018 -0600 +++ b/contact/tests/test_views.py Sat Apr 14 13:53:05 2018 -0500 @@ -2,7 +2,9 @@ Unit tests for the contact application views. """ +from mock import patch, Mock from django.test import TestCase +from django.conf import settings from django.core.urlresolvers import reverse from django.core import mail @@ -10,9 +12,16 @@ class BaseTestCase(TestCase): """Simple tests to ensure basic functionality.""" - def test_usage(self): - url = reverse('contact-form') - response = self.client.get(url) + def setUp(self): + self.url = reverse('contact-form') + + @patch('contact.forms.requests.post') + def test_usage(self, post_mock): + post_response = Mock() + post_response.json.return_value = {'success': True} + post_mock.return_value = post_response + + response = self.client.get(self.url) self.assertEqual(response.status_code, 200) post_data = { @@ -20,8 +29,9 @@ 'email': 'jdoe@example.com', 'subject': 'Test message', 'message': 'Testing contact form.', + 'g-recaptcha-response': 'crazy-google-string', } - response = self.client.post(url, data=post_data, follow=True) + response = self.client.post(self.url, data=post_data, follow=True) self.assertRedirects(response, reverse('contact-thanks')) self.assertEqual(len(mail.outbox), 1) @@ -36,8 +46,13 @@ self.assertTrue(post_data['email'] in msg) self.assertTrue(post_data['message'] in msg) + post_mock.assert_called_once_with(settings.RECAPTCHA_URL, data={ + 'secret': settings.RECAPTCHA_SECRET_KEY, + 'response': 'crazy-google-string', + 'remoteip': '127.0.0.1', + }) + def test_honeypot(self): - url = reverse('contact-form') post_data = { 'name': 'John Doe', 'email': 'jdoe@example.com', @@ -45,6 +60,34 @@ 'message': 'Testing contact form.', 'honeypot': 'some spam', } - response = self.client.post(url, data=post_data) + response = self.client.post(self.url, data=post_data) self.assertEqual(response.status_code, 200) self.assertEqual(len(mail.outbox), 0) + + def test_no_captcha_response(self): + post_data = { + 'name': 'John Doe', + 'email': 'jdoe@example.com', + 'subject': 'Test message', + 'message': 'Testing contact form.', + } + response = self.client.post(self.url, data=post_data, follow=True) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(mail.outbox), 0) + + @patch('contact.forms.requests.post') + def test_captcha_failure(self, post_mock): + post_response = Mock() + post_response.json.return_value = {'success': False} + post_mock.return_value = post_response + + post_data = { + 'name': 'John Doe', + 'email': 'jdoe@example.com', + 'subject': 'Test message', + 'message': 'Testing contact form.', + 'g-recaptcha-response': 'crazy-google-string', + } + response = self.client.post(self.url, data=post_data, follow=True) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(mail.outbox), 0) diff -r b213e4b333bb -r b957e4829a03 contact/views.py --- a/contact/views.py Mon Jan 01 10:39:48 2018 -0600 +++ b/contact/views.py Sat Apr 14 13:53:05 2018 -0500 @@ -1,5 +1,6 @@ """Views for the contact application.""" +from django.conf import settings from django.shortcuts import redirect, render from django.views.generic import TemplateView @@ -9,7 +10,7 @@ def contact_form(request): if request.method == 'POST': - form = ContactForm(request.POST) + form = ContactForm(request.POST, request=request) if form.is_valid(): form.save() return redirect('contact-thanks') @@ -26,10 +27,11 @@ if subject: initial_data['subject'] = subject - form = ContactForm(initial=initial_data) + form = ContactForm(initial=initial_data, request=request) return render(request, 'contact/contact_form.html', { 'form': form, + 'RECAPTCHA_SITE_KEY': settings.RECAPTCHA_SITE_KEY, 'V3_DESIGN': True, }) diff -r b213e4b333bb -r b957e4829a03 sg101/settings/base.py --- a/sg101/settings/base.py Mon Jan 01 10:39:48 2018 -0600 +++ b/sg101/settings/base.py Sat Apr 14 13:53:05 2018 -0500 @@ -311,6 +311,11 @@ # useful when we are rebuilding the search index. SEARCH_QUEUE_ENABLED = True +# Google reCAPTCHA settings +RECAPTCHA_URL = 'https://www.google.com/recaptcha/api/siteverify' +RECAPTCHA_SITE_KEY = SECRETS['RECAPTCHA_SITE_KEY'] +RECAPTCHA_SECRET_KEY = SECRETS['RECAPTCHA_SECRET_KEY'] + ####################################################################### # Asynchronous settings (queues, queued_search, redis, celery, etc) ####################################################################### diff -r b213e4b333bb -r b957e4829a03 sg101/templates/contact/contact_form.html --- a/sg101/templates/contact/contact_form.html Mon Jan 01 10:39:48 2018 -0600 +++ b/sg101/templates/contact/contact_form.html Sat Apr 14 13:53:05 2018 -0500 @@ -10,9 +10,6 @@

{% csrf_token %} -
- +
- +
+
+ +
+
{% endblock %} +{% block custom_js %} + +{% endblock %}