Coverage for /home/marco/Code/django-simple-captcha/captcha/models.py: 95.38%
55 statements
« prev ^ index » next coverage.py v7.6.5, created at 2024-11-15 09:22 +0100
« prev ^ index » next coverage.py v7.6.5, created at 2024-11-15 09:22 +0100
1import datetime
2import hashlib
3import logging
4import random
5import time
7from django.db import models
8from django.utils import timezone
9from django.utils.encoding import smart_str
11from captcha.conf import settings as captcha_settings
14# Heavily based on session key generation in Django
15# Use the system (hardware-based) random number generator if it exists.
16if hasattr(random, "SystemRandom"): 16 ↛ 19line 16 didn't jump to line 19 because the condition on line 16 was always true
17 randrange = random.SystemRandom().randrange
18else:
19 randrange = random.randrange
20MAX_RANDOM_KEY = 18446744073709551616 # 2 << 63
22logger = logging.getLogger(__name__)
25class CaptchaStore(models.Model):
26 id = models.AutoField(primary_key=True)
27 challenge = models.CharField(blank=False, max_length=32)
28 response = models.CharField(blank=False, max_length=32)
29 hashkey = models.CharField(blank=False, max_length=40, unique=True)
30 expiration = models.DateTimeField(blank=False)
32 def save(self, *args, **kwargs):
33 self.response = self.response.lower()
34 if not self.expiration:
35 self.expiration = timezone.now() + datetime.timedelta(
36 minutes=int(captcha_settings.CAPTCHA_TIMEOUT)
37 )
38 if not self.hashkey:
39 key_ = (
40 smart_str(randrange(0, MAX_RANDOM_KEY))
41 + smart_str(time.time())
42 + smart_str(self.challenge, errors="ignore")
43 + smart_str(self.response, errors="ignore")
44 ).encode("utf8")
45 self.hashkey = hashlib.sha1(key_).hexdigest()
46 del key_
47 super().save(*args, **kwargs)
49 def __str__(self):
50 return self.challenge
52 def remove_expired(cls):
53 cls.objects.filter(expiration__lte=timezone.now()).delete()
55 remove_expired = classmethod(remove_expired)
57 @classmethod
58 def generate_key(cls, generator=None):
59 challenge, response = captcha_settings.get_challenge(generator)()
60 store = cls.objects.create(challenge=challenge, response=response)
62 return store.hashkey
64 @classmethod
65 def pick(cls):
66 if not captcha_settings.CAPTCHA_GET_FROM_POOL:
67 return cls.generate_key()
69 def fallback():
70 logger.error("Couldn't get a captcha from pool, generating")
71 return cls.generate_key()
73 # Pick up a random item from pool
74 minimum_expiration = timezone.now() + datetime.timedelta(
75 minutes=int(captcha_settings.CAPTCHA_GET_FROM_POOL_TIMEOUT)
76 )
77 store = (
78 cls.objects.filter(expiration__gt=minimum_expiration).order_by("?").first()
79 )
81 return (store and store.hashkey) or fallback()
83 @classmethod
84 def create_pool(cls, count=1000):
85 assert count > 0
86 while count > 0:
87 cls.generate_key()
88 count -= 1