blender-id/bid_main/recaptcha.py

86 lines
3.1 KiB
Python

"""Utilities for handling reCAPTCHA."""
from typing import Tuple, Optional
import logging
from django.conf import settings
from django.http.request import validate_host
import requests
logger = logging.getLogger(__name__)
recaptcha_session = requests.Session()
recaptcha_session.mount(
'https://',
requests.adapters.HTTPAdapter(
max_retries=requests.adapters.Retry(total=10, backoff_factor=0.2),
),
)
VERIFY_URL = 'https://www.google.com/recaptcha/api/siteverify'
MIN_SCORE = 0.5
msg_recaptcha_unavailable = (
'There was a communication error checking reCAPTCHA. Please try again later.'
)
def check_recaptcha(request, recaptcha_response) -> Tuple[Optional[bool], str]:
"""Handles recaptcha verification and sets request.recaptcha_is_valid.
Be sure to set settings.GOOGLE_RECAPTCHA_SECRET_KEY to a non-empty string,
otherwise this function does nothing.
:returns: None when no check was performed, or True/False indicating a
successful resp. unsuccessful check.
"""
if not recaptcha_response:
logger.warning('reCAPTCHA response not included in request')
return (
False,
'reCAPTCHA failed to check your request. Please disable script/ad '
'blockers and try again.',
)
data = {'secret': settings.GOOGLE_RECAPTCHA_SECRET_KEY, 'response': recaptcha_response}
full_url = request.build_absolute_uri()
try:
r = recaptcha_session.post(VERIFY_URL, data=data)
except (IOError, OSError):
logger.exception('Error communicating with Google reCAPTCHA service at %s', full_url)
return None, msg_recaptcha_unavailable
if r.status_code != 200:
logger.error("Error code %d verifying reCAPTCHA at %s: %s", r.status_code, full_url, r.text)
return None, msg_recaptcha_unavailable
result = r.json()
if not result.get('success', False):
logger.debug("reCAPTCHA failed to verify at %s: %s", full_url, r.text)
message = 'reCAPTCHA failed to verify that you are human being. Please try again.'
error_codes = result.get('error-codes') or []
if 'timeout-or-duplicate' in error_codes:
message = (
'reCAPTCHA token expired or was already used.'
' Please reload the page and try again.'
)
return False, message
# Check that nobody is trying to fake the response via some other website.
hostname = result.get('hostname')
if not validate_host(hostname, settings.ALLOWED_HOSTS):
logger.error(
"reCAPTCHA verified but for an unexpected hostname %r at %s",
hostname,
full_url,
)
return (
False,
'reCAPTCHA verified, but for an unexpected hostname. Please try again. If '
'this keeps happening, send an email to production@blender.org.',
)
score = result.get('score', 0.0)
if not score or score < MIN_SCORE:
logger.debug('score: %s, expected minimum: %s, %s', score, MIN_SCORE, result)
return False, 'reCAPTCHA failed to verify that you are human being. Please try again.'
return True, ''