recaptcha.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. # -*- coding: utf-8 -*-
  2. """
  3. flaskbb.utils.recaptcha
  4. ~~~~~~~~~~~~~~~~~~~~~~~
  5. The reCAPTCHA Field. Taken from Flask-WTF and modified
  6. to use our own settings system.
  7. :copyright: (c) 2014 by the FlaskBB Team.
  8. :license: BSD, see LICENSE for more details.
  9. """
  10. from wtforms.fields import Field
  11. try:
  12. import urllib2 as http
  13. except ImportError:
  14. # Python 3
  15. from urllib import request as http
  16. from flask import request, current_app, Markup, json
  17. from werkzeug import url_encode
  18. from wtforms import ValidationError
  19. from flaskbb._compat import to_bytes, to_unicode
  20. from flaskbb.utils.settings import flaskbb_config
  21. JSONEncoder = json.JSONEncoder
  22. RECAPTCHA_SCRIPT = u'https://www.google.com/recaptcha/api.js'
  23. RECAPTCHA_TEMPLATE = u'''
  24. <script src='%s' async defer></script>
  25. <div class="g-recaptcha" %s></div>
  26. '''
  27. RECAPTCHA_VERIFY_SERVER = 'https://www.google.com/recaptcha/api/siteverify'
  28. RECAPTCHA_ERROR_CODES = {
  29. 'missing-input-secret': 'The secret parameter is missing.',
  30. 'invalid-input-secret': 'The secret parameter is invalid or malformed.',
  31. 'missing-input-response': 'The response parameter is missing.',
  32. 'invalid-input-response': 'The response parameter is invalid or malformed.'
  33. }
  34. class RecaptchaValidator(object):
  35. """Validates a ReCaptcha."""
  36. def __init__(self, message=None):
  37. if message is None:
  38. message = RECAPTCHA_ERROR_CODES['missing-input-response']
  39. self.message = message
  40. def __call__(self, form, field):
  41. if current_app.testing or not flaskbb_config["RECAPTCHA_ENABLED"]:
  42. return True
  43. if request.json:
  44. response = request.json.get('g-recaptcha-response', '')
  45. else:
  46. response = request.form.get('g-recaptcha-response', '')
  47. remote_ip = request.remote_addr
  48. if not response:
  49. raise ValidationError(field.gettext(self.message))
  50. if not self._validate_recaptcha(response, remote_ip):
  51. field.recaptcha_error = 'incorrect-captcha-sol'
  52. raise ValidationError(field.gettext(self.message))
  53. def _validate_recaptcha(self, response, remote_addr):
  54. """Performs the actual validation."""
  55. try:
  56. private_key = flaskbb_config['RECAPTCHA_PRIVATE_KEY']
  57. except KeyError:
  58. raise RuntimeError("No RECAPTCHA_PRIVATE_KEY config set")
  59. data = url_encode({
  60. 'secret': private_key,
  61. 'remoteip': remote_addr,
  62. 'response': response
  63. })
  64. http_response = http.urlopen(RECAPTCHA_VERIFY_SERVER, to_bytes(data))
  65. if http_response.code != 200:
  66. return False
  67. json_resp = json.loads(to_unicode(http_response.read()))
  68. if json_resp["success"]:
  69. return True
  70. for error in json_resp.get("error-codes", []):
  71. if error in RECAPTCHA_ERROR_CODES:
  72. raise ValidationError(RECAPTCHA_ERROR_CODES[error])
  73. return False
  74. class RecaptchaWidget(object):
  75. def recaptcha_html(self, public_key):
  76. html = current_app.config.get('RECAPTCHA_HTML')
  77. if html:
  78. return Markup(html)
  79. params = current_app.config.get('RECAPTCHA_PARAMETERS')
  80. script = RECAPTCHA_SCRIPT
  81. if params:
  82. script += u'?' + url_encode(params)
  83. attrs = current_app.config.get('RECAPTCHA_DATA_ATTRS', {})
  84. attrs['sitekey'] = public_key
  85. snippet = u' '.join([u'data-%s="%s"' % (k, attrs[k]) for k in attrs])
  86. return Markup(RECAPTCHA_TEMPLATE % (script, snippet))
  87. def __call__(self, field, error=None, **kwargs):
  88. """Returns the recaptcha input HTML."""
  89. if not flaskbb_config["RECAPTCHA_ENABLED"]:
  90. return
  91. try:
  92. public_key = flaskbb_config['RECAPTCHA_PUBLIC_KEY']
  93. except KeyError:
  94. raise RuntimeError("RECAPTCHA_PUBLIC_KEY config not set")
  95. return self.recaptcha_html(public_key)
  96. class RecaptchaField(Field):
  97. widget = RecaptchaWidget()
  98. # error message if recaptcha validation fails
  99. recaptcha_error = None
  100. def __init__(self, label='', validators=None, **kwargs):
  101. validators = validators or [RecaptchaValidator()]
  102. super(RecaptchaField, self).__init__(label, validators, **kwargs)