Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

tokens.py 4.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. from datetime import datetime
  2. from django.conf import settings
  3. from django.utils.crypto import constant_time_compare, salted_hmac
  4. from django.utils.http import base36_to_int, int_to_base36
  5. class PasswordResetTokenGenerator:
  6. """
  7. Strategy object used to generate and check tokens for the password
  8. reset mechanism.
  9. """
  10. key_salt = "django.contrib.auth.tokens.PasswordResetTokenGenerator"
  11. algorithm = None
  12. _secret = None
  13. _secret_fallbacks = None
  14. def __init__(self):
  15. self.algorithm = self.algorithm or "sha256"
  16. def _get_secret(self):
  17. return self._secret or settings.SECRET_KEY
  18. def _set_secret(self, secret):
  19. self._secret = secret
  20. secret = property(_get_secret, _set_secret)
  21. def _get_fallbacks(self):
  22. if self._secret_fallbacks is None:
  23. return settings.SECRET_KEY_FALLBACKS
  24. return self._secret_fallbacks
  25. def _set_fallbacks(self, fallbacks):
  26. self._secret_fallbacks = fallbacks
  27. secret_fallbacks = property(_get_fallbacks, _set_fallbacks)
  28. def make_token(self, user):
  29. """
  30. Return a token that can be used once to do a password reset
  31. for the given user.
  32. """
  33. return self._make_token_with_timestamp(
  34. user,
  35. self._num_seconds(self._now()),
  36. self.secret,
  37. )
  38. def check_token(self, user, token):
  39. """
  40. Check that a password reset token is correct for a given user.
  41. """
  42. if not (user and token):
  43. return False
  44. # Parse the token
  45. try:
  46. ts_b36, _ = token.split("-")
  47. except ValueError:
  48. return False
  49. try:
  50. ts = base36_to_int(ts_b36)
  51. except ValueError:
  52. return False
  53. # Check that the timestamp/uid has not been tampered with
  54. for secret in [self.secret, *self.secret_fallbacks]:
  55. if constant_time_compare(
  56. self._make_token_with_timestamp(user, ts, secret),
  57. token,
  58. ):
  59. break
  60. else:
  61. return False
  62. # Check the timestamp is within limit.
  63. if (self._num_seconds(self._now()) - ts) > settings.PASSWORD_RESET_TIMEOUT:
  64. return False
  65. return True
  66. def _make_token_with_timestamp(self, user, timestamp, secret):
  67. # timestamp is number of seconds since 2001-1-1. Converted to base 36,
  68. # this gives us a 6 digit string until about 2069.
  69. ts_b36 = int_to_base36(timestamp)
  70. hash_string = salted_hmac(
  71. self.key_salt,
  72. self._make_hash_value(user, timestamp),
  73. secret=secret,
  74. algorithm=self.algorithm,
  75. ).hexdigest()[
  76. ::2
  77. ] # Limit to shorten the URL.
  78. return "%s-%s" % (ts_b36, hash_string)
  79. def _make_hash_value(self, user, timestamp):
  80. """
  81. Hash the user's primary key, email (if available), and some user state
  82. that's sure to change after a password reset to produce a token that is
  83. invalidated when it's used:
  84. 1. The password field will change upon a password reset (even if the
  85. same password is chosen, due to password salting).
  86. 2. The last_login field will usually be updated very shortly after
  87. a password reset.
  88. Failing those things, settings.PASSWORD_RESET_TIMEOUT eventually
  89. invalidates the token.
  90. Running this data through salted_hmac() prevents password cracking
  91. attempts using the reset token, provided the secret isn't compromised.
  92. """
  93. # Truncate microseconds so that tokens are consistent even if the
  94. # database doesn't support microseconds.
  95. login_timestamp = (
  96. ""
  97. if user.last_login is None
  98. else user.last_login.replace(microsecond=0, tzinfo=None)
  99. )
  100. email_field = user.get_email_field_name()
  101. email = getattr(user, email_field, "") or ""
  102. return f"{user.pk}{user.password}{login_timestamp}{timestamp}{email}"
  103. def _num_seconds(self, dt):
  104. return int((dt - datetime(2001, 1, 1)).total_seconds())
  105. def _now(self):
  106. # Used for mocking in tests
  107. return datetime.now()
  108. default_token_generator = PasswordResetTokenGenerator()