diff --git a/flask_wtf/_compat.py b/flask_wtf/_compat.py index 7295b737..bb6e9edd 100644 --- a/flask_wtf/_compat.py +++ b/flask_wtf/_compat.py @@ -12,3 +12,10 @@ def to_bytes(text): if isinstance(text, text_type): text = text.encode('utf-8') return text + + +def to_unicode(input_bytes, encoding='utf-8'): + """Decodes input_bytes to text if needed.""" + if not isinstance(input_bytes, string_types): + input_bytes = input_bytes.decode(encoding) + return input_bytes diff --git a/flask_wtf/recaptcha/validators.py b/flask_wtf/recaptcha/validators.py index 1906933a..061c18bf 100644 --- a/flask_wtf/recaptcha/validators.py +++ b/flask_wtf/recaptcha/validators.py @@ -7,27 +7,23 @@ from flask import request, current_app from wtforms import ValidationError from werkzeug import url_encode -from .._compat import to_bytes +from .._compat import to_bytes, to_unicode +import json -RECAPTCHA_VERIFY_SERVER = 'https://www.google.com/recaptcha/api/verify' +RECAPTCHA_VERIFY_SERVER = 'https://www.google.com/recaptcha/api/siteverify' __all__ = ["Recaptcha"] class Recaptcha(object): + """Validates a ReCaptcha.""" _error_codes = { - 'invalid-site-public-key': 'The public key for reCAPTCHA is invalid', - 'invalid-site-private-key': 'The private key for reCAPTCHA is invalid', - 'invalid-referrer': ( - 'The public key for reCAPTCHA is not valid for ' - 'this domainin' - ), - 'verify-params-incorrect': ( - 'The parameters passed to reCAPTCHA ' - 'verification are incorrect' - ) + 'missing-input-secret': 'The secret parameter is missing.', + 'invalid-input-secret': 'The secret parameter is invalid or malformed.', + 'missing-input-response': 'The response parameter is missing.', + 'invalid-input-response': 'The response parameter is invalid or malformed.', } def __init__(self, message=u'Invalid word. Please try again.'): @@ -38,21 +34,19 @@ def __call__(self, form, field): return True if request.json: - challenge = request.json.get('recaptcha_challenge_field', '') - response = request.json.get('recaptcha_response_field', '') + response = request.json.get('g-recaptcha-response', '') else: - challenge = request.form.get('recaptcha_challenge_field', '') - response = request.form.get('recaptcha_response_field', '') + response = request.form.get('g-recaptcha-response', '') remote_ip = request.remote_addr - if not challenge or not response: + if not response: raise ValidationError(field.gettext(self.message)) - if not self._validate_recaptcha(challenge, response, remote_ip): + if not self._validate_recaptcha(response, remote_ip): field.recaptcha_error = 'incorrect-captcha-sol' raise ValidationError(field.gettext(self.message)) - def _validate_recaptcha(self, challenge, response, remote_addr): + def _validate_recaptcha(self, response, remote_addr): """Performs the actual validation.""" try: private_key = current_app.config['RECAPTCHA_PRIVATE_KEY'] @@ -60,24 +54,22 @@ def _validate_recaptcha(self, challenge, response, remote_addr): raise RuntimeError("No RECAPTCHA_PRIVATE_KEY config set") data = url_encode({ - 'privatekey': private_key, + 'secret': private_key, 'remoteip': remote_addr, - 'challenge': challenge, 'response': response }) - response = http.urlopen(RECAPTCHA_VERIFY_SERVER, to_bytes(data)) + http_response = http.urlopen(RECAPTCHA_VERIFY_SERVER, to_bytes(data)) - if response.code != 200: + if http_response.code != 200: return False - rv = [l.strip() for l in response.readlines()] + json_resp = json.loads(to_unicode(http_response.read())) - if rv and rv[0] == to_bytes('true'): + if json_resp["success"]: return True - if len(rv) > 1: - error = rv[1] + for error in json_resp["error-codes"]: if error in self._error_codes: raise RuntimeError(self._error_codes[error]) diff --git a/flask_wtf/recaptcha/widgets.py b/flask_wtf/recaptcha/widgets.py index 9662a384..b7935bc9 100644 --- a/flask_wtf/recaptcha/widgets.py +++ b/flask_wtf/recaptcha/widgets.py @@ -1,31 +1,31 @@ # -*- coding: utf-8 -*- from flask import current_app, Markup -from werkzeug import url_encode from flask import json -from .._compat import text_type JSONEncoder = json.JSONEncoder -try: - from speaklater import _LazyString - - class _JSONEncoder(JSONEncoder): - def default(self, o): - if isinstance(o, _LazyString): - return str(o) - return JSONEncoder.default(self, o) -except ImportError: - _JSONEncoder = JSONEncoder - - -RECAPTCHA_API_SERVER = '//www.google.com/recaptcha/api/' RECAPTCHA_HTML = u''' - - + +
''' @@ -34,15 +34,11 @@ def default(self, o): class RecaptchaWidget(object): - def recaptcha_html(self, query, options): + def recaptcha_html(self, public_key): html = current_app.config.get('RECAPTCHA_HTML', RECAPTCHA_HTML) - server = current_app.config.get( - 'RECAPTCHA_API_SERVER', RECAPTCHA_API_SERVER - ) + return Markup(html % dict( - script_url='%schallenge?%s' % (server, query), - frame_url='%snoscript?%s' % (server, query), - options=json.dumps(options, cls=_JSONEncoder) + public_key=public_key )) def __call__(self, field, error=None, **kwargs): @@ -52,32 +48,5 @@ def __call__(self, field, error=None, **kwargs): public_key = current_app.config['RECAPTCHA_PUBLIC_KEY'] except KeyError: raise RuntimeError("RECAPTCHA_PUBLIC_KEY config not set") - query_options = dict(k=public_key) - - if field.recaptcha_error is not None: - query_options['error'] = text_type(field.recaptcha_error) - - query = url_encode(query_options) - - _ = field.gettext - - options = { - 'theme': 'clean', - 'custom_translations': { - 'audio_challenge': _('Get an audio challenge'), - 'cant_hear_this': _('Download sound as MP3'), - 'help_btn': _('Help'), - 'image_alt_text': _('reCAPTCHA challenge image'), - 'incorrect_try_again': _('Incorrect. Try again.'), - 'instructions_audio': _('Type what you hear'), - 'instructions_visual': _('Type the text'), - 'play_again': _('Play sound again'), - 'privacy_and_terms': _('Privacy & Terms'), - 'refresh_btn': _('Get a new challenge'), - 'visual_challenge': _('Get a visual challenge'), - } - } - - options.update(current_app.config.get('RECAPTCHA_OPTIONS', {})) - return self.recaptcha_html(query, options) + return self.recaptcha_html(public_key) diff --git a/tests/test_recaptcha.py b/tests/test_recaptcha.py index ae77f1bd..ef4fc71d 100644 --- a/tests/test_recaptcha.py +++ b/tests/test_recaptcha.py @@ -34,14 +34,12 @@ def test_recaptcha(self): response = self.client.get('/') assert b'//www.google.com/recaptcha/api/' in response.data - def test_invalid_recaptcha(self): response = self.client.post('/', data={}) assert b'Invalid word' in response.data def test_send_recaptcha_request(self): response = self.client.post('/', data={ - 'recaptcha_challenge_field': 'test', 'recaptcha_response_field': 'test' }) assert b'Invalid word' in response.data @@ -49,7 +47,6 @@ def test_send_recaptcha_request(self): def test_testing(self): self.app.testing = True response = self.client.post('/', data={ - 'recaptcha_challenge_field': 'test', 'recaptcha_response_field': 'test' }) assert b'Invalid word' not in response.data @@ -57,7 +54,6 @@ def test_testing(self): def test_no_private_key(self): self.app.config.pop('RECAPTCHA_PRIVATE_KEY', None) response = self.client.post('/', data={ - 'recaptcha_challenge_field': 'test', 'recaptcha_response_field': 'test' }) assert response.status_code == 500