| from datetime import date |
| from django.conf import settings |
| from django.utils.http import int_to_base36, base36_to_int |
| |
| class PasswordResetTokenGenerator(object): |
| """ |
| Stratgy object used to generate and check tokens for the password |
| reset mechanism. |
| """ |
| def make_token(self, user): |
| """ |
| Returns a token that can be used once to do a password reset |
| for the given user. |
| """ |
| return self._make_token_with_timestamp(user, self._num_days(self._today())) |
| |
| def check_token(self, user, token): |
| """ |
| Check that a password reset token is correct for a given user. |
| """ |
| # Parse the tokem |
| try: |
| ts_b36, hash = token.split("-") |
| except ValueError: |
| return False |
| |
| try: |
| ts = base36_to_int(ts_b36) |
| except ValueError: |
| return False |
| |
| # Check that the timestamp/uid has not been tampered with |
| if self._make_token_with_timestamp(user, ts) != token: |
| return False |
| |
| # Check the timestamp is within limit |
| if (self._num_days(self._today()) - ts) > settings.PASSWORD_RESET_TIMEOUT_DAYS: |
| return False |
| |
| return True |
| |
| def _make_token_with_timestamp(self, user, timestamp): |
| # timestamp is number of days since 2001-1-1. Converted to |
| # base 36, this gives us a 3 digit string until about 2121 |
| ts_b36 = int_to_base36(timestamp) |
| |
| # By hashing on the internal state of the user and using state |
| # that is sure to change (the password salt will change as soon as |
| # the password is set, at least for current Django auth, and |
| # last_login will also change), we produce a hash that will be |
| # invalid as soon as it is used. |
| # We limit the hash to 20 chars to keep URL short |
| from django.utils.hashcompat import sha_constructor |
| hash = sha_constructor(settings.SECRET_KEY + unicode(user.id) + |
| user.password + unicode(user.last_login) + |
| unicode(timestamp)).hexdigest()[::2] |
| return "%s-%s" % (ts_b36, hash) |
| |
| def _num_days(self, dt): |
| return (dt - date(2001,1,1)).days |
| |
| def _today(self): |
| # Used for mocking in tests |
| return date.today() |
| |
| default_token_generator = PasswordResetTokenGenerator() |