tokens.py 2.6 KB

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