diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py index 432c624483..8f0e11507e 100644 --- a/django/contrib/auth/hashers.py +++ b/django/contrib/auth/hashers.py @@ -17,7 +17,7 @@ from django.utils.crypto import ( md5, pbkdf2, ) -from django.utils.deprecation import RemovedInDjango50Warning, RemovedInDjango51Warning +from django.utils.deprecation import RemovedInDjango51Warning from django.utils.module_loading import import_string from django.utils.translation import gettext_noop as _ @@ -823,62 +823,3 @@ class UnsaltedMD5PasswordHasher(BasePasswordHasher): def harden_runtime(self, password, encoded): pass - - -# RemovedInDjango50Warning. -class CryptPasswordHasher(BasePasswordHasher): - """ - Password hashing using UNIX crypt (not recommended) - - The crypt module is not supported on all platforms. - """ - - algorithm = "crypt" - library = "crypt" - - def __init__(self, *args, **kwargs): - warnings.warn( - "django.contrib.auth.hashers.CryptPasswordHasher is deprecated.", - RemovedInDjango50Warning, - stacklevel=2, - ) - super().__init__(*args, **kwargs) - - def salt(self): - return get_random_string(2) - - def encode(self, password, salt): - crypt = self._load_library() - if len(salt) != 2: - raise ValueError("salt must be of length 2.") - hash = crypt.crypt(password, salt) - 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) - - def decode(self, encoded): - algorithm, salt, hash = encoded.split("$", 2) - assert algorithm == self.algorithm - return { - "algorithm": algorithm, - "hash": hash, - "salt": salt, - } - - def verify(self, password, encoded): - crypt = self._load_library() - decoded = self.decode(encoded) - data = crypt.crypt(password, decoded["hash"]) - return constant_time_compare(decoded["hash"], data) - - def safe_summary(self, encoded): - decoded = self.decode(encoded) - return { - _("algorithm"): decoded["algorithm"], - _("salt"): decoded["salt"], - _("hash"): mask_hash(decoded["hash"], show=3), - } - - def harden_runtime(self, password, encoded): - pass diff --git a/docs/releases/5.0.txt b/docs/releases/5.0.txt index 31d5e184dc..f1ab7b2e7f 100644 --- a/docs/releases/5.0.txt +++ b/docs/releases/5.0.txt @@ -340,3 +340,5 @@ to remove usage of these features. longer allowed. * The ``django.contrib.gis.admin.OpenLayersWidget`` is removed. + ++ The ``django.contrib.auth.hashers.CryptPasswordHasher`` is removed. diff --git a/tests/auth_tests/test_hashers.py b/tests/auth_tests/test_hashers.py index 4d718da46a..b419b0349d 100644 --- a/tests/auth_tests/test_hashers.py +++ b/tests/auth_tests/test_hashers.py @@ -19,17 +19,7 @@ from django.contrib.auth.hashers import ( ) from django.test import SimpleTestCase, ignore_warnings from django.test.utils import override_settings -from django.utils.deprecation import RemovedInDjango50Warning, RemovedInDjango51Warning - -# RemovedInDjango50Warning. -try: - import crypt -except ImportError: - crypt = None -else: - # On some platforms (e.g. OpenBSD), crypt.crypt() always return None. - if crypt.crypt("") is None: - crypt = None +from django.utils.deprecation import RemovedInDjango51Warning try: import bcrypt @@ -239,57 +229,6 @@ class TestUtilsHashPass(SimpleTestCase): with self.assertRaisesMessage(RemovedInDjango51Warning, msg): get_hasher("unsalted_sha1") - @ignore_warnings(category=RemovedInDjango50Warning) - @skipUnless(crypt, "no crypt module to generate password.") - @override_settings( - PASSWORD_HASHERS=["django.contrib.auth.hashers.CryptPasswordHasher"] - ) - def test_crypt(self): - encoded = make_password("lètmei", "ab", "crypt") - self.assertEqual(encoded, "crypt$$ab1Hv2Lg7ltQo") - self.assertTrue(is_password_usable(encoded)) - self.assertTrue(check_password("lètmei", encoded)) - self.assertFalse(check_password("lètmeiz", encoded)) - self.assertEqual(identify_hasher(encoded).algorithm, "crypt") - # Blank passwords - blank_encoded = make_password("", "ab", "crypt") - self.assertTrue(blank_encoded.startswith("crypt$")) - self.assertTrue(is_password_usable(blank_encoded)) - self.assertTrue(check_password("", blank_encoded)) - self.assertFalse(check_password(" ", blank_encoded)) - - @ignore_warnings(category=RemovedInDjango50Warning) - @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") - - @ignore_warnings(category=RemovedInDjango50Warning) - @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(crypt, "no crypt module to generate password.") - @override_settings( - PASSWORD_HASHERS=["django.contrib.auth.hashers.CryptPasswordHasher"] - ) - def test_crypt_deprecation_warning(self): - msg = "django.contrib.auth.hashers.CryptPasswordHasher is deprecated." - with self.assertRaisesMessage(RemovedInDjango50Warning, msg): - get_hasher("crypt") - @skipUnless(bcrypt, "bcrypt not installed") def test_bcrypt_sha256(self): encoded = make_password("lètmein", hasher="bcrypt_sha256")