Updating Django OSQA to use reCaptcha 2

Our support site is built on OSQA which in turn is built on Django.  It was still using reCaptcha (version 1) which was taken over by Google, and discontinued.  So it was time to roll up our sleeves and update to reCaptcha 2.

 

The main idea is the reCaptcha is a field based on a widget.  In OSQA for example, when a user submits a question and their reputation is low, the question form adds a spam check field to the form using essentially

 

self.fields[‘recaptcha’] = ReCaptchaField

 

ReCaptchaField is defined as

 

class ReCaptchaField(forms.Field):
    def __init__(self, *args, **kwargs):
        super(ReCaptchaField, self).__init__(widget=ReCaptchaWidget)

 

class ReCaptchaWidget(forms.Widget):

 

NOTE: If you copy this code, notice that WordPress has changed the single and double-quote marks.  You’ll need to change them back to the simple characters used in code.

 

The ReCaptchaWidget renders HTML of the reCaptcha which is essentially

 

<script src=’https://www.google.com/recaptcha/api.js’></script>
<div class=”g-recaptcha” data-sitekey=”%(PublicKey)s”></div>

 

Note that you need a PublicKey and a PrivateKey that you obtain from Google when signing up for reCaptcha.

 

When the form is posted, the reCaptcha will post an additional field, g-recaptcha-response, along with the form post.

 

The ReCaptchaField will take that posted field and send it to a function (‘submit’ in this case) which will do a post to Google’s recaptcha/api/siteverify page to verify the reCaptcha was solved correctly.  If it was solved correctly, json is returned with success : true.  That needs to be captured and interpreted.  You can see how this is done on Google’s reCaptcha page.

 

So, without further ado, here is the code:

 

In osqa/forum_modules/recaptcha/settings.py

 

This is pulling in the public and private keys from Django settings

 

from forum.settings import EXT_KEYS_SET
from forum.settings.base import Setting

 

RECAPTCHA_PUB_KEY = Setting(‘RECAPTCHA_PUB_KEY’, ”, EXT_KEYS_SET, dict(
label = “Recaptch public key”,
help_text = “””
Get this key at <a href=”http://recaptcha.net”>reCaptcha</a> to enable
recaptcha anti spam through.
“””,
required=False))

 

RECAPTCHA_PRIV_KEY = Setting(‘RECAPTCHA_PRIV_KEY’, ”, EXT_KEYS_SET, dict(
label = “Recaptch private key”,
help_text = “””
This is the private key you’ll get in the same place as the recaptcha public key.
“””,
required=False))

 

In osqa/forum_modules/handlers.py we have some simple glue code:

 

from formfield import ReCaptchaField

 

def create_anti_spam_field():
    return (‘recaptcha’, ReCaptchaField())

 

Things start getting interesting in /osqa/forum_modules/formfield.py

 

from django import forms
from lib import captcha
from django.utils.safestring import mark_safe
from django.utils.encoding import force_unicode, smart_unicode
from django.utils.translation import ugettext_lazy as _
import settings
import json

 

class ReCaptchaField(forms.Field):
    def __init__(self, *args, **kwargs):
        super(ReCaptchaField, self).__init__(widget=ReCaptchaWidget)

 

def clean(self, value):
    super(ReCaptchaField, self).clean(value)
    recaptcha_response_value = smart_unicode(value)

    check_captcha = captcha.submit(recaptcha_response_value, settings.RECAPTCHA_PRIV_KEY, {})

 

    if not check_captcha.is_valid:
        raise forms.util.ValidationError(_(‘Invalid captcha ‘ + json.dumps(check_captcha.error_code)))

    return value

 

class ReCaptchaWidget(forms.Widget):
    def render(self, name, value, attrs=None):
        return mark_safe(force_unicode(captcha.displayhtml(settings.RECAPTCHA_PUB_KEY)))

 

    def value_from_datadict(self, data, files, name):
        return data.get(‘g-recaptcha-response’, None)

 

And finally to the actual reCaptcha handling in osqa/forum_modules/recaptcha/lib/captcha.py

 

import urllib2, urllib
import json

 

VERIFY_SERVER=”www.google.com”

 

class RecaptchaResponse(object):
    def __init__(self, is_valid, error_code=None):
        self.is_valid = is_valid
        self.error_code = error_code

 

def displayhtml (public_key):

    return “””
    <script src=’https://www.google.com/recaptcha/api.js’></script>
    <div class=”g-recaptcha” data-sitekey=”%(PublicKey)s”></div>
    “”” % {
              ‘PublicKey’ : public_key,
           }

def submit (recaptcha_response_field,
                      private_key,
                      remoteip):

 

    if not (recaptcha_response_field and
            len (recaptcha_response_field)):
        return RecaptchaResponse (is_valid = False, error_code = ‘incorrect-captcha-sol’)

 

    def encode_if_necessary(s):
        if isinstance(s, unicode):
            return s.encode(‘utf-8’)
        return s

 

    params = urllib.urlencode ({
        ‘secret’: encode_if_necessary(private_key),
        ‘response’ : encode_if_necessary(recaptcha_response_field),
        ‘remoteip’ : encode_if_necessary(remoteip),
        })

 

    request = urllib2.Request (
        url = “https://%s/recaptcha/api/siteverify” % VERIFY_SERVER,
        data = params,
        headers = {
        “Content-type”: “application/x-www-form-urlencoded”,
        “User-agent”: “reCAPTCHA Python”
        }
    )

 

    httpresp = urllib2.urlopen(request)

    json_data_str = httpresp.read()

    httpresp.close()

 

    response_dict = json.loads(json_data_str)

    return_code = response_dict[‘success’];

 

    if return_code == True:
        return RecaptchaResponse(is_valid = True)
    else:
        return RecaptchaResponse(is_valid = False, error_code = response_dict[‘error-codes’])

 

The above files can be downloaded directly from here.

 


Posted

in

,

by

Tags: