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