diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py index 9946014ad2..cac11dfd4c 100644 --- a/django/contrib/auth/hashers.py +++ b/django/contrib/auth/hashers.py @@ -689,7 +689,8 @@ class UnsaltedSHA1PasswordHasher(BasePasswordHasher): return '' def encode(self, password, salt): - assert salt == '' + if salt != '': + raise ValueError('salt must be empty.') hash = hashlib.sha1(password.encode()).hexdigest() return 'sha1$$%s' % hash @@ -733,7 +734,8 @@ class UnsaltedMD5PasswordHasher(BasePasswordHasher): return '' def encode(self, password, salt): - assert salt == '' + if salt != '': + raise ValueError('salt must be empty.') return hashlib.md5(password.encode()).hexdigest() def decode(self, encoded): @@ -774,9 +776,11 @@ class CryptPasswordHasher(BasePasswordHasher): def encode(self, password, salt): crypt = self._load_library() - assert len(salt) == 2 + if len(salt) != 2: + raise ValueError('salt must be of length 2.') hash = crypt.crypt(password, salt) - assert hash is not None # A platform like OpenBSD with a dummy crypt module. + if hash is None: # A platform like OpenBSD with a dummy crypt module. + raise TypeError('hash must be provided.') # we don't need to store the salt, but Django used to do this return '%s$%s$%s' % (self.algorithm, '', hash) diff --git a/tests/auth_tests/test_hashers.py b/tests/auth_tests/test_hashers.py index 81234a44f0..dda64971ba 100644 --- a/tests/auth_tests/test_hashers.py +++ b/tests/auth_tests/test_hashers.py @@ -143,6 +143,13 @@ class TestUtilsHashPass(SimpleTestCase): self.assertTrue(check_password('', blank_encoded)) self.assertFalse(check_password(' ', blank_encoded)) + @override_settings(PASSWORD_HASHERS=['django.contrib.auth.hashers.UnsaltedMD5PasswordHasher']) + def test_unsalted_md5_encode_invalid_salt(self): + hasher = get_hasher('unsalted_md5') + msg = 'salt must be empty.' + with self.assertRaisesMessage(ValueError, msg): + hasher.encode('password', salt='salt') + @override_settings(PASSWORD_HASHERS=['django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher']) def test_unsalted_sha1(self): encoded = make_password('lètmein', '', 'unsalted_sha1') @@ -161,6 +168,13 @@ class TestUtilsHashPass(SimpleTestCase): self.assertTrue(check_password('', blank_encoded)) self.assertFalse(check_password(' ', blank_encoded)) + @override_settings(PASSWORD_HASHERS=['django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher']) + def test_unsalted_sha1_encode_invalid_salt(self): + hasher = get_hasher('unsalted_sha1') + msg = 'salt must be empty.' + with self.assertRaisesMessage(ValueError, msg): + hasher.encode('password', salt='salt') + @skipUnless(crypt, "no crypt module to generate password.") @override_settings(PASSWORD_HASHERS=['django.contrib.auth.hashers.CryptPasswordHasher']) def test_crypt(self): @@ -177,6 +191,23 @@ class TestUtilsHashPass(SimpleTestCase): self.assertTrue(check_password('', blank_encoded)) self.assertFalse(check_password(' ', blank_encoded)) + @skipUnless(crypt, 'no crypt module to generate password.') + @override_settings(PASSWORD_HASHERS=['django.contrib.auth.hashers.CryptPasswordHasher']) + def test_crypt_encode_invalid_salt(self): + hasher = get_hasher('crypt') + msg = 'salt must be of length 2.' + with self.assertRaisesMessage(ValueError, msg): + hasher.encode('password', salt='a') + + @skipUnless(crypt, 'no crypt module to generate password.') + @override_settings(PASSWORD_HASHERS=['django.contrib.auth.hashers.CryptPasswordHasher']) + def test_crypt_encode_invalid_hash(self): + hasher = get_hasher('crypt') + msg = 'hash must be provided.' + with mock.patch('crypt.crypt', return_value=None): + with self.assertRaisesMessage(TypeError, msg): + hasher.encode('password', salt='ab') + @skipUnless(bcrypt, "bcrypt not installed") def test_bcrypt_sha256(self): encoded = make_password('lètmein', hasher='bcrypt_sha256')