mirror of https://github.com/django/django.git
[1.5.x] Ensure that passwords are never long enough for a DoS.
* Limit the password length to 4096 bytes
* Password hashers will raise a ValueError
* django.contrib.auth forms will fail validation
* Document in release notes that this is a backwards incompatible change
Thanks to Josh Wright for the report, and Donald Stufft for the patch.
This is a security fix; disclosure to follow shortly.
Backport of aae5a96d57
from master.
This commit is contained in:
parent
e66fe357b2
commit
22b74fa09d
|
@ -12,7 +12,9 @@ from django.utils.translation import ugettext, ugettext_lazy as _
|
||||||
|
|
||||||
from django.contrib.auth import authenticate, get_user_model
|
from django.contrib.auth import authenticate, get_user_model
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.auth.hashers import UNUSABLE_PASSWORD, identify_hasher
|
from django.contrib.auth.hashers import (
|
||||||
|
MAXIMUM_PASSWORD_LENGTH, UNUSABLE_PASSWORD, identify_hasher,
|
||||||
|
)
|
||||||
from django.contrib.auth.tokens import default_token_generator
|
from django.contrib.auth.tokens import default_token_generator
|
||||||
from django.contrib.sites.models import get_current_site
|
from django.contrib.sites.models import get_current_site
|
||||||
|
|
||||||
|
@ -75,9 +77,10 @@ class UserCreationForm(forms.ModelForm):
|
||||||
'invalid': _("This value may contain only letters, numbers and "
|
'invalid': _("This value may contain only letters, numbers and "
|
||||||
"@/./+/-/_ characters.")})
|
"@/./+/-/_ characters.")})
|
||||||
password1 = forms.CharField(label=_("Password"),
|
password1 = forms.CharField(label=_("Password"),
|
||||||
widget=forms.PasswordInput)
|
widget=forms.PasswordInput, max_length=MAXIMUM_PASSWORD_LENGTH)
|
||||||
password2 = forms.CharField(label=_("Password confirmation"),
|
password2 = forms.CharField(label=_("Password confirmation"),
|
||||||
widget=forms.PasswordInput,
|
widget=forms.PasswordInput,
|
||||||
|
max_length=MAXIMUM_PASSWORD_LENGTH,
|
||||||
help_text=_("Enter the same password as above, for verification."))
|
help_text=_("Enter the same password as above, for verification."))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -145,7 +148,11 @@ class AuthenticationForm(forms.Form):
|
||||||
username/password logins.
|
username/password logins.
|
||||||
"""
|
"""
|
||||||
username = forms.CharField(max_length=254)
|
username = forms.CharField(max_length=254)
|
||||||
password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
|
password = forms.CharField(
|
||||||
|
label=_("Password"),
|
||||||
|
widget=forms.PasswordInput,
|
||||||
|
max_length=MAXIMUM_PASSWORD_LENGTH,
|
||||||
|
)
|
||||||
|
|
||||||
error_messages = {
|
error_messages = {
|
||||||
'invalid_login': _("Please enter a correct %(username)s and password. "
|
'invalid_login': _("Please enter a correct %(username)s and password. "
|
||||||
|
@ -269,10 +276,16 @@ class SetPasswordForm(forms.Form):
|
||||||
error_messages = {
|
error_messages = {
|
||||||
'password_mismatch': _("The two password fields didn't match."),
|
'password_mismatch': _("The two password fields didn't match."),
|
||||||
}
|
}
|
||||||
new_password1 = forms.CharField(label=_("New password"),
|
new_password1 = forms.CharField(
|
||||||
widget=forms.PasswordInput)
|
label=_("New password"),
|
||||||
new_password2 = forms.CharField(label=_("New password confirmation"),
|
widget=forms.PasswordInput,
|
||||||
widget=forms.PasswordInput)
|
max_length=MAXIMUM_PASSWORD_LENGTH,
|
||||||
|
)
|
||||||
|
new_password2 = forms.CharField(
|
||||||
|
label=_("New password confirmation"),
|
||||||
|
widget=forms.PasswordInput,
|
||||||
|
max_length=MAXIMUM_PASSWORD_LENGTH,
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, user, *args, **kwargs):
|
def __init__(self, user, *args, **kwargs):
|
||||||
self.user = user
|
self.user = user
|
||||||
|
@ -303,8 +316,11 @@ class PasswordChangeForm(SetPasswordForm):
|
||||||
'password_incorrect': _("Your old password was entered incorrectly. "
|
'password_incorrect': _("Your old password was entered incorrectly. "
|
||||||
"Please enter it again."),
|
"Please enter it again."),
|
||||||
})
|
})
|
||||||
old_password = forms.CharField(label=_("Old password"),
|
old_password = forms.CharField(
|
||||||
widget=forms.PasswordInput)
|
label=_("Old password"),
|
||||||
|
widget=forms.PasswordInput,
|
||||||
|
max_length=MAXIMUM_PASSWORD_LENGTH,
|
||||||
|
)
|
||||||
|
|
||||||
def clean_old_password(self):
|
def clean_old_password(self):
|
||||||
"""
|
"""
|
||||||
|
@ -329,10 +345,16 @@ class AdminPasswordChangeForm(forms.Form):
|
||||||
error_messages = {
|
error_messages = {
|
||||||
'password_mismatch': _("The two password fields didn't match."),
|
'password_mismatch': _("The two password fields didn't match."),
|
||||||
}
|
}
|
||||||
password1 = forms.CharField(label=_("Password"),
|
password1 = forms.CharField(
|
||||||
widget=forms.PasswordInput)
|
label=_("Password"),
|
||||||
password2 = forms.CharField(label=_("Password (again)"),
|
widget=forms.PasswordInput,
|
||||||
widget=forms.PasswordInput)
|
max_length=MAXIMUM_PASSWORD_LENGTH,
|
||||||
|
)
|
||||||
|
password2 = forms.CharField(
|
||||||
|
label=_("Password (again)"),
|
||||||
|
widget=forms.PasswordInput,
|
||||||
|
max_length=MAXIMUM_PASSWORD_LENGTH,
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, user, *args, **kwargs):
|
def __init__(self, user, *args, **kwargs):
|
||||||
self.user = user
|
self.user = user
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
import functools
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
@ -16,6 +17,7 @@ from django.utils.translation import ugettext_noop as _
|
||||||
|
|
||||||
|
|
||||||
UNUSABLE_PASSWORD = '!' # This will never be a valid encoded hash
|
UNUSABLE_PASSWORD = '!' # This will never be a valid encoded hash
|
||||||
|
MAXIMUM_PASSWORD_LENGTH = 4096 # The maximum length a password can be to prevent DoS
|
||||||
HASHERS = None # lazily loaded from PASSWORD_HASHERS
|
HASHERS = None # lazily loaded from PASSWORD_HASHERS
|
||||||
PREFERRED_HASHER = None # defaults to first item in PASSWORD_HASHERS
|
PREFERRED_HASHER = None # defaults to first item in PASSWORD_HASHERS
|
||||||
|
|
||||||
|
@ -27,6 +29,18 @@ def reset_hashers(**kwargs):
|
||||||
PREFERRED_HASHER = None
|
PREFERRED_HASHER = None
|
||||||
|
|
||||||
|
|
||||||
|
def password_max_length(max_length):
|
||||||
|
def inner(fn):
|
||||||
|
@functools.wraps(fn)
|
||||||
|
def wrapper(self, password, *args, **kwargs):
|
||||||
|
if len(password) > max_length:
|
||||||
|
raise ValueError("Invalid password; Must be less than or equal"
|
||||||
|
" to %d bytes" % max_length)
|
||||||
|
return fn(self, password, *args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
return inner
|
||||||
|
|
||||||
|
|
||||||
def is_password_usable(encoded):
|
def is_password_usable(encoded):
|
||||||
if encoded is None or encoded == UNUSABLE_PASSWORD:
|
if encoded is None or encoded == UNUSABLE_PASSWORD:
|
||||||
return False
|
return False
|
||||||
|
@ -225,6 +239,7 @@ class PBKDF2PasswordHasher(BasePasswordHasher):
|
||||||
iterations = 10000
|
iterations = 10000
|
||||||
digest = hashlib.sha256
|
digest = hashlib.sha256
|
||||||
|
|
||||||
|
@password_max_length(MAXIMUM_PASSWORD_LENGTH)
|
||||||
def encode(self, password, salt, iterations=None):
|
def encode(self, password, salt, iterations=None):
|
||||||
assert password
|
assert password
|
||||||
assert salt and '$' not in salt
|
assert salt and '$' not in salt
|
||||||
|
@ -234,6 +249,7 @@ class PBKDF2PasswordHasher(BasePasswordHasher):
|
||||||
hash = base64.b64encode(hash).decode('ascii').strip()
|
hash = base64.b64encode(hash).decode('ascii').strip()
|
||||||
return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash)
|
return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash)
|
||||||
|
|
||||||
|
@password_max_length(MAXIMUM_PASSWORD_LENGTH)
|
||||||
def verify(self, password, encoded):
|
def verify(self, password, encoded):
|
||||||
algorithm, iterations, salt, hash = encoded.split('$', 3)
|
algorithm, iterations, salt, hash = encoded.split('$', 3)
|
||||||
assert algorithm == self.algorithm
|
assert algorithm == self.algorithm
|
||||||
|
@ -279,6 +295,7 @@ class BCryptPasswordHasher(BasePasswordHasher):
|
||||||
bcrypt = self._load_library()
|
bcrypt = self._load_library()
|
||||||
return bcrypt.gensalt(self.rounds)
|
return bcrypt.gensalt(self.rounds)
|
||||||
|
|
||||||
|
@password_max_length(MAXIMUM_PASSWORD_LENGTH)
|
||||||
def encode(self, password, salt):
|
def encode(self, password, salt):
|
||||||
bcrypt = self._load_library()
|
bcrypt = self._load_library()
|
||||||
# Need to reevaluate the force_bytes call once bcrypt is supported on
|
# Need to reevaluate the force_bytes call once bcrypt is supported on
|
||||||
|
@ -286,6 +303,7 @@ class BCryptPasswordHasher(BasePasswordHasher):
|
||||||
data = bcrypt.hashpw(force_bytes(password), salt)
|
data = bcrypt.hashpw(force_bytes(password), salt)
|
||||||
return "%s$%s" % (self.algorithm, data)
|
return "%s$%s" % (self.algorithm, data)
|
||||||
|
|
||||||
|
@password_max_length(MAXIMUM_PASSWORD_LENGTH)
|
||||||
def verify(self, password, encoded):
|
def verify(self, password, encoded):
|
||||||
algorithm, data = encoded.split('$', 1)
|
algorithm, data = encoded.split('$', 1)
|
||||||
assert algorithm == self.algorithm
|
assert algorithm == self.algorithm
|
||||||
|
@ -310,12 +328,14 @@ class SHA1PasswordHasher(BasePasswordHasher):
|
||||||
"""
|
"""
|
||||||
algorithm = "sha1"
|
algorithm = "sha1"
|
||||||
|
|
||||||
|
@password_max_length(MAXIMUM_PASSWORD_LENGTH)
|
||||||
def encode(self, password, salt):
|
def encode(self, password, salt):
|
||||||
assert password
|
assert password
|
||||||
assert salt and '$' not in salt
|
assert salt and '$' not in salt
|
||||||
hash = hashlib.sha1(force_bytes(salt + password)).hexdigest()
|
hash = hashlib.sha1(force_bytes(salt + password)).hexdigest()
|
||||||
return "%s$%s$%s" % (self.algorithm, salt, hash)
|
return "%s$%s$%s" % (self.algorithm, salt, hash)
|
||||||
|
|
||||||
|
@password_max_length(MAXIMUM_PASSWORD_LENGTH)
|
||||||
def verify(self, password, encoded):
|
def verify(self, password, encoded):
|
||||||
algorithm, salt, hash = encoded.split('$', 2)
|
algorithm, salt, hash = encoded.split('$', 2)
|
||||||
assert algorithm == self.algorithm
|
assert algorithm == self.algorithm
|
||||||
|
@ -338,12 +358,14 @@ class MD5PasswordHasher(BasePasswordHasher):
|
||||||
"""
|
"""
|
||||||
algorithm = "md5"
|
algorithm = "md5"
|
||||||
|
|
||||||
|
@password_max_length(MAXIMUM_PASSWORD_LENGTH)
|
||||||
def encode(self, password, salt):
|
def encode(self, password, salt):
|
||||||
assert password
|
assert password
|
||||||
assert salt and '$' not in salt
|
assert salt and '$' not in salt
|
||||||
hash = hashlib.md5(force_bytes(salt + password)).hexdigest()
|
hash = hashlib.md5(force_bytes(salt + password)).hexdigest()
|
||||||
return "%s$%s$%s" % (self.algorithm, salt, hash)
|
return "%s$%s$%s" % (self.algorithm, salt, hash)
|
||||||
|
|
||||||
|
@password_max_length(MAXIMUM_PASSWORD_LENGTH)
|
||||||
def verify(self, password, encoded):
|
def verify(self, password, encoded):
|
||||||
algorithm, salt, hash = encoded.split('$', 2)
|
algorithm, salt, hash = encoded.split('$', 2)
|
||||||
assert algorithm == self.algorithm
|
assert algorithm == self.algorithm
|
||||||
|
@ -374,11 +396,13 @@ class UnsaltedSHA1PasswordHasher(BasePasswordHasher):
|
||||||
def salt(self):
|
def salt(self):
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
@password_max_length(MAXIMUM_PASSWORD_LENGTH)
|
||||||
def encode(self, password, salt):
|
def encode(self, password, salt):
|
||||||
assert salt == ''
|
assert salt == ''
|
||||||
hash = hashlib.sha1(force_bytes(password)).hexdigest()
|
hash = hashlib.sha1(force_bytes(password)).hexdigest()
|
||||||
return 'sha1$$%s' % hash
|
return 'sha1$$%s' % hash
|
||||||
|
|
||||||
|
@password_max_length(MAXIMUM_PASSWORD_LENGTH)
|
||||||
def verify(self, password, encoded):
|
def verify(self, password, encoded):
|
||||||
encoded_2 = self.encode(password, '')
|
encoded_2 = self.encode(password, '')
|
||||||
return constant_time_compare(encoded, encoded_2)
|
return constant_time_compare(encoded, encoded_2)
|
||||||
|
@ -408,10 +432,12 @@ class UnsaltedMD5PasswordHasher(BasePasswordHasher):
|
||||||
def salt(self):
|
def salt(self):
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
@password_max_length(MAXIMUM_PASSWORD_LENGTH)
|
||||||
def encode(self, password, salt):
|
def encode(self, password, salt):
|
||||||
assert salt == ''
|
assert salt == ''
|
||||||
return hashlib.md5(force_bytes(password)).hexdigest()
|
return hashlib.md5(force_bytes(password)).hexdigest()
|
||||||
|
|
||||||
|
@password_max_length(MAXIMUM_PASSWORD_LENGTH)
|
||||||
def verify(self, password, encoded):
|
def verify(self, password, encoded):
|
||||||
if len(encoded) == 37 and encoded.startswith('md5$$'):
|
if len(encoded) == 37 and encoded.startswith('md5$$'):
|
||||||
encoded = encoded[5:]
|
encoded = encoded[5:]
|
||||||
|
@ -437,6 +463,7 @@ class CryptPasswordHasher(BasePasswordHasher):
|
||||||
def salt(self):
|
def salt(self):
|
||||||
return get_random_string(2)
|
return get_random_string(2)
|
||||||
|
|
||||||
|
@password_max_length(MAXIMUM_PASSWORD_LENGTH)
|
||||||
def encode(self, password, salt):
|
def encode(self, password, salt):
|
||||||
crypt = self._load_library()
|
crypt = self._load_library()
|
||||||
assert len(salt) == 2
|
assert len(salt) == 2
|
||||||
|
@ -444,6 +471,7 @@ class CryptPasswordHasher(BasePasswordHasher):
|
||||||
# we don't need to store the salt, but Django used to do this
|
# we don't need to store the salt, but Django used to do this
|
||||||
return "%s$%s$%s" % (self.algorithm, '', data)
|
return "%s$%s$%s" % (self.algorithm, '', data)
|
||||||
|
|
||||||
|
@password_max_length(MAXIMUM_PASSWORD_LENGTH)
|
||||||
def verify(self, password, encoded):
|
def verify(self, password, encoded):
|
||||||
crypt = self._load_library()
|
crypt = self._load_library()
|
||||||
algorithm, salt, data = encoded.split('$', 2)
|
algorithm, salt, data = encoded.split('$', 2)
|
||||||
|
@ -458,4 +486,3 @@ class CryptPasswordHasher(BasePasswordHasher):
|
||||||
(_('salt'), salt),
|
(_('salt'), salt),
|
||||||
(_('hash'), mask_hash(data, show=3)),
|
(_('hash'), mask_hash(data, show=3)),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,8 @@ from __future__ import unicode_literals
|
||||||
from django.conf.global_settings import PASSWORD_HASHERS as default_hashers
|
from django.conf.global_settings import PASSWORD_HASHERS as default_hashers
|
||||||
from django.contrib.auth.hashers import (is_password_usable,
|
from django.contrib.auth.hashers import (is_password_usable,
|
||||||
check_password, make_password, PBKDF2PasswordHasher, load_hashers,
|
check_password, make_password, PBKDF2PasswordHasher, load_hashers,
|
||||||
PBKDF2SHA1PasswordHasher, get_hasher, identify_hasher, UNUSABLE_PASSWORD)
|
PBKDF2SHA1PasswordHasher, get_hasher, identify_hasher, UNUSABLE_PASSWORD,
|
||||||
|
MAXIMUM_PASSWORD_LENGTH, password_max_length)
|
||||||
from django.utils import unittest
|
from django.utils import unittest
|
||||||
from django.utils.unittest import skipUnless
|
from django.utils.unittest import skipUnless
|
||||||
|
|
||||||
|
@ -31,6 +32,12 @@ class TestUtilsHashPass(unittest.TestCase):
|
||||||
self.assertTrue(is_password_usable(encoded))
|
self.assertTrue(is_password_usable(encoded))
|
||||||
self.assertTrue(check_password('lètmein', encoded))
|
self.assertTrue(check_password('lètmein', encoded))
|
||||||
self.assertFalse(check_password('lètmeinz', encoded))
|
self.assertFalse(check_password('lètmeinz', encoded))
|
||||||
|
# Long password
|
||||||
|
self.assertRaises(
|
||||||
|
ValueError,
|
||||||
|
make_password,
|
||||||
|
b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
|
||||||
|
)
|
||||||
|
|
||||||
def test_pkbdf2(self):
|
def test_pkbdf2(self):
|
||||||
encoded = make_password('lètmein', 'seasalt', 'pbkdf2_sha256')
|
encoded = make_password('lètmein', 'seasalt', 'pbkdf2_sha256')
|
||||||
|
@ -40,6 +47,14 @@ class TestUtilsHashPass(unittest.TestCase):
|
||||||
self.assertTrue(check_password('lètmein', encoded))
|
self.assertTrue(check_password('lètmein', encoded))
|
||||||
self.assertFalse(check_password('lètmeinz', encoded))
|
self.assertFalse(check_password('lètmeinz', encoded))
|
||||||
self.assertEqual(identify_hasher(encoded).algorithm, "pbkdf2_sha256")
|
self.assertEqual(identify_hasher(encoded).algorithm, "pbkdf2_sha256")
|
||||||
|
# Long password
|
||||||
|
self.assertRaises(
|
||||||
|
ValueError,
|
||||||
|
make_password,
|
||||||
|
b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
|
||||||
|
"seasalt",
|
||||||
|
"pbkdf2_sha256",
|
||||||
|
)
|
||||||
|
|
||||||
def test_sha1(self):
|
def test_sha1(self):
|
||||||
encoded = make_password('lètmein', 'seasalt', 'sha1')
|
encoded = make_password('lètmein', 'seasalt', 'sha1')
|
||||||
|
@ -49,6 +64,14 @@ class TestUtilsHashPass(unittest.TestCase):
|
||||||
self.assertTrue(check_password('lètmein', encoded))
|
self.assertTrue(check_password('lètmein', encoded))
|
||||||
self.assertFalse(check_password('lètmeinz', encoded))
|
self.assertFalse(check_password('lètmeinz', encoded))
|
||||||
self.assertEqual(identify_hasher(encoded).algorithm, "sha1")
|
self.assertEqual(identify_hasher(encoded).algorithm, "sha1")
|
||||||
|
# Long password
|
||||||
|
self.assertRaises(
|
||||||
|
ValueError,
|
||||||
|
make_password,
|
||||||
|
b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
|
||||||
|
"seasalt",
|
||||||
|
"sha1",
|
||||||
|
)
|
||||||
|
|
||||||
def test_md5(self):
|
def test_md5(self):
|
||||||
encoded = make_password('lètmein', 'seasalt', 'md5')
|
encoded = make_password('lètmein', 'seasalt', 'md5')
|
||||||
|
@ -58,6 +81,14 @@ class TestUtilsHashPass(unittest.TestCase):
|
||||||
self.assertTrue(check_password('lètmein', encoded))
|
self.assertTrue(check_password('lètmein', encoded))
|
||||||
self.assertFalse(check_password('lètmeinz', encoded))
|
self.assertFalse(check_password('lètmeinz', encoded))
|
||||||
self.assertEqual(identify_hasher(encoded).algorithm, "md5")
|
self.assertEqual(identify_hasher(encoded).algorithm, "md5")
|
||||||
|
# Long password
|
||||||
|
self.assertRaises(
|
||||||
|
ValueError,
|
||||||
|
make_password,
|
||||||
|
b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
|
||||||
|
"seasalt",
|
||||||
|
"md5",
|
||||||
|
)
|
||||||
|
|
||||||
def test_unsalted_md5(self):
|
def test_unsalted_md5(self):
|
||||||
encoded = make_password('lètmein', '', 'unsalted_md5')
|
encoded = make_password('lètmein', '', 'unsalted_md5')
|
||||||
|
@ -71,6 +102,14 @@ class TestUtilsHashPass(unittest.TestCase):
|
||||||
self.assertTrue(is_password_usable(alt_encoded))
|
self.assertTrue(is_password_usable(alt_encoded))
|
||||||
self.assertTrue(check_password('lètmein', alt_encoded))
|
self.assertTrue(check_password('lètmein', alt_encoded))
|
||||||
self.assertFalse(check_password('lètmeinz', alt_encoded))
|
self.assertFalse(check_password('lètmeinz', alt_encoded))
|
||||||
|
# Long password
|
||||||
|
self.assertRaises(
|
||||||
|
ValueError,
|
||||||
|
make_password,
|
||||||
|
b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
|
||||||
|
"",
|
||||||
|
"unsalted_md5",
|
||||||
|
)
|
||||||
|
|
||||||
def test_unsalted_sha1(self):
|
def test_unsalted_sha1(self):
|
||||||
encoded = make_password('lètmein', '', 'unsalted_sha1')
|
encoded = make_password('lètmein', '', 'unsalted_sha1')
|
||||||
|
@ -82,6 +121,14 @@ class TestUtilsHashPass(unittest.TestCase):
|
||||||
# Raw SHA1 isn't acceptable
|
# Raw SHA1 isn't acceptable
|
||||||
alt_encoded = encoded[6:]
|
alt_encoded = encoded[6:]
|
||||||
self.assertFalse(check_password('lètmein', alt_encoded))
|
self.assertFalse(check_password('lètmein', alt_encoded))
|
||||||
|
# Long password
|
||||||
|
self.assertRaises(
|
||||||
|
ValueError,
|
||||||
|
make_password,
|
||||||
|
b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
|
||||||
|
"",
|
||||||
|
"unslated_sha1",
|
||||||
|
)
|
||||||
|
|
||||||
@skipUnless(crypt, "no crypt module to generate password.")
|
@skipUnless(crypt, "no crypt module to generate password.")
|
||||||
def test_crypt(self):
|
def test_crypt(self):
|
||||||
|
@ -91,6 +138,14 @@ class TestUtilsHashPass(unittest.TestCase):
|
||||||
self.assertTrue(check_password('lètmei', encoded))
|
self.assertTrue(check_password('lètmei', encoded))
|
||||||
self.assertFalse(check_password('lètmeiz', encoded))
|
self.assertFalse(check_password('lètmeiz', encoded))
|
||||||
self.assertEqual(identify_hasher(encoded).algorithm, "crypt")
|
self.assertEqual(identify_hasher(encoded).algorithm, "crypt")
|
||||||
|
# Long password
|
||||||
|
self.assertRaises(
|
||||||
|
ValueError,
|
||||||
|
make_password,
|
||||||
|
b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
|
||||||
|
"seasalt",
|
||||||
|
"crypt",
|
||||||
|
)
|
||||||
|
|
||||||
@skipUnless(bcrypt, "py-bcrypt not installed")
|
@skipUnless(bcrypt, "py-bcrypt not installed")
|
||||||
def test_bcrypt(self):
|
def test_bcrypt(self):
|
||||||
|
@ -100,6 +155,13 @@ class TestUtilsHashPass(unittest.TestCase):
|
||||||
self.assertTrue(check_password('lètmein', encoded))
|
self.assertTrue(check_password('lètmein', encoded))
|
||||||
self.assertFalse(check_password('lètmeinz', encoded))
|
self.assertFalse(check_password('lètmeinz', encoded))
|
||||||
self.assertEqual(identify_hasher(encoded).algorithm, "bcrypt")
|
self.assertEqual(identify_hasher(encoded).algorithm, "bcrypt")
|
||||||
|
# Long password
|
||||||
|
self.assertRaises(
|
||||||
|
ValueError,
|
||||||
|
make_password,
|
||||||
|
b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
|
||||||
|
hasher="bcrypt",
|
||||||
|
)
|
||||||
|
|
||||||
def test_unusable(self):
|
def test_unusable(self):
|
||||||
encoded = make_password(None)
|
encoded = make_password(None)
|
||||||
|
@ -121,6 +183,14 @@ class TestUtilsHashPass(unittest.TestCase):
|
||||||
self.assertFalse(is_password_usable('lètmein_badencoded'))
|
self.assertFalse(is_password_usable('lètmein_badencoded'))
|
||||||
self.assertFalse(is_password_usable(''))
|
self.assertFalse(is_password_usable(''))
|
||||||
|
|
||||||
|
def test_max_password_length_decorator(self):
|
||||||
|
@password_max_length(10)
|
||||||
|
def encode(s, password, salt):
|
||||||
|
return True
|
||||||
|
|
||||||
|
self.assertTrue(encode(None, b"1234", b"1234"))
|
||||||
|
self.assertRaises(ValueError, encode, None, b"1234567890A", b"1234")
|
||||||
|
|
||||||
def test_low_level_pkbdf2(self):
|
def test_low_level_pkbdf2(self):
|
||||||
hasher = PBKDF2PasswordHasher()
|
hasher = PBKDF2PasswordHasher()
|
||||||
encoded = hasher.encode('lètmein', 'seasalt')
|
encoded = hasher.encode('lètmein', 'seasalt')
|
||||||
|
|
Loading…
Reference in New Issue