Fixed #28718 -- Allowed user to request a password reset if their password doesn't use an enabled hasher.
Regression inaeb1389442
. Reverted changes to is_password_usable() from703c266682
and documentation changes from92f48680db
.
This commit is contained in:
parent
d97cce3409
commit
a4f0e9aec7
|
@ -116,9 +116,7 @@ class AbstractBaseUser(models.Model):
|
||||||
|
|
||||||
def has_usable_password(self):
|
def has_usable_password(self):
|
||||||
"""
|
"""
|
||||||
Return False if set_unusable_password() has been called for this user,
|
Return False if set_unusable_password() has been called for this user.
|
||||||
or if the password is None, or if the password uses a hasher that's not
|
|
||||||
in the PASSWORD_HASHERS setting.
|
|
||||||
"""
|
"""
|
||||||
return is_password_usable(self.password)
|
return is_password_usable(self.password)
|
||||||
|
|
||||||
|
|
|
@ -21,13 +21,11 @@ UNUSABLE_PASSWORD_SUFFIX_LENGTH = 40 # number of random chars to add after UNUS
|
||||||
|
|
||||||
|
|
||||||
def is_password_usable(encoded):
|
def is_password_usable(encoded):
|
||||||
if encoded is None or encoded.startswith(UNUSABLE_PASSWORD_PREFIX):
|
"""
|
||||||
return False
|
Return True if this password wasn't generated by
|
||||||
try:
|
User.set_unusable_password(), i.e. make_password(None).
|
||||||
identify_hasher(encoded)
|
"""
|
||||||
except ValueError:
|
return encoded is None or not encoded.startswith(UNUSABLE_PASSWORD_PREFIX)
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def check_password(password, encoded, setter=None, preferred='default'):
|
def check_password(password, encoded, setter=None, preferred='default'):
|
||||||
|
@ -42,7 +40,11 @@ def check_password(password, encoded, setter=None, preferred='default'):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
preferred = get_hasher(preferred)
|
preferred = get_hasher(preferred)
|
||||||
hasher = identify_hasher(encoded)
|
try:
|
||||||
|
hasher = identify_hasher(encoded)
|
||||||
|
except ValueError:
|
||||||
|
# encoded is gibberish or uses a hasher that's no longer installed.
|
||||||
|
return False
|
||||||
|
|
||||||
hasher_changed = hasher.algorithm != preferred.algorithm
|
hasher_changed = hasher.algorithm != preferred.algorithm
|
||||||
must_update = hasher_changed or preferred.must_update(encoded)
|
must_update = hasher_changed or preferred.must_update(encoded)
|
||||||
|
|
|
@ -212,9 +212,15 @@ Methods
|
||||||
|
|
||||||
Returns ``False`` if
|
Returns ``False`` if
|
||||||
:meth:`~django.contrib.auth.models.User.set_unusable_password()` has
|
:meth:`~django.contrib.auth.models.User.set_unusable_password()` has
|
||||||
been called for this user, or if the password is ``None``, or if the
|
been called for this user.
|
||||||
password uses a hasher that's not in the :setting:`PASSWORD_HASHERS`
|
|
||||||
setting.
|
.. versionchanged:: 2.1
|
||||||
|
|
||||||
|
In older versions, this also returns ``False`` if the password is
|
||||||
|
``None`` or an empty string, or if the password uses a hasher
|
||||||
|
that's not in the :setting:`PASSWORD_HASHERS` setting. That
|
||||||
|
behavior is considered a bug as it prevents users with such
|
||||||
|
passwords from requesting a password reset.
|
||||||
|
|
||||||
.. method:: get_group_permissions(obj=None)
|
.. method:: get_group_permissions(obj=None)
|
||||||
|
|
||||||
|
|
|
@ -358,6 +358,14 @@ Miscellaneous
|
||||||
changed from 0 to an empty string, which mainly may require some adjustments
|
changed from 0 to an empty string, which mainly may require some adjustments
|
||||||
in tests that compare HTML.
|
in tests that compare HTML.
|
||||||
|
|
||||||
|
* :meth:`.User.has_usable_password` and the
|
||||||
|
:func:`~django.contrib.auth.hashers.is_password_usable` function no longer
|
||||||
|
return ``False`` if the password is ``None`` or an empty string, or if the
|
||||||
|
password uses a hasher that's not in the :setting:`PASSWORD_HASHERS` setting.
|
||||||
|
This undocumented behavior was a regression in Django 1.6 and prevented users
|
||||||
|
with such passwords from requesting a password reset. Audit your code to
|
||||||
|
confirm that your usage of these APIs don't rely on the old behavior.
|
||||||
|
|
||||||
.. _deprecated-features-2.1:
|
.. _deprecated-features-2.1:
|
||||||
|
|
||||||
Features deprecated in 2.1
|
Features deprecated in 2.1
|
||||||
|
|
|
@ -409,8 +409,16 @@ from the ``User`` model.
|
||||||
|
|
||||||
.. function:: is_password_usable(encoded_password)
|
.. function:: is_password_usable(encoded_password)
|
||||||
|
|
||||||
Checks if the given string is a hashed password that has a chance
|
Returns ``False`` if the password is a result of
|
||||||
of being verified against :func:`check_password`.
|
:meth:`.User.set_unusable_password`.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.1
|
||||||
|
|
||||||
|
In older versions, this also returns ``False`` if the password is
|
||||||
|
``None`` or an empty string, or if the password uses a hasher that's
|
||||||
|
not in the :setting:`PASSWORD_HASHERS` setting. That behavior is
|
||||||
|
considered a bug as it prevents users with such passwords from
|
||||||
|
requesting a password reset.
|
||||||
|
|
||||||
.. _password-validation:
|
.. _password-validation:
|
||||||
|
|
||||||
|
|
|
@ -276,9 +276,11 @@ class TestUtilsHashPass(SimpleTestCase):
|
||||||
with self.assertRaisesMessage(ValueError, msg % 'lolcat'):
|
with self.assertRaisesMessage(ValueError, msg % 'lolcat'):
|
||||||
identify_hasher('lolcat$salt$hash')
|
identify_hasher('lolcat$salt$hash')
|
||||||
|
|
||||||
def test_bad_encoded(self):
|
def test_is_password_usable(self):
|
||||||
self.assertFalse(is_password_usable('lètmein_badencoded'))
|
passwords = ('lètmein_badencoded', '', None)
|
||||||
self.assertFalse(is_password_usable(''))
|
for password in passwords:
|
||||||
|
with self.subTest(password=password):
|
||||||
|
self.assertIs(is_password_usable(password), True)
|
||||||
|
|
||||||
def test_low_level_pbkdf2(self):
|
def test_low_level_pbkdf2(self):
|
||||||
hasher = PBKDF2PasswordHasher()
|
hasher = PBKDF2PasswordHasher()
|
||||||
|
|
|
@ -158,6 +158,13 @@ class UserManagerTestCase(TestCase):
|
||||||
|
|
||||||
class AbstractBaseUserTests(TestCase):
|
class AbstractBaseUserTests(TestCase):
|
||||||
|
|
||||||
|
def test_has_usable_password(self):
|
||||||
|
"""
|
||||||
|
Passwords are usable even if they don't correspond to a hasher in
|
||||||
|
settings.PASSWORD_HASHERS.
|
||||||
|
"""
|
||||||
|
self.assertIs(User(password='some-gibbberish').has_usable_password(), True)
|
||||||
|
|
||||||
def test_normalize_username(self):
|
def test_normalize_username(self):
|
||||||
self.assertEqual(IntegerUsernameUser().normalize_username(123), 123)
|
self.assertEqual(IntegerUsernameUser().normalize_username(123), 123)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue