1
0
mirror of https://github.com/django/django.git synced 2025-10-27 07:36:08 +00:00

Fixed #28622 -- Allowed specifying password reset link expiration in seconds and deprecated PASSWORD_RESET_TIMEOUT_DAYS.

This commit is contained in:
Hasan Ramezani
2019-08-23 17:14:07 +02:00
committed by Mariusz Felisiak
parent 0719edcd5f
commit 226ebb1729
8 changed files with 178 additions and 27 deletions

View File

@@ -1,4 +1,4 @@
from datetime import date
from datetime import datetime
from django.conf import settings
from django.utils.crypto import constant_time_compare, salted_hmac
@@ -18,7 +18,7 @@ class PasswordResetTokenGenerator:
Return 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()))
return self._make_token_with_timestamp(user, self._num_seconds(self._now()))
def check_token(self, user, token):
"""
@@ -41,19 +41,15 @@ class PasswordResetTokenGenerator:
if not constant_time_compare(self._make_token_with_timestamp(user, ts), token):
return False
# Check the timestamp is within limit. Timestamps are rounded to
# midnight (server time) providing a resolution of only 1 day. If a
# link is generated 5 minutes before midnight and used 6 minutes later,
# that counts as 1 day. Therefore, PASSWORD_RESET_TIMEOUT_DAYS = 1 means
# "at least 1 day, could be up to 2."
if (self._num_days(self._today()) - ts) > settings.PASSWORD_RESET_TIMEOUT_DAYS:
# Check the timestamp is within limit.
if (self._num_seconds(self._now()) - ts) > settings.PASSWORD_RESET_TIMEOUT:
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
# timestamp is number of seconds since 2001-1-1. Converted to base 36,
# this gives us a 6 digit string until about 2069.
ts_b36 = int_to_base36(timestamp)
hash_string = salted_hmac(
self.key_salt,
@@ -71,7 +67,7 @@ class PasswordResetTokenGenerator:
same password is chosen, due to password salting).
2. The last_login field will usually be updated very shortly after
a password reset.
Failing those things, settings.PASSWORD_RESET_TIMEOUT_DAYS eventually
Failing those things, settings.PASSWORD_RESET_TIMEOUT eventually
invalidates the token.
Running this data through salted_hmac() prevents password cracking
@@ -82,12 +78,12 @@ class PasswordResetTokenGenerator:
login_timestamp = '' if user.last_login is None else user.last_login.replace(microsecond=0, tzinfo=None)
return str(user.pk) + user.password + str(login_timestamp) + str(timestamp)
def _num_days(self, dt):
return (dt - date(2001, 1, 1)).days
def _num_seconds(self, dt):
return int((dt - datetime(2001, 1, 1)).total_seconds())
def _today(self):
def _now(self):
# Used for mocking in tests
return date.today()
return datetime.now()
default_token_generator = PasswordResetTokenGenerator()