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 %}