import datetime import json import os import re import warnings from io import BytesIO from PIL import Image from testfixtures import LogCapture import django from django.core import management from django.core.exceptions import ImproperlyConfigured from django.test import TestCase, override_settings from django.urls import reverse from django.utils import timezone from django.utils.translation import gettext_lazy from captcha.conf import settings from captcha.fields import CaptchaField, CaptchaTextInput from captcha.models import CaptchaStore @override_settings(ROOT_URLCONF="captcha.tests.urls") class CaptchaCase(TestCase): def setUp(self): self.stores = {} self.__current_settings_output_format = settings.CAPTCHA_OUTPUT_FORMAT self.__current_settings_dictionary = settings.CAPTCHA_WORDS_DICTIONARY self.__current_settings_punctuation = settings.CAPTCHA_PUNCTUATION tested_helpers = [ "captcha.helpers.math_challenge", "captcha.helpers.random_char_challenge", "captcha.helpers.unicode_challenge", ] if os.path.exists("/usr/share/dict/words"): settings.CAPTCHA_WORDS_DICTIONARY = "/usr/share/dict/words" settings.CAPTCHA_PUNCTUATION = ";-,." tested_helpers.append("captcha.helpers.word_challenge") tested_helpers.append( "captcha.helpers.huge_words_and_punctuation_challenge" ) for helper in tested_helpers: challenge, response = settings._callable_from_string(helper)() ( self.stores[helper.rsplit(".", 1)[-1].replace("_challenge", "_store")], _, ) = CaptchaStore.objects.get_or_create( challenge=challenge, response=response ) challenge, response = settings.get_challenge()() self.stores["default_store"], _ = CaptchaStore.objects.get_or_create( challenge=challenge, response=response ) self.default_store = self.stores["default_store"] def tearDown(self): settings.CAPTCHA_OUTPUT_FORMAT = self.__current_settings_output_format settings.CAPTCHA_WORDS_DICTIONARY = self.__current_settings_dictionary settings.CAPTCHA_PUNCTUATION = self.__current_settings_punctuation def _assertFormError(self, response, form_name, *args, **kwargs): if django.VERSION >= (4, 1): self.assertFormError(response.context.get(form_name), *args, **kwargs) else: self.assertFormError(response, form_name, *args, **kwargs) def __extract_hash_and_response(self, r): hash_ = re.findall(r'value="([0-9a-f]+)"', str(r.content))[0] response = CaptchaStore.objects.get(hashkey=hash_).response return hash_, response def test_image(self): for key in [store.hashkey for store in self.stores.values()]: response = self.client.get(reverse("captcha-image", kwargs=dict(key=key))) self.assertEqual(response.status_code, 200) self.assertTrue(response.has_header("content-type")) self.assertEqual(response["content-type"], "image/png") def test_audio(self): if not settings.CAPTCHA_FLITE_PATH: return for key in ( self.stores.get("math_store").hashkey, self.stores.get("math_store").hashkey, self.default_store.hashkey, ): response = self.client.get(reverse("captcha-audio", kwargs=dict(key=key))) self.assertEqual(response.status_code, 200) self.assertTrue(response.ranged_file.size > 1024) self.assertTrue(response.has_header("content-type")) self.assertEqual(response["content-type"], "audio/wav") def test_form_submit(self): r = self.client.get(reverse("captcha-test")) self.assertEqual(r.status_code, 200) hash_, response = self.__extract_hash_and_response(r) r = self.client.post( reverse("captcha-test"), dict( captcha_0=hash_, captcha_1=response, subject="xxx", sender="asasd@asdasd.com", ), ) self.assertEqual(r.status_code, 200) self.assertTrue(str(r.content).find("Form validated") > 0) r = self.client.post( reverse("captcha-test"), dict( captcha_0=hash_, captcha_1=response, subject="xxx", sender="asasd@asdasd.com", ), ) self.assertEqual(r.status_code, 200) self.assertFalse(str(r.content).find("Form validated") > 0) def test_modelform(self): r = self.client.get(reverse("captcha-test-model-form")) self.assertEqual(r.status_code, 200) hash_, response = self.__extract_hash_and_response(r) r = self.client.post( reverse("captcha-test-model-form"), dict( captcha_0=hash_, captcha_1=response, subject="xxx", sender="asasd@asdasd.com", ), ) self.assertEqual(r.status_code, 200) self.assertTrue(str(r.content).find("Form validated") > 0) r = self.client.post( reverse("captcha-test-model-form"), dict( captcha_0=hash_, captcha_1=response, subject="xxx", sender="asasd@asdasd.com", ), ) self.assertEqual(r.status_code, 200) self.assertFalse(str(r.content).find("Form validated") > 0) def test_wrong_submit(self): for urlname in ("captcha-test", "captcha-test-model-form"): r = self.client.get(reverse(urlname)) self.assertEqual(r.status_code, 200) r = self.client.post( reverse(urlname), dict( captcha_0="abc", captcha_1="wrong response", subject="xxx", sender="asasd@asdasd.com", ), ) self._assertFormError(r, "form", "captcha", gettext_lazy("Invalid CAPTCHA")) def test_deleted_expired(self): self.default_store.expiration = timezone.now() - datetime.timedelta(minutes=5) self.default_store.save() hash_ = self.default_store.hashkey r = self.client.post( reverse("captcha-test"), dict( captcha_0=hash_, captcha_1=self.default_store.response, subject="xxx", sender="asasd@asdasd.com", ), ) self.assertEqual(r.status_code, 200) self.assertFalse("Form validated" in str(r.content)) # expired -> deleted try: CaptchaStore.objects.get(hashkey=hash_) self.fail() except Exception: pass def test_custom_error_message(self): r = self.client.get(reverse("captcha-test-custom-error-message")) self.assertEqual(r.status_code, 200) # Wrong answer r = self.client.post( reverse("captcha-test-custom-error-message"), dict(captcha_0="abc", captcha_1="wrong response"), ) self._assertFormError(r, "form", "captcha", "TEST CUSTOM ERROR MESSAGE") # empty answer r = self.client.post( reverse("captcha-test-custom-error-message"), dict(captcha_0="abc", captcha_1=""), ) self._assertFormError( r, "form", "captcha", gettext_lazy("This field is required.") ) def test_repeated_challenge(self): CaptchaStore.objects.create(challenge="xxx", response="xxx") try: CaptchaStore.objects.create(challenge="xxx", response="xxx") except Exception: self.fail() def test_repeated_challenge_form_submit(self): __current_challange_function = settings.CAPTCHA_CHALLENGE_FUNCT for urlname in ("captcha-test", "captcha-test-model-form"): settings.CAPTCHA_CHALLENGE_FUNCT = "captcha.tests.trivial_challenge" r1 = self.client.get(reverse(urlname)) r2 = self.client.get(reverse(urlname)) self.assertEqual(r1.status_code, 200) self.assertEqual(r2.status_code, 200) if re.findall(r'value="([0-9a-f]+)"', str(r1.content)): hash_1 = re.findall(r'value="([0-9a-f]+)"', str(r1.content))[0] else: self.fail() if re.findall(r'value="([0-9a-f]+)"', str(r2.content)): hash_2 = re.findall(r'value="([0-9a-f]+)"', str(r2.content))[0] else: self.fail() try: store_1 = CaptchaStore.objects.get(hashkey=hash_1) store_2 = CaptchaStore.objects.get(hashkey=hash_2) except Exception: self.fail() self.assertTrue(store_1.pk != store_2.pk) self.assertTrue(store_1.response == store_2.response) self.assertTrue(hash_1 != hash_2) r1 = self.client.post( reverse(urlname), dict( captcha_0=hash_1, captcha_1=store_1.response, subject="xxx", sender="asasd@asdasd.com", ), ) self.assertEqual(r1.status_code, 200) self.assertTrue(str(r1.content).find("Form validated") > 0) try: store_2 = CaptchaStore.objects.get(hashkey=hash_2) except Exception: self.fail() r2 = self.client.post( reverse(urlname), dict( captcha_0=hash_2, captcha_1=store_2.response, subject="xxx", sender="asasd@asdasd.com", ), ) self.assertEqual(r2.status_code, 200) self.assertTrue(str(r2.content).find("Form validated") > 0) settings.CAPTCHA_CHALLENGE_FUNCT = __current_challange_function def test_output_format(self): for urlname in ("captcha-test", "captcha-test-model-form"): settings.CAPTCHA_OUTPUT_FORMAT = ( "%(image)s

