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.