def test_config(self): """test hashing interface this function is run against both the actual django code, to verify the assumptions of the unittests are correct; and run against the passlib extension, to verify it matches those assumptions. """ patched, config = self.patched, self.config # this tests the following methods: # User.set_password() # User.check_password() # make_password() -- 1.4 only # check_password() # identify_hasher() # User.has_usable_password() # User.set_unusable_password() # XXX: this take a while to run. what could be trimmed? # TODO: get_hasher() #======================================================= # setup helpers & imports #======================================================= ctx = self.context setter = create_mock_setter() PASS1 = "toomanysecrets" WRONG1 = "letmein" if has_django14: from passlib.ext.django.utils import hasher_to_passlib_name, passlib_to_hasher_name from django.contrib.auth.hashers import check_password, make_password, is_password_usable if patched: from django.contrib.auth.hashers import identify_hasher else: from django.contrib.auth.models import check_password #======================================================= # make sure extension is configured correctly #======================================================= if patched: # contexts should match from passlib.ext.django.models import password_context self.assertEqual(password_context.to_dict(resolve=True), ctx.to_dict(resolve=True)) # should have patched both places if has_django14: from django.contrib.auth.models import check_password as check_password2 self.assertIs(check_password2, check_password) #======================================================= # default algorithm #======================================================= # User.set_password() should use default alg user = FakeUser() user.set_password(PASS1) self.assertTrue(ctx.handler().verify(PASS1, user.password)) self.assert_valid_password(user) # User.check_password() - n/a # make_password() should use default alg if has_django14: hash = make_password(PASS1) self.assertTrue(ctx.handler().verify(PASS1, hash)) # check_password() - n/a #======================================================= # empty password behavior #======================================================= if has_django14: # NOTE: django 1.4 treats empty password as invalid # User.set_password() should set unusable flag user = FakeUser() user.set_password('') self.assert_unusable_password(user) # User.check_password() should never return True user = FakeUser() user.password = hash = ctx.encrypt("") self.assertFalse(user.check_password("")) self.assert_valid_password(user, hash) # make_password() should reject empty passwords self.assertEqual(make_password(""), "!") # check_password() should never return True self.assertFalse(check_password("", hash)) else: # User.set_password() should use default alg user = FakeUser() user.set_password('') hash = user.password self.assertTrue(ctx.handler().verify('', hash)) self.assert_valid_password(user, hash) # User.check_password() should return True self.assertTrue(user.check_password("")) self.assert_valid_password(user, hash) # no make_password() # check_password() should return True self.assertTrue(check_password("", hash)) #======================================================= # 'unusable flag' behavior #======================================================= if has_django1 or patched: # sanity check via user.set_unusable_password() user = FakeUser() user.set_unusable_password() self.assert_unusable_password(user) # ensure User.set_password() sets flag user = FakeUser() user.set_password(None) self.assert_unusable_password(user) # User.check_password() should always fail self.assertFalse(user.check_password(None)) self.assertFalse(user.check_password('')) self.assertFalse(user.check_password(PASS1)) self.assertFalse(user.check_password(WRONG1)) self.assert_unusable_password(user) # make_password() should also set flag if has_django14: self.assertEqual(make_password(None), "!") # check_password() should return False (didn't handle disabled under 1.3) if has_django14 or patched: self.assertFalse(check_password(PASS1, '!')) # identify_hasher() and is_password_usable() should reject it if has_django14: self.assertFalse(is_password_usable(user.password)) if has_django14 and patched: self.assertRaises(ValueError, identify_hasher, user.password) #======================================================= # hash=None #======================================================= # User.set_password() - n/a # User.check_password() - returns False user = FakeUser() user.password = None if has_django14 or patched: self.assertFalse(user.check_password(PASS1)) else: self.assertRaises(TypeError, user.check_password, PASS1) if has_django1 or patched: self.assertFalse(user.has_usable_password()) # make_password() - n/a # check_password() - error if has_django14 or patched: self.assertFalse(check_password(PASS1, None)) else: self.assertRaises(AttributeError, check_password, PASS1, None) # identify_hasher() - error if has_django14 and patched: self.assertRaises(TypeError, identify_hasher, None) #======================================================= # invalid hash values #======================================================= for hash in ("", "$789$foo"): # User.set_password() - n/a # User.check_password() - blank hash causes error user = FakeUser() user.password = hash if has_django14 or patched or hash: self.assertRaises(ValueError, user.check_password, PASS1) else: # django 1.3 returns False for empty hashes self.assertFalse(user.check_password(PASS1)) self.assert_valid_password( user, hash) # '' counts as valid for some reason # make_password() - n/a # check_password() - error self.assertRaises(ValueError, check_password, PASS1, hash) # identify_hasher() - error if has_django14 and patched: self.assertRaises(ValueError, identify_hasher, hash) #======================================================= # run through all the schemes in the context, # testing various bits of per-scheme behavior. #======================================================= for scheme in ctx.schemes(): #------------------------------------------------------- # setup constants & imports, pick a sample secret/hash combo #------------------------------------------------------- handler = ctx.handler(scheme) deprecated = ctx._is_deprecated_scheme(scheme) assert not deprecated or scheme != ctx.default_scheme() try: testcase = get_handler_case(scheme) except exc.MissingBackendError: assert scheme == "bcrypt" continue assert testcase.handler is handler if testcase.is_disabled_handler: continue if not has_active_backend(handler): assert scheme == "django_bcrypt" continue while True: secret, hash = testcase('setUp').get_sample_hash() if secret: # don't select blank passwords, special under django break other = 'letmein' # User.set_password() - n/a #------------------------------------------------------- # User.check_password()+migration against known hash #------------------------------------------------------- user = FakeUser() user.password = hash # check against invalid password if has_django1 or patched: self.assertFalse(user.check_password(None)) else: self.assertRaises(TypeError, user.check_password, None) ##self.assertFalse(user.check_password('')) self.assertFalse(user.check_password(other)) self.assert_valid_password(user, hash) # check against valid password if has_django0 and isinstance(secret, unicode): secret = secret.encode("utf-8") self.assertTrue(user.check_password(secret)) # check if it upgraded the hash needs_update = deprecated if needs_update: self.assertFalse(handler.identify(user.password)) self.assertTrue(ctx.handler().verify(secret, user.password)) self.assert_valid_password(user, saved=user.password) else: self.assert_valid_password(user, hash) # don't need to check rest for most deployments if TEST_MODE(max="default"): continue #------------------------------------------------------- # make_password() correctly selects algorithm #------------------------------------------------------- if has_django14: hash2 = make_password(secret, hasher=passlib_to_hasher_name(scheme)) self.assertTrue(handler.verify(secret, hash2)) #------------------------------------------------------- # check_password()+setter against known hash #------------------------------------------------------- if has_django14 or patched: # should call setter only if it needs_update self.assertTrue(check_password(secret, hash, setter=setter)) self.assertEqual(setter.popstate(), [secret] if needs_update else []) # should not call setter self.assertFalse(check_password(other, hash, setter=setter)) self.assertEqual(setter.popstate(), []) ### check preferred kwd is ignored (django 1.4 feature we don't support) ##self.assertTrue(check_password(secret, hash, setter=setter, preferred='fooey')) ##self.assertEqual(setter.popstate(), [secret]) elif patched or scheme != "hex_md5": # django 1.3 never called check_password() for hex_md5 self.assertTrue(check_password(secret, hash)) self.assertFalse(check_password(other, hash)) # TODO: get_hasher() #------------------------------------------------------- # identify_hasher() recognizes known hash #------------------------------------------------------- if has_django14 and patched: self.assertTrue(is_password_usable(hash)) name = hasher_to_passlib_name(identify_hasher(hash).algorithm) self.assertEqual(name, scheme)
def test_config(self): """test hashing interface this function is run against both the actual django code, to verify the assumptions of the unittests are correct; and run against the passlib extension, to verify it matches those assumptions. """ patched, config = self.patched, self.config # this tests the following methods: # User.set_password() # User.check_password() # make_password() -- 1.4 only # check_password() # identify_hasher() # User.has_usable_password() # User.set_unusable_password() # XXX: this take a while to run. what could be trimmed? # TODO: get_hasher() #======================================================= # setup helpers & imports #======================================================= ctx = self.context setter = create_mock_setter() PASS1 = "toomanysecrets" WRONG1 = "letmein" has_identify_hasher = False from passlib.ext.django.utils import hasher_to_passlib_name, passlib_to_hasher_name from django.contrib.auth.hashers import check_password, make_password, is_password_usable if patched or DJANGO_VERSION >= (1,5): # identify_hasher() # django 1.4 -- not present # django 1.5 -- present (added in django ticket 18184) # passlib integration -- present even under 1.4 from django.contrib.auth.hashers import identify_hasher has_identify_hasher = True #======================================================= # make sure extension is configured correctly #======================================================= if patched: # contexts should match from passlib.ext.django.models import password_context self.assertEqual(password_context.to_dict(resolve=True), ctx.to_dict(resolve=True)) # should have patched both places from django.contrib.auth.models import check_password as check_password2 self.assertIs(check_password2, check_password) #======================================================= # default algorithm #======================================================= # User.set_password() should use default alg user = FakeUser() user.set_password(PASS1) self.assertTrue(ctx.handler().verify(PASS1, user.password)) self.assert_valid_password(user) # User.check_password() - n/a # make_password() should use default alg hash = make_password(PASS1) self.assertTrue(ctx.handler().verify(PASS1, hash)) # check_password() - n/a #======================================================= # empty password behavior #======================================================= if DJANGO_VERSION < (1,6): # NOTE: django 1.4-1.5 treat empty password as invalid # User.set_password() should set unusable flag user = FakeUser() user.set_password('') self.assert_unusable_password(user) # User.check_password() should never return True user = FakeUser() user.password = hash = ctx.encrypt("") self.assertFalse(user.check_password("")) self.assert_valid_password(user, hash) # make_password() should reject empty passwords self.assertEqual(make_password(""), "!") # check_password() should never return True self.assertFalse(check_password("", hash)) else: # User.set_password() should use default alg user = FakeUser() user.set_password('') hash = user.password self.assertTrue(ctx.handler().verify('', hash)) self.assert_valid_password(user, hash) # User.check_password() should return True self.assertTrue(user.check_password("")) self.assert_valid_password(user, hash) # no make_password() # check_password() should return True self.assertTrue(check_password("", hash)) #======================================================= # 'unusable flag' behavior #======================================================= # sanity check via user.set_unusable_password() user = FakeUser() user.set_unusable_password() self.assert_unusable_password(user) # ensure User.set_password() sets unusable flag user = FakeUser() user.set_password(None) self.assert_unusable_password(user) # User.check_password() should always fail self.assertFalse(user.check_password(None)) self.assertFalse(user.check_password('None')) self.assertFalse(user.check_password('')) self.assertFalse(user.check_password(PASS1)) self.assertFalse(user.check_password(WRONG1)) self.assert_unusable_password(user) # make_password() should also set flag if DJANGO_VERSION >= (1,6): self.assertTrue(make_password(None).startswith("!")) else: self.assertEqual(make_password(None), "!") # check_password() should return False (didn't handle disabled under 1.3) self.assertFalse(check_password(PASS1, '!')) # identify_hasher() and is_password_usable() should reject it self.assertFalse(is_password_usable(user.password)) if has_identify_hasher: self.assertRaises(ValueError, identify_hasher, user.password) #======================================================= # hash=None #======================================================= # User.set_password() - n/a # User.check_password() - returns False user = FakeUser() user.password = None self.assertFalse(user.check_password(PASS1)) self.assertFalse(user.has_usable_password()) # make_password() - n/a # check_password() - error self.assertFalse(check_password(PASS1, None)) # identify_hasher() - error if has_identify_hasher: 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 #======================================================= for hash in ("", # empty hash "$789$foo", # empty identifier ): # User.set_password() - n/a # User.check_password() # empty # ----- # django 1.4 -- blank threw error (fixed in 1.5) # django 1.5 -- blank hash returns False # # invalid # ------- # django 1.4 -- invalid hash threw error (fixed in 1.5) # django 1.5 -- invalid hash returns False user = FakeUser() user.password = hash if DJANGO_VERSION >= (1,5): # returns False for hash self.assertFalse(user.check_password(PASS1)) else: # throws error for hash self.assertRaises(ValueError, user.check_password, PASS1) # verify hash wasn't changed/upgraded during check_password() call self.assertEqual(user.password, hash) self.assertEqual(user.pop_saved_passwords(), []) # User.has_usable_password() # django 1.4 -- invalid/empty usable (fixed in 1.5) # django 1.5 -- invalid/empty no longer usable if DJANGO_VERSION < (1,5): self.assertTrue(user.has_usable_password()) else: self.assertFalse(user.has_usable_password()) # make_password() - n/a # check_password() # django 1.4 -- invalid/empty hash threw error (fixed in 1.5) # django 1.5 -- invalid/empty hash now returns False if DJANGO_VERSION < (1,5): self.assertRaises(ValueError, check_password, PASS1, hash) else: self.assertFalse(check_password(PASS1, hash)) # identify_hasher() - throws error if has_identify_hasher: self.assertRaises(ValueError, identify_hasher, hash) #======================================================= # run through all the schemes in the context, # testing various bits of per-scheme behavior. #======================================================= for scheme in ctx.schemes(): #------------------------------------------------------- # setup constants & imports, pick a sample secret/hash combo #------------------------------------------------------- handler = ctx.handler(scheme) deprecated = ctx._is_deprecated_scheme(scheme) assert not deprecated or scheme != ctx.default_scheme() try: testcase = get_handler_case(scheme) except exc.MissingBackendError: assert scheme == "bcrypt" continue assert testcase.handler is handler if testcase.is_disabled_handler: continue if not has_active_backend(handler): # TODO: move this above get_handler_case(), # and omit MissingBackendError check. assert scheme in ["django_bcrypt", "django_bcrypt_sha256"], "%r scheme should always have active backend" % scheme continue try: secret, hash = sample_hashes[scheme] except KeyError: while True: secret, hash = testcase('setUp').get_sample_hash() if secret: # don't select blank passwords, especially under django 1.4/1.5 break other = 'dontletmein' # User.set_password() - n/a #------------------------------------------------------- # User.check_password()+migration against known hash #------------------------------------------------------- user = FakeUser() user.password = hash # check against invalid password self.assertFalse(user.check_password(None)) ##self.assertFalse(user.check_password('')) self.assertFalse(user.check_password(other)) self.assert_valid_password(user, hash) # check against valid password self.assertTrue(user.check_password(secret)) # check if it upgraded the hash # NOTE: needs_update kept separate in case we need to test rounds. needs_update = deprecated if needs_update: self.assertNotEqual(user.password, hash) self.assertFalse(handler.identify(user.password)) self.assertTrue(ctx.handler().verify(secret, user.password)) self.assert_valid_password(user, saved=user.password) else: self.assert_valid_password(user, hash) # don't need to check rest for most deployments if TEST_MODE(max="default"): continue #------------------------------------------------------- # make_password() correctly selects algorithm #------------------------------------------------------- hash2 = make_password(secret, hasher=passlib_to_hasher_name(scheme)) self.assertTrue(handler.verify(secret, hash2)) #------------------------------------------------------- # check_password()+setter against known hash #------------------------------------------------------- # should call setter only if it needs_update self.assertTrue(check_password(secret, hash, setter=setter)) self.assertEqual(setter.popstate(), [secret] if needs_update else []) # should not call setter self.assertFalse(check_password(other, hash, setter=setter)) self.assertEqual(setter.popstate(), []) ### check preferred kwd is ignored (django 1.4 feature we don't support) ##self.assertTrue(check_password(secret, hash, setter=setter, preferred='fooey')) ##self.assertEqual(setter.popstate(), [secret]) # TODO: get_hasher() #------------------------------------------------------- # identify_hasher() recognizes known hash #------------------------------------------------------- if has_identify_hasher: self.assertTrue(is_password_usable(hash)) name = hasher_to_passlib_name(identify_hasher(hash).algorithm) self.assertEqual(name, scheme)
def test_config(self): """test hashing interface this function is run against both the actual django code, to verify the assumptions of the unittests are correct; and run against the passlib extension, to verify it matches those assumptions. """ patched, config = self.patched, self.config # this tests the following methods: # User.set_password() # User.check_password() # make_password() -- 1.4 only # check_password() # identify_hasher() # User.has_usable_password() # User.set_unusable_password() # XXX: this take a while to run. what could be trimmed? # TODO: get_hasher() #======================================================= # setup helpers & imports #======================================================= ctx = self.context setter = create_mock_setter() PASS1 = "toomanysecrets" WRONG1 = "letmein" if has_django14: from passlib.ext.django.utils import hasher_to_passlib_name, passlib_to_hasher_name from django.contrib.auth.hashers import check_password, make_password, is_password_usable if patched: from django.contrib.auth.hashers import identify_hasher else: from django.contrib.auth.models import check_password #======================================================= # make sure extension is configured correctly #======================================================= if patched: # contexts should match from passlib.ext.django.models import password_context self.assertEqual(password_context.to_dict(resolve=True), ctx.to_dict(resolve=True)) # should have patched both places if has_django14: from django.contrib.auth.models import check_password as check_password2 self.assertIs(check_password2, check_password) #======================================================= # default algorithm #======================================================= # User.set_password() should use default alg user = FakeUser() user.set_password(PASS1) self.assertTrue(ctx.handler().verify(PASS1, user.password)) self.assert_valid_password(user) # User.check_password() - n/a # make_password() should use default alg if has_django14: hash = make_password(PASS1) self.assertTrue(ctx.handler().verify(PASS1, hash)) # check_password() - n/a #======================================================= # empty password behavior #======================================================= if has_django14: # NOTE: django 1.4 treats empty password as invalid # User.set_password() should set unusable flag user = FakeUser() user.set_password('') self.assert_unusable_password(user) # User.check_password() should never return True user = FakeUser() user.password = hash = ctx.encrypt("") self.assertFalse(user.check_password("")) self.assert_valid_password(user, hash) # make_password() should reject empty passwords self.assertEqual(make_password(""), "!") # check_password() should never return True self.assertFalse(check_password("", hash)) else: # User.set_password() should use default alg user = FakeUser() user.set_password('') hash = user.password self.assertTrue(ctx.handler().verify('', hash)) self.assert_valid_password(user, hash) # User.check_password() should return True self.assertTrue(user.check_password("")) self.assert_valid_password(user, hash) # no make_password() # check_password() should return True self.assertTrue(check_password("", hash)) #======================================================= # 'unusable flag' behavior #======================================================= if has_django1 or patched: # sanity check via user.set_unusable_password() user = FakeUser() user.set_unusable_password() self.assert_unusable_password(user) # ensure User.set_password() sets flag user = FakeUser() user.set_password(None) self.assert_unusable_password(user) # User.check_password() should always fail self.assertFalse(user.check_password(None)) self.assertFalse(user.check_password('')) self.assertFalse(user.check_password(PASS1)) self.assertFalse(user.check_password(WRONG1)) self.assert_unusable_password(user) # make_password() should also set flag if has_django14: self.assertEqual(make_password(None), "!") # check_password() should return False (didn't handle disabled under 1.3) if has_django14 or patched: self.assertFalse(check_password(PASS1, '!')) # identify_hasher() and is_password_usable() should reject it if has_django14: self.assertFalse(is_password_usable(user.password)) if has_django14 and patched: self.assertRaises(ValueError, identify_hasher, user.password) #======================================================= # hash=None #======================================================= # User.set_password() - n/a # User.check_password() - returns False user = FakeUser() user.password = None if has_django14 or patched: self.assertFalse(user.check_password(PASS1)) else: self.assertRaises(TypeError, user.check_password, PASS1) if has_django1 or patched: self.assertFalse(user.has_usable_password()) # make_password() - n/a # check_password() - error if has_django14 or patched: self.assertFalse(check_password(PASS1, None)) else: self.assertRaises(AttributeError, check_password, PASS1, None) # identify_hasher() - error if has_django14 and patched: self.assertRaises(TypeError, identify_hasher, None) #======================================================= # invalid hash values #======================================================= for hash in ("", "$789$foo"): # User.set_password() - n/a # User.check_password() - blank hash causes error user = FakeUser() user.password = hash if has_django14 or patched or hash: self.assertRaises(ValueError, user.check_password, PASS1) else: # django 1.3 returns False for empty hashes self.assertFalse(user.check_password(PASS1)) self.assert_valid_password(user, hash) # '' counts as valid for some reason # make_password() - n/a # check_password() - error self.assertRaises(ValueError, check_password, PASS1, hash) # identify_hasher() - error if has_django14 and patched: self.assertRaises(ValueError, identify_hasher, hash) #======================================================= # run through all the schemes in the context, # testing various bits of per-scheme behavior. #======================================================= for scheme in ctx.schemes(): #------------------------------------------------------- # setup constants & imports, pick a sample secret/hash combo #------------------------------------------------------- handler = ctx.handler(scheme) deprecated = ctx._is_deprecated_scheme(scheme) assert not deprecated or scheme != ctx.default_scheme() try: testcase = get_handler_case(scheme) except exc.MissingBackendError: assert scheme == "bcrypt" continue assert testcase.handler is handler if testcase.is_disabled_handler: continue if not has_active_backend(handler): assert scheme == "django_bcrypt" continue while True: secret, hash = testcase('setUp').get_sample_hash() if secret: # don't select blank passwords, special under django break other = 'letmein' # User.set_password() - n/a #------------------------------------------------------- # User.check_password()+migration against known hash #------------------------------------------------------- user = FakeUser() user.password = hash # check against invalid password if has_django1 or patched: self.assertFalse(user.check_password(None)) else: self.assertRaises(TypeError, user.check_password, None) ##self.assertFalse(user.check_password('')) self.assertFalse(user.check_password(other)) self.assert_valid_password(user, hash) # check against valid password if has_django0 and isinstance(secret, unicode): secret = secret.encode("utf-8") self.assertTrue(user.check_password(secret)) # check if it upgraded the hash needs_update = deprecated if needs_update: self.assertFalse(handler.identify(user.password)) self.assertTrue(ctx.handler().verify(secret, user.password)) self.assert_valid_password(user, saved=user.password) else: self.assert_valid_password(user, hash) # don't need to check rest for most deployments if TEST_MODE(max="default"): continue #------------------------------------------------------- # make_password() correctly selects algorithm #------------------------------------------------------- if has_django14: hash2 = make_password(secret, hasher=passlib_to_hasher_name(scheme)) self.assertTrue(handler.verify(secret, hash2)) #------------------------------------------------------- # check_password()+setter against known hash #------------------------------------------------------- if has_django14 or patched: # should call setter only if it needs_update self.assertTrue(check_password(secret, hash, setter=setter)) self.assertEqual(setter.popstate(), [secret] if needs_update else []) # should not call setter self.assertFalse(check_password(other, hash, setter=setter)) self.assertEqual(setter.popstate(), []) ### check preferred kwd is ignored (django 1.4 feature we don't support) ##self.assertTrue(check_password(secret, hash, setter=setter, preferred='fooey')) ##self.assertEqual(setter.popstate(), [secret]) elif patched or scheme != "hex_md5": # django 1.3 never called check_password() for hex_md5 self.assertTrue(check_password(secret, hash)) self.assertFalse(check_password(other, hash)) # TODO: get_hasher() #------------------------------------------------------- # identify_hasher() recognizes known hash #------------------------------------------------------- if has_django14 and patched: self.assertTrue(is_password_usable(hash)) name = hasher_to_passlib_name(identify_hasher(hash).algorithm) self.assertEqual(name, scheme)