Hello, captcha world

%(hidden_field)s%(text_field)s" ) r = self.client.get(reverse(urlname)) self.assertEqual(r.status_code, 200) self.assertTrue("

Hello, captcha world

" in str(r.content)) def test_invalid_output_format(self): for urlname in ("captcha-test", "captcha-test-model-form"): settings.CAPTCHA_OUTPUT_FORMAT = "%(image)s" try: with warnings.catch_warnings(record=True) as w: self.client.get(reverse(urlname)) assert len(w) == 1 self.assertTrue("CAPTCHA_OUTPUT_FORMAT" in str(w[-1].message)) self.fail() except ImproperlyConfigured as e: self.assertTrue("CAPTCHA_OUTPUT_FORMAT" in str(e)) def test_per_form_format(self): settings.CAPTCHA_OUTPUT_FORMAT = ( "%(image)s testCustomFormatString %(hidden_field)s %(text_field)s" ) r = self.client.get(reverse("captcha-test")) self.assertTrue("testCustomFormatString" in str(r.content)) r = self.client.get(reverse("test_per_form_format")) self.assertTrue("testPerFieldCustomFormatString" in str(r.content)) def test_custom_generator(self): r = self.client.get(reverse("test_custom_generator")) hash_, response = self.__extract_hash_and_response(r) self.assertEqual(response, "111111") def test_issue31_proper_abel(self): settings.CAPTCHA_OUTPUT_FORMAT = "%(image)s %(hidden_field)s %(text_field)s" r = self.client.get(reverse("captcha-test")) self.assertTrue('