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