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

277 lines
9.7 KiB
Python
Raw Normal View History

2023-11-06 16:21:10 +01:00
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