/django/contrib/auth/tokens.py

https://github.com/blacktear23/django · Python · 82 lines · 42 code · 13 blank · 27 comment · 7 complexity · 46605cb9696e40a1e94ac223e9b2b6c4 MD5 · raw file

  1. from datetime import date
  2. from django.conf import settings
  3. from django.utils.hashcompat import sha_constructor
  4. from django.utils.http import int_to_base36, base36_to_int
  5. from django.utils.crypto import constant_time_compare, salted_hmac
  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. # Fallback to Django 1.2 method for compatibility.
  33. # PendingDeprecationWarning <- here to remind us to remove this in
  34. # Django 1.5
  35. if not constant_time_compare(self._make_token_with_timestamp_old(user, ts), token):
  36. return False
  37. # Check the timestamp is within limit
  38. if (self._num_days(self._today()) - ts) > settings.PASSWORD_RESET_TIMEOUT_DAYS:
  39. return False
  40. return True
  41. def _make_token_with_timestamp(self, user, timestamp):
  42. # timestamp is number of days since 2001-1-1. Converted to
  43. # base 36, this gives us a 3 digit string until about 2121
  44. ts_b36 = int_to_base36(timestamp)
  45. # By hashing on the internal state of the user and using state
  46. # that is sure to change (the password salt will change as soon as
  47. # the password is set, at least for current Django auth, and
  48. # last_login will also change), we produce a hash that will be
  49. # invalid as soon as it is used.
  50. # We limit the hash to 20 chars to keep URL short
  51. key_salt = "django.contrib.auth.tokens.PasswordResetTokenGenerator"
  52. value = unicode(user.id) + \
  53. user.password + user.last_login.strftime('%Y-%m-%d %H:%M:%S') + \
  54. unicode(timestamp)
  55. hash = salted_hmac(key_salt, value).hexdigest()[::2]
  56. return "%s-%s" % (ts_b36, hash)
  57. def _make_token_with_timestamp_old(self, user, timestamp):
  58. # The Django 1.2 method
  59. ts_b36 = int_to_base36(timestamp)
  60. hash = sha_constructor(settings.SECRET_KEY + unicode(user.id) +
  61. user.password + user.last_login.strftime('%Y-%m-%d %H:%M:%S') +
  62. unicode(timestamp)).hexdigest()[::2]
  63. return "%s-%s" % (ts_b36, hash)
  64. def _num_days(self, dt):
  65. return (dt - date(2001,1,1)).days
  66. def _today(self):
  67. # Used for mocking in tests
  68. return date.today()
  69. default_token_generator = PasswordResetTokenGenerator()