Implement basic re-captcha handling
authorMagnus Hagander <magnus@hagander.net>
Thu, 17 Dec 2015 12:37:49 +0000 (13:37 +0100)
committerMagnus Hagander <magnus@hagander.net>
Thu, 17 Dec 2015 12:37:49 +0000 (13:37 +0100)
I very much hate captchas, but we need something, and using something like
re-captcha for it makes it easy to implement.

pgweb/account/forms.py
pgweb/account/recaptcha.py [new file with mode: 0644]
pgweb/account/views.py
pgweb/settings.py
templates/base/form.html

index 4e9269bb704f207698c5f069e393a60537eb64ba..9ecb18f1d212ac88a119cdd73d0382b7bacebad9 100644 (file)
@@ -6,12 +6,15 @@ from django.contrib.auth.models import User
 from pgweb.core.models import UserProfile
 from pgweb.contributors.models import Contributor
 
+from recaptcha import ReCaptchaField
+
 class SignupForm(forms.Form):
        username = forms.CharField(max_length=30)
        first_name = forms.CharField(max_length=30)
        last_name = forms.CharField(max_length=30)
        email = forms.EmailField()
        email2 = forms.EmailField(label="Repeat email")
+       captcha = ReCaptchaField()
 
        def clean_email2(self):
                # If the primary email checker had an exception, the data will be gone
diff --git a/pgweb/account/recaptcha.py b/pgweb/account/recaptcha.py
new file mode 100644 (file)
index 0000000..60a6c1a
--- /dev/null
@@ -0,0 +1,65 @@
+#
+# Basic field and widget for simple recaptcha support using
+# the v2 APIs.
+#
+from django import forms
+from django.forms import ValidationError
+from django.utils.safestring import mark_safe
+from django.conf import settings
+
+import httplib
+import urllib
+import json
+
+class ReCaptchaWidget(forms.widgets.Widget):
+       def render(self, name, value, attrs=None):
+               if settings.NOCAPTCHA:
+                       return u'Captcha disabled on this system'
+               return mark_safe(u'<div class="g-recaptcha" data-sitekey="{0}"></div>'.format(settings.RECAPTCHA_SITE_KEY))
+
+       def value_from_datadict(self, data, files, name):
+               if settings.NOCAPTCHA:
+                       return None
+               return data['g-recaptcha-response']
+
+
+class ReCaptchaField(forms.CharField):
+       def __init__(self, *args, **kwargs):
+               self.widget = ReCaptchaWidget()
+               self.required = not settings.NOCAPTCHA
+               super(ReCaptchaField, self).__init__(*args, **kwargs)
+
+       def clean(self, value):
+               if settings.NOCAPTCHA:
+                       return True
+
+               super(ReCaptchaField, self).clean(value)
+
+               # Validate the recaptcha
+               c = httplib.HTTPSConnection('www.google.com', strict=True, timeout=5)
+               param = urllib.urlencode({
+                       'secret': settings.RECAPTCHA_SECRET_KEY,
+                       'response': value,
+                       # XXX: include remote ip!
+               })
+               c.request('POST', '/recaptcha/api/siteverify', param, {
+                       'Content-type': 'application/x-www-form-urlencoded',
+               })
+               c.sock.settimeout(10)
+               try:
+                       r = c.getresponse()
+               except:
+                       raise ValidationError('Failed in API call to google recaptcha')
+               if r.status != 200:
+                       raise ValidationError('Invalid response code from google recaptcha')
+
+               try:
+                       j = json.loads(r.read())
+               except:
+                       raise ValidationError('Invalid response structure from google recaptcha')
+
+               if not j['success']:
+                       raise ValidationError('Invalid. Try again.')
+
+               # Recaptcha validated ok!
+               return True
index 2a4b08c82c0f23b52dd53c0d3a431cb0f841f9f5..4a79962215608974e84ba5912404a3aae6fe14c4 100644 (file)
@@ -275,6 +275,7 @@ content is available for reading without an account.
 """,
                        'savebutton': 'Sign up',
                        'operation': 'New',
+                       'recaptcha': True,
        }, NavContext(request, 'account'))
 
 
index 2125c4431ff798b36d9153d430e64430fafc842a..624c920dc6468a8e38b3a42a8b2bad8d6dfd39bb 100644 (file)
@@ -135,6 +135,11 @@ PASSWORD_HASHERS = (
     'django.contrib.auth.hashers.CryptPasswordHasher',
 )
 
+# Configure recaptcha. Most details contain keys and are thus handled
+# in settings_local.py. Override NOCAPTCHA to actually use them.
+NOCAPTCHA=True
+RECAPTCHA_SITE_KEY=""
+RECAPTCHA_SECRET_KEY=""
 
 ###
 # Application specific settings, likely overridden in settings_local.py.
index d8003d602aea96ce567a2ef194439ac81e7cb29b..0e03fe1d6cf2590845db8bc2133094846b9aee24 100644 (file)
@@ -81,7 +81,11 @@ $(document).ready(function() {
 });
 {%endfor%}
 </script>
-{%endif%}
+{%endif%}{# toggle_fields #}
+{%endif%}{# markdownfields #}
+
+{%if recaptcha%}
+<script src="https://www.google.com/recaptcha/api.js?hl=en" async defer></script>
 {%endif%}
 {%endblock%}