impuls/lib/python3.11/site-packages/captcha/fields.py

277 lines
9.7 KiB
Python

import warnings
import django
from django.core.exceptions import ImproperlyConfigured
from django.forms import ValidationError
from django.forms.fields import CharField, MultiValueField
from django.forms.widgets import HiddenInput, MultiWidget, TextInput
from django.template.loader import render_to_string
from django.urls import NoReverseMatch, reverse
from django.utils import timezone
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy
from captcha.conf import settings
from captcha.models import CaptchaStore
class CaptchaHiddenInput(HiddenInput):
"""Hidden input for the captcha key."""
# Use *args and **kwargs because signature changed in Django 1.11
def build_attrs(self, *args, **kwargs):
"""Disable autocomplete to prevent problems on page reload."""
attrs = super().build_attrs(*args, **kwargs)
attrs["autocomplete"] = "off"
return attrs
class CaptchaAnswerInput(TextInput):
"""Text input for captcha answer."""
# Use *args and **kwargs because signature changed in Django 1.11
def build_attrs(self, *args, **kwargs):
"""Disable automatic corrections and completions."""
attrs = super().build_attrs(*args, **kwargs)
attrs["autocapitalize"] = "off"
attrs["autocomplete"] = "off"
attrs["autocorrect"] = "off"
attrs["spellcheck"] = "false"
return attrs
class BaseCaptchaTextInput(MultiWidget):
"""
Base class for Captcha widgets
"""
def __init__(self, attrs=None):
widgets = (CaptchaHiddenInput(attrs), CaptchaAnswerInput(attrs))
super(BaseCaptchaTextInput, self).__init__(widgets, attrs)
def decompress(self, value):
if value:
return value.split(",")
return [None, None]
def fetch_captcha_store(self, name, value, attrs=None, generator=None):
"""
Fetches a new CaptchaStore
This has to be called inside render
"""
try:
reverse("captcha-image", args=("dummy",))
except NoReverseMatch:
raise ImproperlyConfigured(
"Make sure you've included captcha.urls as explained in the INSTALLATION section on http://readthedocs.org/docs/django-simple-captcha/en/latest/usage.html#installation"
)
if settings.CAPTCHA_GET_FROM_POOL:
key = CaptchaStore.pick()
else:
key = CaptchaStore.generate_key(generator)
# these can be used by format_output and render
self._value = [key, ""]
self._key = key
self.id_ = self.build_attrs(attrs).get("id", None)
def id_for_label(self, id_):
if id_:
return id_ + "_1"
return id_
def image_url(self):
return reverse("captcha-image", kwargs={"key": self._key})
def audio_url(self):
return (
reverse("captcha-audio", kwargs={"key": self._key})
if settings.CAPTCHA_FLITE_PATH
else None
)
def refresh_url(self):
return reverse("captcha-refresh")
class CaptchaTextInput(BaseCaptchaTextInput):
template_name = "captcha/widgets/captcha.html"
def __init__(
self,
attrs=None,
field_template=None,
id_prefix=None,
generator=None,
output_format=None,
):
self.id_prefix = id_prefix
self.generator = generator
if field_template is not None:
msg = "CaptchaTextInput's field_template argument is deprecated in favor of widget's template_name."
warnings.warn(msg, DeprecationWarning)
self.field_template = field_template or settings.CAPTCHA_FIELD_TEMPLATE
if output_format is not None:
msg = "CaptchaTextInput's output_format argument is deprecated in favor of widget's template_name."
warnings.warn(msg, DeprecationWarning)
self.output_format = output_format or settings.CAPTCHA_OUTPUT_FORMAT
# Fallback to custom rendering in Django < 1.11
if (
not hasattr(self, "_render")
and self.field_template is None
and self.output_format is None
):
self.field_template = "captcha/field.html"
if self.output_format:
for key in ("image", "hidden_field", "text_field"):
if "%%(%s)s" % key not in self.output_format:
raise ImproperlyConfigured(
"All of %s must be present in your CAPTCHA_OUTPUT_FORMAT setting. Could not find %s"
% (
", ".join(
[
"%%(%s)s" % k
for k in ("image", "hidden_field", "text_field")
]
),
"%%(%s)s" % key,
)
)
super(CaptchaTextInput, self).__init__(attrs)
def build_attrs(self, *args, **kwargs):
ret = super(CaptchaTextInput, self).build_attrs(*args, **kwargs)
if self.id_prefix and "id" in ret:
ret["id"] = "%s_%s" % (self.id_prefix, ret["id"])
return ret
def id_for_label(self, id_):
ret = super(CaptchaTextInput, self).id_for_label(id_)
if self.id_prefix and "id" in ret:
ret = "%s_%s" % (self.id_prefix, ret)
return ret
def get_context(self, name, value, attrs):
"""Add captcha specific variables to context."""
context = super(CaptchaTextInput, self).get_context(name, value, attrs)
context["image"] = self.image_url()
context["audio"] = self.audio_url()
return context
def format_output(self, rendered_widgets):
# hidden_field, text_field = rendered_widgets
if self.output_format:
ret = self.output_format % {
"image": self.image_and_audio,
"hidden_field": self.hidden_field,
"text_field": self.text_field,
}
return ret
elif self.field_template:
context = {
"image": mark_safe(self.image_and_audio),
"hidden_field": mark_safe(self.hidden_field),
"text_field": mark_safe(self.text_field),
}
return render_to_string(self.field_template, context)
def _direct_render(self, name, attrs):
"""Render the widget the old way - using field_template or output_format."""
context = {
"image": self.image_url(),
"name": name,
"key": self._key,
"id": "%s_%s" % (self.id_prefix, attrs.get("id"))
if self.id_prefix
else attrs.get("id"),
"audio": self.audio_url(),
}
self.image_and_audio = render_to_string(
settings.CAPTCHA_IMAGE_TEMPLATE, context
)
self.hidden_field = render_to_string(
settings.CAPTCHA_HIDDEN_FIELD_TEMPLATE, context
)
self.text_field = render_to_string(
settings.CAPTCHA_TEXT_FIELD_TEMPLATE, context
)
return self.format_output(None)
def render(self, name, value, attrs=None, renderer=None):
self.fetch_captcha_store(name, value, attrs, self.generator)
if self.field_template or self.output_format:
return self._direct_render(name, attrs)
extra_kwargs = {}
if django.VERSION >= (1, 11):
# https://docs.djangoproject.com/en/1.11/ref/forms/widgets/#django.forms.Widget.render
extra_kwargs["renderer"] = renderer
return super(CaptchaTextInput, self).render(
name, self._value, attrs=attrs, **extra_kwargs
)
class CaptchaField(MultiValueField):
def __init__(self, *args, **kwargs):
fields = (CharField(show_hidden_initial=True), CharField())
if "error_messages" not in kwargs or "invalid" not in kwargs.get(
"error_messages"
):
if "error_messages" not in kwargs:
kwargs["error_messages"] = {}
kwargs["error_messages"].update(
{"invalid": gettext_lazy("Invalid CAPTCHA")}
)
kwargs["widget"] = kwargs.pop(
"widget",
CaptchaTextInput(
output_format=kwargs.pop("output_format", None),
id_prefix=kwargs.pop("id_prefix", None),
generator=kwargs.pop("generator", None),
),
)
super(CaptchaField, self).__init__(fields, *args, **kwargs)
def compress(self, data_list):
if data_list:
return ",".join(data_list)
return None
def clean(self, value):
super(CaptchaField, self).clean(value)
response, value[1] = (value[1] or "").strip().lower(), ""
if not settings.CAPTCHA_GET_FROM_POOL:
CaptchaStore.remove_expired()
if settings.CAPTCHA_TEST_MODE and response.lower() == "passed":
# automatically pass the test
try:
# try to delete the captcha based on its hash
CaptchaStore.objects.get(hashkey=value[0]).delete()
except CaptchaStore.DoesNotExist:
# ignore errors
pass
elif not self.required and not response:
pass
else:
try:
CaptchaStore.objects.get(
response=response, hashkey=value[0], expiration__gt=timezone.now()
).delete()
except CaptchaStore.DoesNotExist:
raise ValidationError(
getattr(self, "error_messages", {}).get(
"invalid", gettext_lazy("Invalid CAPTCHA")
)
)
return value