models.py 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. import datetime
  2. import hashlib
  3. import logging
  4. import random
  5. import time
  6. from django.db import models
  7. from django.utils import timezone
  8. from django.utils.encoding import smart_str
  9. from captcha.conf import settings as captcha_settings
  10. # Heavily based on session key generation in Django
  11. # Use the system (hardware-based) random number generator if it exists.
  12. if hasattr(random, "SystemRandom"):
  13. randrange = random.SystemRandom().randrange
  14. else:
  15. randrange = random.randrange
  16. MAX_RANDOM_KEY = 18446744073709551616 # 2 << 63
  17. logger = logging.getLogger(__name__)
  18. class CaptchaStore(models.Model):
  19. id = models.AutoField(primary_key=True)
  20. challenge = models.CharField(blank=False, max_length=32)
  21. response = models.CharField(blank=False, max_length=32)
  22. hashkey = models.CharField(blank=False, max_length=40, unique=True)
  23. expiration = models.DateTimeField(blank=False)
  24. def save(self, *args, **kwargs):
  25. self.response = self.response.lower()
  26. if not self.expiration:
  27. self.expiration = timezone.now() + datetime.timedelta(
  28. minutes=int(captcha_settings.CAPTCHA_TIMEOUT)
  29. )
  30. if not self.hashkey:
  31. key_ = (
  32. smart_str(randrange(0, MAX_RANDOM_KEY))
  33. + smart_str(time.time())
  34. + smart_str(self.challenge, errors="ignore")
  35. + smart_str(self.response, errors="ignore")
  36. ).encode("utf8")
  37. self.hashkey = hashlib.sha1(key_).hexdigest()
  38. del key_
  39. super(CaptchaStore, self).save(*args, **kwargs)
  40. def __str__(self):
  41. return self.challenge
  42. def remove_expired(cls):
  43. cls.objects.filter(expiration__lte=timezone.now()).delete()
  44. remove_expired = classmethod(remove_expired)
  45. @classmethod
  46. def generate_key(cls, generator=None):
  47. challenge, response = captcha_settings.get_challenge(generator)()
  48. store = cls.objects.create(challenge=challenge, response=response)
  49. return store.hashkey
  50. @classmethod
  51. def pick(cls):
  52. if not captcha_settings.CAPTCHA_GET_FROM_POOL:
  53. return cls.generate_key()
  54. def fallback():
  55. logger.error("Couldn't get a captcha from pool, generating")
  56. return cls.generate_key()
  57. # Pick up a random item from pool
  58. minimum_expiration = timezone.now() + datetime.timedelta(
  59. minutes=int(captcha_settings.CAPTCHA_GET_FROM_POOL_TIMEOUT)
  60. )
  61. store = (
  62. cls.objects.filter(expiration__gt=minimum_expiration).order_by("?").first()
  63. )
  64. return (store and store.hashkey) or fallback()
  65. @classmethod
  66. def create_pool(cls, count=1000):
  67. assert count > 0
  68. while count > 0:
  69. cls.generate_key()
  70. count -= 1