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

1import datetime 

2import hashlib 

3import logging 

4import random 

5import time 

6 

7from django.db import models 

8from django.utils import timezone 

9from django.utils.encoding import smart_str 

10 

11from captcha.conf import settings as captcha_settings 

12 

13 

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 

21 

22logger = logging.getLogger(__name__) 

23 

24 

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) 

31 

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) 

48 

49 def __str__(self): 

50 return self.challenge 

51 

52 def remove_expired(cls): 

53 cls.objects.filter(expiration__lte=timezone.now()).delete() 

54 

55 remove_expired = classmethod(remove_expired) 

56 

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) 

61 

62 return store.hashkey 

63 

64 @classmethod 

65 def pick(cls): 

66 if not captcha_settings.CAPTCHA_GET_FROM_POOL: 

67 return cls.generate_key() 

68 

69 def fallback(): 

70 logger.error("Couldn't get a captcha from pool, generating") 

71 return cls.generate_key() 

72 

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 ) 

80 

81 return (store and store.hashkey) or fallback() 

82 

83 @classmethod 

84 def create_pool(cls, count=1000): 

85 assert count > 0 

86 while count > 0: 

87 cls.generate_key() 

88 count -= 1