1from urllib.parse import urlencode
2
3from flask import current_app
4from markupsafe import escape
5from markupsafe import Markup
6from wtforms.widgets import html_params
7
8RECAPTCHA_SCRIPT_DEFAULT = "https://www.google.com/recaptcha/api.js"
9RECAPTCHA_DIV_CLASS_DEFAULT = "g-recaptcha"
10
11__all__ = ["RecaptchaWidget"]
12
13
14class RecaptchaWidget:
15 def recaptcha_html(self, public_key, nonce=None, **kwargs):
16 html = current_app.config.get("RECAPTCHA_HTML")
17 if html:
18 return Markup(html)
19 params = current_app.config.get("RECAPTCHA_PARAMETERS")
20 script = current_app.config.get("RECAPTCHA_SCRIPT")
21 if not script:
22 script = RECAPTCHA_SCRIPT_DEFAULT
23 if params:
24 script += f"?{urlencode(params)}"
25 if callable(nonce):
26 nonce = nonce()
27 nonce_attr = f' nonce="{escape(nonce)}"' if nonce else ""
28
29 kwargs.setdefault(
30 "class",
31 current_app.config.get("RECAPTCHA_DIV_CLASS")
32 or RECAPTCHA_DIV_CLASS_DEFAULT,
33 )
34
35 data_attrs = dict(current_app.config.get("RECAPTCHA_DATA_ATTRS", {}))
36 data_attrs["sitekey"] = public_key
37 for k, v in data_attrs.items():
38 kwargs.setdefault(f"data-{k}", v)
39
40 attributes = html_params(**kwargs)
41 return Markup(
42 f"\n<script src='{script}' async defer{nonce_attr}></script>\n"
43 f"<div {attributes}></div>\n"
44 )
45
46 def __call__(self, field, error=None, **kwargs):
47 """Returns the recaptcha input HTML."""
48
49 if not current_app.config.get("RECAPTCHA_ENABLED", True):
50 return Markup("<!-- recaptcha disabled -->")
51
52 try:
53 public_key = current_app.config["RECAPTCHA_PUBLIC_KEY"]
54 except KeyError:
55 raise RuntimeError("RECAPTCHA_PUBLIC_KEY config not set") from None
56
57 kwargs.setdefault("id", field.id)
58 return self.recaptcha_html(public_key, nonce=field.nonce, **kwargs)