Skip to solution
How an In-Body Javascript is ultimately introduced in Google Recaptcha:
1. Your Django Form has ReCaptchaField. e.g. File: apps/users/forms.py
import captcha.fields class UserRegisterForm(happyforms.ModelForm, PasswordMixin): passwords ... recaptcha = captcha.fields.ReCaptchaField() ... irrelevent stuff ...
2. captcha.fields.ReCaptchaField() in zamboni/vendors (not shown on zamboni github), but it's on Mozilla's django-recaptcha
from django.conf import settings from django import forms from django.utils.encoding import smart_unicode from django.utils.translation import ugettext_lazy as _ from recaptcha.client import captcha from captcha.widgets import ReCaptcha class ReCaptchaField(forms.CharField): default_error_messages = { 'captcha_invalid': _(u'Invalid captcha') } def __init__(self, *args, **kwargs): self.widget = ReCaptcha self.required = True super(ReCaptchaField, self).__init__(*args, **kwargs) def clean(self, values): super(ReCaptchaField, self).clean(values[1]) recaptcha_challenge_value = smart_unicode(values[0]) recaptcha_response_value = smart_unicode(values[1]) check_captcha = captcha.submit(recaptcha_challenge_value, recaptcha_response_value, settings.RECAPTCHA_PRIVATE_KEY, {}) if not check_captcha.is_valid: raise forms.util.ValidationError( self.error_messages['captcha_invalid']) return values[0]
3. from captcha.widgets import ReCaptcha
from django import forms from django.utils.safestring import mark_safe from django.conf import settings from recaptcha.client import captcha class ReCaptcha(forms.widgets.Widget): recaptcha_challenge_name = 'recaptcha_challenge_field' recaptcha_response_name = 'recaptcha_response_field' def render(self, name, value, attrs=None): use_ssl = False if 'RECAPTCHA_USE_SSL' in settings.__members__: use_ssl = settings.RECAPTCHA_USE_SSL return mark_safe(u'%s' % captcha.displayhtml(settings.RECAPTCHA_PUBLIC_KEY, use_ssl=use_ssl)) ...
4. from recaptcha.client (which is from Python's recaptcha client) import captcha
API_SSL_SERVER="https://api-secure.recaptcha.net" API_SERVER="http://api.recaptcha.net" def displayhtml (public_key, use_ssl = False, error = None): """Gets the HTML to display for reCAPTCHA public_key -- The public api key use_ssl -- Should the request be sent over ssl? error -- An error message to display (from RecaptchaResponse.error_code)""" error_param = '' if error: error_param = '&error=%s' % error if use_ssl: server = API_SSL_SERVER else: server = API_SERVER return """<script type="text/javascript" src="%(ApiServer)s/challenge?k=%(PublicKey)s%(ErrorParam)s"></script> # this src contains in-body script!! <noscript> <iframe src="%(ApiServer)s/noscript?k=%(PublicKey)s%(ErrorParam)s" height="300" width="500" frameborder="0"></iframe><br /> <textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea> <input type='hidden' name='recaptcha_response_field' value='manual_challenge' /> </noscript> """ % { 'ApiServer' : server, 'PublicKey' : public_key, 'ErrorParam' : error_param, }
5. The src directs to (key varies for host, content is the same) https://www.google.com/recaptcha/api/challenge?k=6LcCCsYSAAAAACm9eF4n2ttYMU4TFbDMXMO-Bw2q
6. Which then directs to https://www.google.com/recaptcha/api/js/recaptcha.js that contains an in-body script:
document.write('<script>Recaptcha.widget = Recaptcha.$("recaptcha_widget_div"); Recaptcha.challenge_callback();<\/script>');
SOLVED!!!: click to see commit 2 break throughs, 1 question:
BT1: change into amo register's custom RecaptchaOptions to avoid in-body script.
BT2: have to allow setInterval like 'CSP_OPTIONS = ("eval-script",)'.
Q1: How come amo register does not have "setInterval blocked by CSP" problem even without CSP_OPTIONS?
BT1: In-body script is skipped with a custom RecaptchaOptions
as seen in Google recaptcha's js, note the javascript comma:
if (RecaptchaOptions.theme == "custom") { if (RecaptchaOptions.custom_theme_widget) Recaptcha.widget = Recaptcha.$(RecaptchaOptions.custom_theme_widget); Recaptcha.challenge_callback() } else document.write('<div id="recaptcha_widget_div" style="display:none"></div>'), document.write('<script>Recaptcha.widget = Recaptcha.$("recaptcha_widget_div"); Recaptcha.challenge_callback();<\/script>');So the entire "else", which contains the in-body javascript, is skipped!
BT2: Make CSP policy allow setInterval
add
CSP_OPTIONS = ("eval-script",)
into settings.pysolves the "call to setInterval blocked by CSP" issue (seen in Firebug).
Q1: Why doesn't amo have this issue?
Solved: because amo has CSP_REPORT_ONLY, meaning that CSP is not actually enforced, but only reported!
---------
Have to get around setInterval(). CSP only blocks setInterval if it's called with a string argument.
So let's call it with a function!
Continued on this post.
Nice job... i have been looking for information about Captcha Code i think i found it.....
ReplyDelete