jo
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
from .fields import RecaptchaField
|
||||
from .validators import Recaptcha
|
||||
from .widgets import RecaptchaWidget
|
||||
|
||||
__all__ = ["RecaptchaField", "RecaptchaWidget", "Recaptcha"]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,17 @@
|
||||
from wtforms.fields import Field
|
||||
|
||||
from . import widgets
|
||||
from .validators import Recaptcha
|
||||
|
||||
__all__ = ["RecaptchaField"]
|
||||
|
||||
|
||||
class RecaptchaField(Field):
|
||||
widget = widgets.RecaptchaWidget()
|
||||
|
||||
# error message if recaptcha validation fails
|
||||
recaptcha_error = None
|
||||
|
||||
def __init__(self, label="", validators=None, **kwargs):
|
||||
validators = validators or [Recaptcha()]
|
||||
super().__init__(label, validators, **kwargs)
|
||||
@@ -0,0 +1,75 @@
|
||||
import json
|
||||
from urllib import request as http
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from flask import current_app
|
||||
from flask import request
|
||||
from wtforms import ValidationError
|
||||
|
||||
RECAPTCHA_VERIFY_SERVER_DEFAULT = "https://www.google.com/recaptcha/api/siteverify"
|
||||
RECAPTCHA_ERROR_CODES = {
|
||||
"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.",
|
||||
}
|
||||
|
||||
|
||||
__all__ = ["Recaptcha"]
|
||||
|
||||
|
||||
class Recaptcha:
|
||||
"""Validates a ReCaptcha."""
|
||||
|
||||
def __init__(self, message=None):
|
||||
if message is None:
|
||||
message = RECAPTCHA_ERROR_CODES["missing-input-response"]
|
||||
self.message = message
|
||||
|
||||
def __call__(self, form, field):
|
||||
if current_app.testing:
|
||||
return True
|
||||
|
||||
if request.is_json:
|
||||
response = request.json.get("g-recaptcha-response", "")
|
||||
else:
|
||||
response = request.form.get("g-recaptcha-response", "")
|
||||
remote_ip = request.remote_addr
|
||||
|
||||
if not response:
|
||||
raise ValidationError(field.gettext(self.message))
|
||||
|
||||
if not self._validate_recaptcha(response, remote_ip):
|
||||
field.recaptcha_error = "incorrect-captcha-sol"
|
||||
raise ValidationError(field.gettext(self.message))
|
||||
|
||||
def _validate_recaptcha(self, response, remote_addr):
|
||||
"""Performs the actual validation."""
|
||||
try:
|
||||
private_key = current_app.config["RECAPTCHA_PRIVATE_KEY"]
|
||||
except KeyError:
|
||||
raise RuntimeError("No RECAPTCHA_PRIVATE_KEY config set") from None
|
||||
|
||||
verify_server = current_app.config.get("RECAPTCHA_VERIFY_SERVER")
|
||||
if not verify_server:
|
||||
verify_server = RECAPTCHA_VERIFY_SERVER_DEFAULT
|
||||
|
||||
data = urlencode(
|
||||
{"secret": private_key, "remoteip": remote_addr, "response": response}
|
||||
)
|
||||
|
||||
http_response = http.urlopen(verify_server, data.encode("utf-8"))
|
||||
|
||||
if http_response.code != 200:
|
||||
return False
|
||||
|
||||
json_resp = json.loads(http_response.read())
|
||||
|
||||
if json_resp["success"]:
|
||||
return True
|
||||
|
||||
for error in json_resp.get("error-codes", []):
|
||||
if error in RECAPTCHA_ERROR_CODES:
|
||||
raise ValidationError(RECAPTCHA_ERROR_CODES[error])
|
||||
|
||||
return False
|
||||
@@ -0,0 +1,43 @@
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from flask import current_app
|
||||
from markupsafe import Markup
|
||||
|
||||
RECAPTCHA_SCRIPT_DEFAULT = "https://www.google.com/recaptcha/api.js"
|
||||
RECAPTCHA_DIV_CLASS_DEFAULT = "g-recaptcha"
|
||||
RECAPTCHA_TEMPLATE = """
|
||||
<script src='%s' async defer></script>
|
||||
<div class="%s" %s></div>
|
||||
"""
|
||||
|
||||
__all__ = ["RecaptchaWidget"]
|
||||
|
||||
|
||||
class RecaptchaWidget:
|
||||
def recaptcha_html(self, public_key):
|
||||
html = current_app.config.get("RECAPTCHA_HTML")
|
||||
if html:
|
||||
return Markup(html)
|
||||
params = current_app.config.get("RECAPTCHA_PARAMETERS")
|
||||
script = current_app.config.get("RECAPTCHA_SCRIPT")
|
||||
if not script:
|
||||
script = RECAPTCHA_SCRIPT_DEFAULT
|
||||
if params:
|
||||
script += "?" + urlencode(params)
|
||||
attrs = current_app.config.get("RECAPTCHA_DATA_ATTRS", {})
|
||||
attrs["sitekey"] = public_key
|
||||
snippet = " ".join(f'data-{k}="{attrs[k]}"' for k in attrs) # noqa: B028, B907
|
||||
div_class = current_app.config.get("RECAPTCHA_DIV_CLASS")
|
||||
if not div_class:
|
||||
div_class = RECAPTCHA_DIV_CLASS_DEFAULT
|
||||
return Markup(RECAPTCHA_TEMPLATE % (script, div_class, snippet))
|
||||
|
||||
def __call__(self, field, error=None, **kwargs):
|
||||
"""Returns the recaptcha input HTML."""
|
||||
|
||||
try:
|
||||
public_key = current_app.config["RECAPTCHA_PUBLIC_KEY"]
|
||||
except KeyError:
|
||||
raise RuntimeError("RECAPTCHA_PUBLIC_KEY config not set") from None
|
||||
|
||||
return self.recaptcha_html(public_key)
|
||||
Reference in New Issue
Block a user