Mercurial > public > sg101
changeset 1172:b957e4829a03
Add reCAPTCHA to contact form
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Sat, 14 Apr 2018 13:53:05 -0500 |
parents | b213e4b333bb |
children | a1a223ab0c8f |
files | contact/forms.py contact/tests/test_views.py contact/views.py sg101/settings/base.py sg101/templates/contact/contact_form.html |
diffstat | 5 files changed, 97 insertions(+), 13 deletions(-) [+] |
line wrap: on
line diff
--- 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', '<Missing>'), + result.get('error-codes', '<Missing>')) + success = result.get('success', False) + if not success: + raise forms.ValidationError('reCAPTCHA failure') + def save(self): # Send the feedback message email
--- 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)
--- 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, })
--- 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) #######################################################################
--- 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 @@ </p> <form action="{% url 'contact-form' %}" method="post">{% csrf_token %} -<!-- -<fieldset class="fieldset"> - <legend>Contact Us</legend> --> <div class="row"> <div class="small-12 medium-8 columns"> <label>{{ form.name.label }} @@ -54,9 +51,16 @@ </div> <div class="row"> <div class="small-12 medium-8 columns"> - <input type="submit" class="button" value="Send Message" /> + <div class="g-recaptcha" data-sitekey="{{ RECAPTCHA_SITE_KEY }}"></div> </div> </div> -<!-- </fieldset> --> + <div class="row"> + <div class="small-12 medium-8 columns"> + <input type="submit" class="button more-top-margin" value="Send Message" /> + </div> + </div> </form> {% endblock %} +{% block custom_js %} +<script src="https://www.google.com/recaptcha/api.js" async defer></script> +{% endblock %}