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 2.7 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. from datetime import date
  2. from django.conf import settings
  3. from django.utils import six
  4. from django.utils.crypto import constant_time_compare, salted_hmac
  5. from django.utils.http import base36_to_int, int_to_base36
  6. class PasswordResetTokenGenerator(object):
  7. """
  8. Strategy object used to generate and check tokens for the password
  9. reset mechanism.
  10. """
  11. key_salt = "django.contrib.auth.tokens.PasswordResetTokenGenerator"
  12. def make_token(self, user):
  13. """
  14. Returns a token that can be used once to do a password reset
  15. for the given user.
  16. """
  17. return self._make_token_with_timestamp(user, self._num_days(self._today()))
  18. def check_token(self, user, token):
  19. """
  20. Check that a password reset token is correct for a given user.
  21. """
  22. # Parse the token
  23. try:
  24. ts_b36, hash = token.split("-")
  25. except ValueError:
  26. return False
  27. try:
  28. ts = base36_to_int(ts_b36)
  29. except ValueError:
  30. return False
  31. # Check that the timestamp/uid has not been tampered with
  32. if not constant_time_compare(self._make_token_with_timestamp(user, ts), token):
  33. return False
  34. # Check the timestamp is within limit
  35. if (self._num_days(self._today()) - ts) > settings.PASSWORD_RESET_TIMEOUT_DAYS:
  36. return False
  37. return True
  38. def _make_token_with_timestamp(self, user, timestamp):
  39. # timestamp is number of days since 2001-1-1. Converted to
  40. # base 36, this gives us a 3 digit string until about 2121
  41. ts_b36 = int_to_base36(timestamp)
  42. # By hashing on the internal state of the user and using state
  43. # that is sure to change (the password salt will change as soon as
  44. # the password is set, at least for current Django auth, and
  45. # last_login will also change), we produce a hash that will be
  46. # invalid as soon as it is used.
  47. # We limit the hash to 20 chars to keep URL short
  48. hash = salted_hmac(
  49. self.key_salt,
  50. self._make_hash_value(user, timestamp),
  51. ).hexdigest()[::2]
  52. return "%s-%s" % (ts_b36, hash)
  53. def _make_hash_value(self, user, timestamp):
  54. # Ensure results are consistent across DB backends
  55. login_timestamp = '' if user.last_login is None else user.last_login.replace(microsecond=0, tzinfo=None)
  56. return (
  57. six.text_type(user.pk) + user.password +
  58. six.text_type(login_timestamp) + six.text_type(timestamp)
  59. )
  60. def _num_days(self, dt):
  61. return (dt - date(2001, 1, 1)).days
  62. def _today(self):
  63. # Used for mocking in tests
  64. return date.today()
  65. default_token_generator = PasswordResetTokenGenerator()