diff options
Diffstat (limited to 'passlib/tests/test_ext_django.py')
-rw-r--r-- | passlib/tests/test_ext_django.py | 141 |
1 files changed, 107 insertions, 34 deletions
diff --git a/passlib/tests/test_ext_django.py b/passlib/tests/test_ext_django.py index f827a36..8464fa0 100644 --- a/passlib/tests/test_ext_django.py +++ b/passlib/tests/test_ext_django.py @@ -13,7 +13,7 @@ from passlib import apps as _apps, exc, registry from passlib.apps import django10_context, django14_context, django16_context from passlib.context import CryptContext from passlib.ext.django.utils import ( - DJANGO_VERSION, MIN_DJANGO_VERSION, DjangoTranslator, + DJANGO_VERSION, MIN_DJANGO_VERSION, DjangoTranslator, quirks, ) from passlib.utils.compat import iteritems, get_method_function, u from passlib.utils.decor import memoized_property @@ -60,6 +60,11 @@ if has_min_django: from django.apps import apps apps.populate(["django.contrib.contenttypes", "django.contrib.auth"]) +# log a warning if tested w/ newer version. +# NOTE: this is mainly here as place to mark what version it was run against before release. +if DJANGO_VERSION >= (3, 2): + log.info("this release hasn't been tested against Django %r", DJANGO_VERSION) + #============================================================================= # support funcs #============================================================================= @@ -133,28 +138,58 @@ def check_django_hasher_has_backend(name): # work up stock django config #============================================================================= +def _modify_django_config(kwds, sha_rounds=None): + """ + helper to build django CryptContext config matching expected setup for stock django deploy. + :param kwds: + :param sha_rounds: + :return: + """ + # make sure we have dict + if hasattr(kwds, "to_dict"): + # type: CryptContext + kwds = kwds.to_dict() + + # update defaults + kwds.update( + # TODO: push this to passlib.apps django contexts + deprecated="auto", + ) + + # fill in default rounds for current django version, so our sample hashes come back + # unchanged, instead of being upgraded in-place by check_password(). + if sha_rounds is None and has_min_django: + from django.contrib.auth.hashers import PBKDF2PasswordHasher + sha_rounds = PBKDF2PasswordHasher.iterations + + # modify rounds + if sha_rounds: + kwds.update( + django_pbkdf2_sha1__default_rounds=sha_rounds, + django_pbkdf2_sha256__default_rounds=sha_rounds, + ) + + return kwds + +#---------------------------------------------------- # build config dict that matches stock django -# XXX: move these to passlib.apps? -if DJANGO_VERSION >= (1, 11): - stock_config = _apps.django110_context.to_dict() - stock_rounds = 36000 +#---------------------------------------------------- + +# XXX: replace this with code that interrogates default django config directly? +# could then separate out "validation of djangoXX_context objects" +# and "validation that individual hashers match django". +# or maybe add a "get_django_context(django_version)" helper to passlib.apps? +if DJANGO_VERSION >= (2, 1): + stock_config = _modify_django_config(_apps.django21_context) elif DJANGO_VERSION >= (1, 10): - stock_config = _apps.django110_context.to_dict() - stock_rounds = 30000 -elif DJANGO_VERSION >= (1, 9): - stock_config = _apps.django16_context.to_dict() - stock_rounds = 24000 -else: # 1.8 - stock_config = _apps.django16_context.to_dict() - stock_rounds = 20000 - -stock_config.update( - deprecated="auto", - django_pbkdf2_sha1__default_rounds=stock_rounds, - django_pbkdf2_sha256__default_rounds=stock_rounds, -) + stock_config = _modify_django_config(_apps.django110_context) +else: + # assert DJANGO_VERSION >= (1, 8) + stock_config = _modify_django_config(_apps.django16_context) +#---------------------------------------------------- # override sample hashes used in test cases +#---------------------------------------------------- from passlib.hash import django_pbkdf2_sha256 sample_hashes = dict( django_pbkdf2_sha256=("not a password", django_pbkdf2_sha256 @@ -459,34 +494,65 @@ class DjangoBehaviorTest(_ExtensionTest): # User.set_password() - n/a # User.check_password() - returns False - # FIXME: at some point past 1.8, some of these django started handler None differently; - # and/or throwing TypeError. need to investigate when that change occurred; - # update these tests, and maybe passlib.ext.django as well. user = FakeUser() user.password = None - self.assertFalse(user.check_password(PASS1)) - self.assertFalse(user.has_usable_password()) + if quirks.none_causes_check_password_error and not patched: + # django 2.1+ + self.assertRaises(TypeError, user.check_password, PASS1) + else: + self.assertFalse(user.check_password(PASS1)) + + self.assertEqual(user.has_usable_password(), + quirks.empty_is_usable_password) # make_password() - n/a # check_password() - error - self.assertFalse(check_password(PASS1, None)) + if quirks.none_causes_check_password_error and not patched: + self.assertRaises(TypeError, check_password, PASS1, None) + else: + self.assertFalse(check_password(PASS1, None)) # identify_hasher() - error self.assertRaises(TypeError, identify_hasher, None) #======================================================= - # empty & invalid hash values - # NOTE: django 1.5 behavior change due to django ticket 18453 - # NOTE: passlib integration tries to match current django version + # empty hash values + #======================================================= + + # User.set_password() - n/a + + # User.check_password() + # As of django 1.5, blank hash returns False (django issue 18453) + user = FakeUser() + user.password = "" + self.assertFalse(user.check_password(PASS1)) + + # verify hash wasn't changed/upgraded during check_password() call + self.assertEqual(user.password, "") + self.assertEqual(user.pop_saved_passwords(), []) + + # User.has_usable_password() + self.assertEqual(user.has_usable_password(), quirks.empty_is_usable_password) + + # make_password() - n/a + + # check_password() + self.assertFalse(check_password(PASS1, "")) + + # identify_hasher() - throws error + self.assertRaises(ValueError, identify_hasher, "") + + #======================================================= + # invalid hash values #======================================================= - for hash in ("", # empty hash - "$789$foo", # empty identifier - ): + for hash in [ + "$789$foo", # empty identifier + ]: # User.set_password() - n/a # User.check_password() - # As of django 1.5, blank OR invalid hash returns False + # As of django 1.5, invalid hash returns False (side effect of django issue 18453) user = FakeUser() user.password = hash self.assertFalse(user.check_password(PASS1)) @@ -496,7 +562,7 @@ class DjangoBehaviorTest(_ExtensionTest): self.assertEqual(user.pop_saved_passwords(), []) # User.has_usable_password() - self.assertFalse(user.has_usable_password()) + self.assertEqual(user.has_usable_password(), quirks.invalid_is_usable_password) # make_password() - n/a @@ -701,12 +767,15 @@ class DjangoExtensionTest(_ExtensionTest): passlib_to_django = DjangoTranslator().passlib_to_django # should return native django hasher if available - if DJANGO_VERSION > (1,10): + if DJANGO_VERSION > (1, 10): self.assertRaises(ValueError, passlib_to_django, "hex_md5") else: hasher = passlib_to_django("hex_md5") self.assertIsInstance(hasher, hashers.UnsaltedMD5PasswordHasher) + # should return native django hasher + # NOTE: present but not enabled by default in django as of 2.1 + # (see _builtin_django_hashers) hasher = passlib_to_django("django_bcrypt") self.assertIsInstance(hasher, hashers.BCryptPasswordHasher) @@ -732,6 +801,10 @@ class DjangoExtensionTest(_ExtensionTest): 'hash': u('v2RWkZ*************************************'), }) + # made up name should throw error + # XXX: should this throw ValueError instead, to match django? + self.assertRaises(KeyError, passlib_to_django, "does_not_exist") + #=================================================================== # PASSLIB_CONFIG settings #=================================================================== |