1import json
2from urllib import request as http
3from urllib.parse import urlencode
4
5from flask import current_app
6from flask import request
7from wtforms import ValidationError
8
9RECAPTCHA_VERIFY_SERVER_DEFAULT = "https://www.google.com/recaptcha/api/siteverify"
10RECAPTCHA_ERROR_CODES = {
11 "missing-input-secret": "The secret parameter is missing.",
12 "invalid-input-secret": "The secret parameter is invalid or malformed.",
13 "missing-input-response": "The response parameter is missing.",
14 "invalid-input-response": "The response parameter is invalid or malformed.",
15}
16
17
18__all__ = ["Recaptcha"]
19
20
21class Recaptcha:
22 """Validates a ReCaptcha."""
23
24 def __init__(self, message=None):
25 if message is None:
26 message = RECAPTCHA_ERROR_CODES["missing-input-response"]
27 self.message = message
28
29 def __call__(self, form, field):
30 if current_app.testing:
31 return True
32
33 if request.is_json:
34 response = request.json.get("g-recaptcha-response", "")
35 else:
36 response = request.form.get("g-recaptcha-response", "")
37 remote_ip = request.remote_addr
38
39 if not response:
40 raise ValidationError(field.gettext(self.message))
41
42 if not self._validate_recaptcha(response, remote_ip):
43 field.recaptcha_error = "incorrect-captcha-sol"
44 raise ValidationError(field.gettext(self.message))
45
46 def _validate_recaptcha(self, response, remote_addr):
47 """Performs the actual validation."""
48 try:
49 private_key = current_app.config["RECAPTCHA_PRIVATE_KEY"]
50 except KeyError:
51 raise RuntimeError("No RECAPTCHA_PRIVATE_KEY config set") from None
52
53 verify_server = current_app.config.get("RECAPTCHA_VERIFY_SERVER")
54 if not verify_server:
55 verify_server = RECAPTCHA_VERIFY_SERVER_DEFAULT
56
57 data = urlencode(
58 {"secret": private_key, "remoteip": remote_addr, "response": response}
59 )
60
61 http_response = http.urlopen(verify_server, data.encode("utf-8"))
62
63 if http_response.code != 200:
64 return False
65
66 json_resp = json.loads(http_response.read())
67
68 if json_resp["success"]:
69 return True
70
71 for error in json_resp.get("error-codes", []):
72 if error in RECAPTCHA_ERROR_CODES:
73 raise ValidationError(RECAPTCHA_ERROR_CODES[error])
74
75 return False