diff --git a/venv/Lib/site-packages/passlib/tests/test_utils_handlers.py b/venv/Lib/site-packages/passlib/tests/test_utils_handlers.py new file mode 100644 index 0000000..19cd4ca --- /dev/null +++ b/venv/Lib/site-packages/passlib/tests/test_utils_handlers.py @@ -0,0 +1,870 @@ +"""tests for passlib.hash -- (c) Assurance Technologies 2003-2009""" +#============================================================================= +# imports +#============================================================================= +from __future__ import with_statement +# core +import re +import hashlib +from logging import getLogger +import warnings +# site +# pkg +from passlib.hash import ldap_md5, sha256_crypt +from passlib.exc import MissingBackendError, PasslibHashWarning +from passlib.utils.compat import str_to_uascii, \ + uascii_to_str, unicode +import passlib.utils.handlers as uh +from passlib.tests.utils import HandlerCase, TestCase +from passlib.utils.compat import u +# module +log = getLogger(__name__) + +#============================================================================= +# utils +#============================================================================= +def _makelang(alphabet, size): + """generate all strings of given size using alphabet""" + def helper(size): + if size < 2: + for char in alphabet: + yield char + else: + for char in alphabet: + for tail in helper(size-1): + yield char+tail + return set(helper(size)) + +#============================================================================= +# test GenericHandler & associates mixin classes +#============================================================================= +class SkeletonTest(TestCase): + """test hash support classes""" + + #=================================================================== + # StaticHandler + #=================================================================== + def test_00_static_handler(self): + """test StaticHandler class""" + + class d1(uh.StaticHandler): + name = "d1" + context_kwds = ("flag",) + _hash_prefix = u("_") + checksum_chars = u("ab") + checksum_size = 1 + + def __init__(self, flag=False, **kwds): + super(d1, self).__init__(**kwds) + self.flag = flag + + def _calc_checksum(self, secret): + return u('b') if self.flag else u('a') + + # check default identify method + self.assertTrue(d1.identify(u('_a'))) + self.assertTrue(d1.identify(b'_a')) + self.assertTrue(d1.identify(u('_b'))) + + self.assertFalse(d1.identify(u('_c'))) + self.assertFalse(d1.identify(b'_c')) + self.assertFalse(d1.identify(u('a'))) + self.assertFalse(d1.identify(u('b'))) + self.assertFalse(d1.identify(u('c'))) + self.assertRaises(TypeError, d1.identify, None) + self.assertRaises(TypeError, d1.identify, 1) + + # check default genconfig method + self.assertEqual(d1.genconfig(), d1.hash("")) + + # check default verify method + self.assertTrue(d1.verify('s', b'_a')) + self.assertTrue(d1.verify('s',u('_a'))) + self.assertFalse(d1.verify('s', b'_b')) + self.assertFalse(d1.verify('s',u('_b'))) + self.assertTrue(d1.verify('s', b'_b', flag=True)) + self.assertRaises(ValueError, d1.verify, 's', b'_c') + self.assertRaises(ValueError, d1.verify, 's', u('_c')) + + # check default hash method + self.assertEqual(d1.hash('s'), '_a') + self.assertEqual(d1.hash('s', flag=True), '_b') + + def test_01_calc_checksum_hack(self): + """test StaticHandler legacy attr""" + # release 1.5 StaticHandler required genhash(), + # not _calc_checksum, be implemented. we have backward compat wrapper, + # this tests that it works. + + class d1(uh.StaticHandler): + name = "d1" + + @classmethod + def identify(cls, hash): + if not hash or len(hash) != 40: + return False + try: + int(hash, 16) + except ValueError: + return False + return True + + @classmethod + def genhash(cls, secret, hash): + if secret is None: + raise TypeError("no secret provided") + if isinstance(secret, unicode): + secret = secret.encode("utf-8") + # NOTE: have to support hash=None since this is test of legacy 1.5 api + if hash is not None and not cls.identify(hash): + raise ValueError("invalid hash") + return hashlib.sha1(b"xyz" + secret).hexdigest() + + @classmethod + def verify(cls, secret, hash): + if hash is None: + raise ValueError("no hash specified") + return cls.genhash(secret, hash) == hash.lower() + + # hash should issue api warnings, but everything else should be fine. + with self.assertWarningList("d1.*should be updated.*_calc_checksum"): + hash = d1.hash("test") + self.assertEqual(hash, '7c622762588a0e5cc786ad0a143156f9fd38eea3') + + self.assertTrue(d1.verify("test", hash)) + self.assertFalse(d1.verify("xtest", hash)) + + # not defining genhash either, however, should cause NotImplementedError + del d1.genhash + self.assertRaises(NotImplementedError, d1.hash, 'test') + + #=================================================================== + # GenericHandler & mixins + #=================================================================== + def test_10_identify(self): + """test GenericHandler.identify()""" + class d1(uh.GenericHandler): + @classmethod + def from_string(cls, hash): + if isinstance(hash, bytes): + hash = hash.decode("ascii") + if hash == u('a'): + return cls(checksum=hash) + else: + raise ValueError + + # check fallback + self.assertRaises(TypeError, d1.identify, None) + self.assertRaises(TypeError, d1.identify, 1) + self.assertFalse(d1.identify('')) + self.assertTrue(d1.identify('a')) + self.assertFalse(d1.identify('b')) + + # check regexp + d1._hash_regex = re.compile(u('@.')) + self.assertRaises(TypeError, d1.identify, None) + self.assertRaises(TypeError, d1.identify, 1) + self.assertTrue(d1.identify('@a')) + self.assertFalse(d1.identify('a')) + del d1._hash_regex + + # check ident-based + d1.ident = u('!') + self.assertRaises(TypeError, d1.identify, None) + self.assertRaises(TypeError, d1.identify, 1) + self.assertTrue(d1.identify('!a')) + self.assertFalse(d1.identify('a')) + del d1.ident + + def test_11_norm_checksum(self): + """test GenericHandler checksum handling""" + # setup helpers + class d1(uh.GenericHandler): + name = 'd1' + checksum_size = 4 + checksum_chars = u('xz') + + def norm_checksum(checksum=None, **k): + return d1(checksum=checksum, **k).checksum + + # too small + self.assertRaises(ValueError, norm_checksum, u('xxx')) + + # right size + self.assertEqual(norm_checksum(u('xxxx')), u('xxxx')) + self.assertEqual(norm_checksum(u('xzxz')), u('xzxz')) + + # too large + self.assertRaises(ValueError, norm_checksum, u('xxxxx')) + + # wrong chars + self.assertRaises(ValueError, norm_checksum, u('xxyx')) + + # wrong type + self.assertRaises(TypeError, norm_checksum, b'xxyx') + + # relaxed + # NOTE: this could be turned back on if we test _norm_checksum() directly... + #with self.assertWarningList("checksum should be unicode"): + # self.assertEqual(norm_checksum(b'xxzx', relaxed=True), u('xxzx')) + #self.assertRaises(TypeError, norm_checksum, 1, relaxed=True) + + # test _stub_checksum behavior + self.assertEqual(d1()._stub_checksum, u('xxxx')) + + def test_12_norm_checksum_raw(self): + """test GenericHandler + HasRawChecksum mixin""" + class d1(uh.HasRawChecksum, uh.GenericHandler): + name = 'd1' + checksum_size = 4 + + def norm_checksum(*a, **k): + return d1(*a, **k).checksum + + # test bytes + self.assertEqual(norm_checksum(b'1234'), b'1234') + + # test unicode + self.assertRaises(TypeError, norm_checksum, u('xxyx')) + + # NOTE: this could be turned back on if we test _norm_checksum() directly... + # self.assertRaises(TypeError, norm_checksum, u('xxyx'), relaxed=True) + + # test _stub_checksum behavior + self.assertEqual(d1()._stub_checksum, b'\x00'*4) + + def test_20_norm_salt(self): + """test GenericHandler + HasSalt mixin""" + # setup helpers + class d1(uh.HasSalt, uh.GenericHandler): + name = 'd1' + setting_kwds = ('salt',) + min_salt_size = 2 + max_salt_size = 4 + default_salt_size = 3 + salt_chars = 'ab' + + def norm_salt(**k): + return d1(**k).salt + + def gen_salt(sz, **k): + return d1.using(salt_size=sz, **k)(use_defaults=True).salt + + salts2 = _makelang('ab', 2) + salts3 = _makelang('ab', 3) + salts4 = _makelang('ab', 4) + + # check salt=None + self.assertRaises(TypeError, norm_salt) + self.assertRaises(TypeError, norm_salt, salt=None) + self.assertIn(norm_salt(use_defaults=True), salts3) + + # check explicit salts + with warnings.catch_warnings(record=True) as wlog: + + # check too-small salts + self.assertRaises(ValueError, norm_salt, salt='') + self.assertRaises(ValueError, norm_salt, salt='a') + self.consumeWarningList(wlog) + + # check correct salts + self.assertEqual(norm_salt(salt='ab'), 'ab') + self.assertEqual(norm_salt(salt='aba'), 'aba') + self.assertEqual(norm_salt(salt='abba'), 'abba') + self.consumeWarningList(wlog) + + # check too-large salts + self.assertRaises(ValueError, norm_salt, salt='aaaabb') + self.consumeWarningList(wlog) + + # check generated salts + with warnings.catch_warnings(record=True) as wlog: + + # check too-small salt size + self.assertRaises(ValueError, gen_salt, 0) + self.assertRaises(ValueError, gen_salt, 1) + self.consumeWarningList(wlog) + + # check correct salt size + self.assertIn(gen_salt(2), salts2) + self.assertIn(gen_salt(3), salts3) + self.assertIn(gen_salt(4), salts4) + self.consumeWarningList(wlog) + + # check too-large salt size + self.assertRaises(ValueError, gen_salt, 5) + self.consumeWarningList(wlog) + + self.assertIn(gen_salt(5, relaxed=True), salts4) + self.consumeWarningList(wlog, ["salt_size.*above max_salt_size"]) + + # test with max_salt_size=None + del d1.max_salt_size + with self.assertWarningList([]): + self.assertEqual(len(gen_salt(None)), 3) + self.assertEqual(len(gen_salt(5)), 5) + + # TODO: test HasRawSalt mixin + + def test_30_init_rounds(self): + """test GenericHandler + HasRounds mixin""" + # setup helpers + class d1(uh.HasRounds, uh.GenericHandler): + name = 'd1' + setting_kwds = ('rounds',) + min_rounds = 1 + max_rounds = 3 + default_rounds = 2 + + # NOTE: really is testing _init_rounds(), could dup to test _norm_rounds() via .replace + def norm_rounds(**k): + return d1(**k).rounds + + # check rounds=None + self.assertRaises(TypeError, norm_rounds) + self.assertRaises(TypeError, norm_rounds, rounds=None) + self.assertEqual(norm_rounds(use_defaults=True), 2) + + # check rounds=non int + self.assertRaises(TypeError, norm_rounds, rounds=1.5) + + # check explicit rounds + with warnings.catch_warnings(record=True) as wlog: + # too small + self.assertRaises(ValueError, norm_rounds, rounds=0) + self.consumeWarningList(wlog) + + # just right + self.assertEqual(norm_rounds(rounds=1), 1) + self.assertEqual(norm_rounds(rounds=2), 2) + self.assertEqual(norm_rounds(rounds=3), 3) + self.consumeWarningList(wlog) + + # too large + self.assertRaises(ValueError, norm_rounds, rounds=4) + self.consumeWarningList(wlog) + + # check no default rounds + d1.default_rounds = None + self.assertRaises(TypeError, norm_rounds, use_defaults=True) + + def test_40_backends(self): + """test GenericHandler + HasManyBackends mixin""" + class d1(uh.HasManyBackends, uh.GenericHandler): + name = 'd1' + setting_kwds = () + + backends = ("a", "b") + + _enable_a = False + _enable_b = False + + @classmethod + def _load_backend_a(cls): + if cls._enable_a: + cls._set_calc_checksum_backend(cls._calc_checksum_a) + return True + else: + return False + + @classmethod + def _load_backend_b(cls): + if cls._enable_b: + cls._set_calc_checksum_backend(cls._calc_checksum_b) + return True + else: + return False + + def _calc_checksum_a(self, secret): + return 'a' + + def _calc_checksum_b(self, secret): + return 'b' + + # test no backends + self.assertRaises(MissingBackendError, d1.get_backend) + self.assertRaises(MissingBackendError, d1.set_backend) + self.assertRaises(MissingBackendError, d1.set_backend, 'any') + self.assertRaises(MissingBackendError, d1.set_backend, 'default') + self.assertFalse(d1.has_backend()) + + # enable 'b' backend + d1._enable_b = True + + # test lazy load + obj = d1() + self.assertEqual(obj._calc_checksum('s'), 'b') + + # test repeat load + d1.set_backend('b') + d1.set_backend('any') + self.assertEqual(obj._calc_checksum('s'), 'b') + + # test unavailable + self.assertRaises(MissingBackendError, d1.set_backend, 'a') + self.assertTrue(d1.has_backend('b')) + self.assertFalse(d1.has_backend('a')) + + # enable 'a' backend also + d1._enable_a = True + + # test explicit + self.assertTrue(d1.has_backend()) + d1.set_backend('a') + self.assertEqual(obj._calc_checksum('s'), 'a') + + # test unknown backend + self.assertRaises(ValueError, d1.set_backend, 'c') + self.assertRaises(ValueError, d1.has_backend, 'c') + + # test error thrown if _has & _load are mixed + d1.set_backend("b") # switch away from 'a' so next call actually checks loader + class d2(d1): + _has_backend_a = True + self.assertRaises(AssertionError, d2.has_backend, "a") + + def test_41_backends(self): + """test GenericHandler + HasManyBackends mixin (deprecated api)""" + warnings.filterwarnings("ignore", + category=DeprecationWarning, + message=r".* support for \._has_backend_.* is deprecated.*", + ) + + class d1(uh.HasManyBackends, uh.GenericHandler): + name = 'd1' + setting_kwds = () + + backends = ("a", "b") + + _has_backend_a = False + _has_backend_b = False + + def _calc_checksum_a(self, secret): + return 'a' + + def _calc_checksum_b(self, secret): + return 'b' + + # test no backends + self.assertRaises(MissingBackendError, d1.get_backend) + self.assertRaises(MissingBackendError, d1.set_backend) + self.assertRaises(MissingBackendError, d1.set_backend, 'any') + self.assertRaises(MissingBackendError, d1.set_backend, 'default') + self.assertFalse(d1.has_backend()) + + # enable 'b' backend + d1._has_backend_b = True + + # test lazy load + obj = d1() + self.assertEqual(obj._calc_checksum('s'), 'b') + + # test repeat load + d1.set_backend('b') + d1.set_backend('any') + self.assertEqual(obj._calc_checksum('s'), 'b') + + # test unavailable + self.assertRaises(MissingBackendError, d1.set_backend, 'a') + self.assertTrue(d1.has_backend('b')) + self.assertFalse(d1.has_backend('a')) + + # enable 'a' backend also + d1._has_backend_a = True + + # test explicit + self.assertTrue(d1.has_backend()) + d1.set_backend('a') + self.assertEqual(obj._calc_checksum('s'), 'a') + + # test unknown backend + self.assertRaises(ValueError, d1.set_backend, 'c') + self.assertRaises(ValueError, d1.has_backend, 'c') + + def test_50_norm_ident(self): + """test GenericHandler + HasManyIdents""" + # setup helpers + class d1(uh.HasManyIdents, uh.GenericHandler): + name = 'd1' + setting_kwds = ('ident',) + default_ident = u("!A") + ident_values = (u("!A"), u("!B")) + ident_aliases = { u("A"): u("!A")} + + def norm_ident(**k): + return d1(**k).ident + + # check ident=None + self.assertRaises(TypeError, norm_ident) + self.assertRaises(TypeError, norm_ident, ident=None) + self.assertEqual(norm_ident(use_defaults=True), u('!A')) + + # check valid idents + self.assertEqual(norm_ident(ident=u('!A')), u('!A')) + self.assertEqual(norm_ident(ident=u('!B')), u('!B')) + self.assertRaises(ValueError, norm_ident, ident=u('!C')) + + # check aliases + self.assertEqual(norm_ident(ident=u('A')), u('!A')) + + # check invalid idents + self.assertRaises(ValueError, norm_ident, ident=u('B')) + + # check identify is honoring ident system + self.assertTrue(d1.identify(u("!Axxx"))) + self.assertTrue(d1.identify(u("!Bxxx"))) + self.assertFalse(d1.identify(u("!Cxxx"))) + self.assertFalse(d1.identify(u("A"))) + self.assertFalse(d1.identify(u(""))) + self.assertRaises(TypeError, d1.identify, None) + self.assertRaises(TypeError, d1.identify, 1) + + # check default_ident missing is detected. + d1.default_ident = None + self.assertRaises(AssertionError, norm_ident, use_defaults=True) + + #=================================================================== + # experimental - the following methods are not finished or tested, + # but way work correctly for some hashes + #=================================================================== + def test_91_parsehash(self): + """test parsehash()""" + # NOTE: this just tests some existing GenericHandler classes + from passlib import hash + + # + # parsehash() + # + + # simple hash w/ salt + result = hash.des_crypt.parsehash("OgAwTx2l6NADI") + self.assertEqual(result, {'checksum': u('AwTx2l6NADI'), 'salt': u('Og')}) + + # parse rounds and extra implicit_rounds flag + h = '$5$LKO/Ute40T3FNF95$U0prpBQd4PloSGU0pnpM4z9wKn4vZ1.jsrzQfPqxph9' + s = u('LKO/Ute40T3FNF95') + c = u('U0prpBQd4PloSGU0pnpM4z9wKn4vZ1.jsrzQfPqxph9') + result = hash.sha256_crypt.parsehash(h) + self.assertEqual(result, dict(salt=s, rounds=5000, + implicit_rounds=True, checksum=c)) + + # omit checksum + result = hash.sha256_crypt.parsehash(h, checksum=False) + self.assertEqual(result, dict(salt=s, rounds=5000, implicit_rounds=True)) + + # sanitize + result = hash.sha256_crypt.parsehash(h, sanitize=True) + self.assertEqual(result, dict(rounds=5000, implicit_rounds=True, + salt=u('LK**************'), + checksum=u('U0pr***************************************'))) + + # parse w/o implicit rounds flag + result = hash.sha256_crypt.parsehash('$5$rounds=10428$uy/jIAhCetNCTtb0$YWvUOXbkqlqhyoPMpN8BMe.ZGsGx2aBvxTvDFI613c3') + self.assertEqual(result, dict( + checksum=u('YWvUOXbkqlqhyoPMpN8BMe.ZGsGx2aBvxTvDFI613c3'), + salt=u('uy/jIAhCetNCTtb0'), + rounds=10428, + )) + + # parsing of raw checksums & salts + h1 = '$pbkdf2$60000$DoEwpvQeA8B4T.k951yLUQ$O26Y3/NJEiLCVaOVPxGXshyjW8k' + result = hash.pbkdf2_sha1.parsehash(h1) + self.assertEqual(result, dict( + checksum=b';n\x98\xdf\xf3I\x12"\xc2U\xa3\x95?\x11\x97\xb2\x1c\xa3[\xc9', + rounds=60000, + salt=b'\x0e\x810\xa6\xf4\x1e\x03\xc0xO\xe9=\xe7\\\x8bQ', + )) + + # sanitizing of raw checksums & salts + result = hash.pbkdf2_sha1.parsehash(h1, sanitize=True) + self.assertEqual(result, dict( + checksum=u('O26************************'), + rounds=60000, + salt=u('Do********************'), + )) + + def test_92_bitsize(self): + """test bitsize()""" + # NOTE: this just tests some existing GenericHandler classes + from passlib import hash + + # no rounds + self.assertEqual(hash.des_crypt.bitsize(), + {'checksum': 66, 'salt': 12}) + + # log2 rounds + self.assertEqual(hash.bcrypt.bitsize(), + {'checksum': 186, 'salt': 132}) + + # linear rounds + # NOTE: +3 comes from int(math.log(.1,2)), + # where 0.1 = 10% = default allowed variation in rounds + self.patchAttr(hash.sha256_crypt, "default_rounds", 1 << (14 + 3)) + self.assertEqual(hash.sha256_crypt.bitsize(), + {'checksum': 258, 'rounds': 14, 'salt': 96}) + + # raw checksum + self.patchAttr(hash.pbkdf2_sha1, "default_rounds", 1 << (13 + 3)) + self.assertEqual(hash.pbkdf2_sha1.bitsize(), + {'checksum': 160, 'rounds': 13, 'salt': 128}) + + # TODO: handle fshp correctly, and other glitches noted in code. + ##self.assertEqual(hash.fshp.bitsize(variant=1), + ## {'checksum': 256, 'rounds': 13, 'salt': 128}) + + #=================================================================== + # eoc + #=================================================================== + +#============================================================================= +# PrefixWrapper +#============================================================================= +class dummy_handler_in_registry(object): + """context manager that inserts dummy handler in registry""" + def __init__(self, name): + self.name = name + self.dummy = type('dummy_' + name, (uh.GenericHandler,), dict( + name=name, + setting_kwds=(), + )) + + def __enter__(self): + from passlib import registry + registry._unload_handler_name(self.name, locations=False) + registry.register_crypt_handler(self.dummy) + assert registry.get_crypt_handler(self.name) is self.dummy + return self.dummy + + def __exit__(self, *exc_info): + from passlib import registry + registry._unload_handler_name(self.name, locations=False) + +class PrefixWrapperTest(TestCase): + """test PrefixWrapper class""" + + def test_00_lazy_loading(self): + """test PrefixWrapper lazy loading of handler""" + d1 = uh.PrefixWrapper("d1", "ldap_md5", "{XXX}", "{MD5}", lazy=True) + + # check base state + self.assertEqual(d1._wrapped_name, "ldap_md5") + self.assertIs(d1._wrapped_handler, None) + + # check loading works + self.assertIs(d1.wrapped, ldap_md5) + self.assertIs(d1._wrapped_handler, ldap_md5) + + # replace w/ wrong handler, make sure doesn't reload w/ dummy + with dummy_handler_in_registry("ldap_md5") as dummy: + self.assertIs(d1.wrapped, ldap_md5) + + def test_01_active_loading(self): + """test PrefixWrapper active loading of handler""" + d1 = uh.PrefixWrapper("d1", "ldap_md5", "{XXX}", "{MD5}") + + # check base state + self.assertEqual(d1._wrapped_name, "ldap_md5") + self.assertIs(d1._wrapped_handler, ldap_md5) + self.assertIs(d1.wrapped, ldap_md5) + + # replace w/ wrong handler, make sure doesn't reload w/ dummy + with dummy_handler_in_registry("ldap_md5") as dummy: + self.assertIs(d1.wrapped, ldap_md5) + + def test_02_explicit(self): + """test PrefixWrapper with explicitly specified handler""" + + d1 = uh.PrefixWrapper("d1", ldap_md5, "{XXX}", "{MD5}") + + # check base state + self.assertEqual(d1._wrapped_name, None) + self.assertIs(d1._wrapped_handler, ldap_md5) + self.assertIs(d1.wrapped, ldap_md5) + + # replace w/ wrong handler, make sure doesn't reload w/ dummy + with dummy_handler_in_registry("ldap_md5") as dummy: + self.assertIs(d1.wrapped, ldap_md5) + + def test_10_wrapped_attributes(self): + d1 = uh.PrefixWrapper("d1", "ldap_md5", "{XXX}", "{MD5}") + self.assertEqual(d1.name, "d1") + self.assertIs(d1.setting_kwds, ldap_md5.setting_kwds) + self.assertFalse('max_rounds' in dir(d1)) + + d2 = uh.PrefixWrapper("d2", "sha256_crypt", "{XXX}") + self.assertIs(d2.setting_kwds, sha256_crypt.setting_kwds) + self.assertTrue('max_rounds' in dir(d2)) + + def test_11_wrapped_methods(self): + d1 = uh.PrefixWrapper("d1", "ldap_md5", "{XXX}", "{MD5}") + dph = "{XXX}X03MO1qnZdYdgyfeuILPmQ==" + lph = "{MD5}X03MO1qnZdYdgyfeuILPmQ==" + + # genconfig + self.assertEqual(d1.genconfig(), '{XXX}1B2M2Y8AsgTpgAmY7PhCfg==') + + # genhash + self.assertRaises(TypeError, d1.genhash, "password", None) + self.assertEqual(d1.genhash("password", dph), dph) + self.assertRaises(ValueError, d1.genhash, "password", lph) + + # hash + self.assertEqual(d1.hash("password"), dph) + + # identify + self.assertTrue(d1.identify(dph)) + self.assertFalse(d1.identify(lph)) + + # verify + self.assertRaises(ValueError, d1.verify, "password", lph) + self.assertTrue(d1.verify("password", dph)) + + def test_12_ident(self): + # test ident is proxied + h = uh.PrefixWrapper("h2", "ldap_md5", "{XXX}") + self.assertEqual(h.ident, u("{XXX}{MD5}")) + self.assertIs(h.ident_values, None) + + # test lack of ident means no proxy + h = uh.PrefixWrapper("h2", "des_crypt", "{XXX}") + self.assertIs(h.ident, None) + self.assertIs(h.ident_values, None) + + # test orig_prefix disabled ident proxy + h = uh.PrefixWrapper("h1", "ldap_md5", "{XXX}", "{MD5}") + self.assertIs(h.ident, None) + self.assertIs(h.ident_values, None) + + # test custom ident overrides default + h = uh.PrefixWrapper("h3", "ldap_md5", "{XXX}", ident="{X") + self.assertEqual(h.ident, u("{X")) + self.assertIs(h.ident_values, None) + + # test custom ident must match + h = uh.PrefixWrapper("h3", "ldap_md5", "{XXX}", ident="{XXX}A") + self.assertRaises(ValueError, uh.PrefixWrapper, "h3", "ldap_md5", + "{XXX}", ident="{XY") + self.assertRaises(ValueError, uh.PrefixWrapper, "h3", "ldap_md5", + "{XXX}", ident="{XXXX") + + # test ident_values is proxied + h = uh.PrefixWrapper("h4", "phpass", "{XXX}") + self.assertIs(h.ident, None) + self.assertEqual(h.ident_values, (u("{XXX}$P$"), u("{XXX}$H$"))) + + # test ident=True means use prefix even if hash has no ident. + h = uh.PrefixWrapper("h5", "des_crypt", "{XXX}", ident=True) + self.assertEqual(h.ident, u("{XXX}")) + self.assertIs(h.ident_values, None) + + # ... but requires prefix + self.assertRaises(ValueError, uh.PrefixWrapper, "h6", "des_crypt", ident=True) + + # orig_prefix + HasManyIdent - warning + with self.assertWarningList("orig_prefix.*may not work correctly"): + h = uh.PrefixWrapper("h7", "phpass", orig_prefix="$", prefix="?") + self.assertEqual(h.ident_values, None) # TODO: should output (u("?P$"), u("?H$"))) + self.assertEqual(h.ident, None) + + def test_13_repr(self): + """test repr()""" + h = uh.PrefixWrapper("h2", "md5_crypt", "{XXX}", orig_prefix="$1$") + self.assertRegex(repr(h), + r"""(?x)^PrefixWrapper\( + ['"]h2['"],\s+ + ['"]md5_crypt['"],\s+ + prefix=u?["']{XXX}['"],\s+ + orig_prefix=u?["']\$1\$['"] + \)$""") + + def test_14_bad_hash(self): + """test orig_prefix sanity check""" + # shoudl throw InvalidHashError if wrapped hash doesn't begin + # with orig_prefix. + h = uh.PrefixWrapper("h2", "md5_crypt", orig_prefix="$6$") + self.assertRaises(ValueError, h.hash, 'test') + +#============================================================================= +# sample algorithms - these serve as known quantities +# to test the unittests themselves, as well as other +# parts of passlib. they shouldn't be used as actual password schemes. +#============================================================================= +class UnsaltedHash(uh.StaticHandler): + """test algorithm which lacks a salt""" + name = "unsalted_test_hash" + checksum_chars = uh.LOWER_HEX_CHARS + checksum_size = 40 + + def _calc_checksum(self, secret): + if isinstance(secret, unicode): + secret = secret.encode("utf-8") + data = b"boblious" + secret + return str_to_uascii(hashlib.sha1(data).hexdigest()) + +class SaltedHash(uh.HasSalt, uh.GenericHandler): + """test algorithm with a salt""" + name = "salted_test_hash" + setting_kwds = ("salt",) + + min_salt_size = 2 + max_salt_size = 4 + checksum_size = 40 + salt_chars = checksum_chars = uh.LOWER_HEX_CHARS + + _hash_regex = re.compile(u("^@salt[0-9a-f]{42,44}$")) + + @classmethod + def from_string(cls, hash): + if not cls.identify(hash): + raise uh.exc.InvalidHashError(cls) + if isinstance(hash, bytes): + hash = hash.decode("ascii") + return cls(salt=hash[5:-40], checksum=hash[-40:]) + + def to_string(self): + hash = u("@salt%s%s") % (self.salt, self.checksum) + return uascii_to_str(hash) + + def _calc_checksum(self, secret): + if isinstance(secret, unicode): + secret = secret.encode("utf-8") + data = self.salt.encode("ascii") + secret + self.salt.encode("ascii") + return str_to_uascii(hashlib.sha1(data).hexdigest()) + +#============================================================================= +# test sample algorithms - really a self-test of HandlerCase +#============================================================================= + +# TODO: provide data samples for algorithms +# (positive knowns, negative knowns, invalid identify) + +UPASS_TEMP = u('\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2') + +class UnsaltedHashTest(HandlerCase): + handler = UnsaltedHash + + known_correct_hashes = [ + ("password", "61cfd32684c47de231f1f982c214e884133762c0"), + (UPASS_TEMP, '96b329d120b97ff81ada770042e44ba87343ad2b'), + ] + + def test_bad_kwds(self): + self.assertRaises(TypeError, UnsaltedHash, salt='x') + self.assertRaises(TypeError, UnsaltedHash.genconfig, rounds=1) + +class SaltedHashTest(HandlerCase): + handler = SaltedHash + + known_correct_hashes = [ + ("password", '@salt77d71f8fe74f314dac946766c1ac4a2a58365482c0'), + (UPASS_TEMP, '@salt9f978a9bfe360d069b0c13f2afecd570447407fa7e48'), + ] + + def test_bad_kwds(self): + stub = SaltedHash(use_defaults=True)._stub_checksum + self.assertRaises(TypeError, SaltedHash, checksum=stub, salt=None) + self.assertRaises(ValueError, SaltedHash, checksum=stub, salt='xxx') + +#============================================================================= +# eof +#============================================================================= diff --git a/venv/Lib/site-packages/passlib/tests/test_utils_md4.py b/venv/Lib/site-packages/passlib/tests/test_utils_md4.py new file mode 100644 index 0000000..5d824a1 --- /dev/null +++ b/venv/Lib/site-packages/passlib/tests/test_utils_md4.py @@ -0,0 +1,41 @@ +""" +passlib.tests -- tests for passlib.utils.md4 + +.. warning:: + + This module & it's functions have been deprecated, and superceded + by the functions in passlib.crypto. This file is being maintained + until the deprecated functions are removed, and is only present prevent + historical regressions up to that point. New and more thorough testing + is being done by the replacement tests in ``test_utils_crypto_builtin_md4``. +""" +#============================================================================= +# imports +#============================================================================= +# core +import warnings +# site +# pkg +# module +from passlib.tests.test_crypto_builtin_md4 import _Common_MD4_Test +# local +__all__ = [ + "Legacy_MD4_Test", +] +#============================================================================= +# test pure-python MD4 implementation +#============================================================================= +class Legacy_MD4_Test(_Common_MD4_Test): + descriptionPrefix = "passlib.utils.md4.md4()" + + def setUp(self): + super(Legacy_MD4_Test, self).setUp() + warnings.filterwarnings("ignore", ".*passlib.utils.md4.*deprecated", DeprecationWarning) + + def get_md4_const(self): + from passlib.utils.md4 import md4 + return md4 + +#============================================================================= +# eof +#============================================================================= diff --git a/venv/Lib/site-packages/passlib/tests/test_utils_pbkdf2.py b/venv/Lib/site-packages/passlib/tests/test_utils_pbkdf2.py new file mode 100644 index 0000000..443eb53 --- /dev/null +++ b/venv/Lib/site-packages/passlib/tests/test_utils_pbkdf2.py @@ -0,0 +1,323 @@ +""" +passlib.tests -- tests for passlib.utils.pbkdf2 + +.. warning:: + + This module & it's functions have been deprecated, and superceded + by the functions in passlib.crypto. This file is being maintained + until the deprecated functions are removed, and is only present prevent + historical regressions up to that point. New and more thorough testing + is being done by the replacement tests in ``test_utils_crypto.py``. +""" +#============================================================================= +# imports +#============================================================================= +from __future__ import with_statement +# core +import hashlib +import warnings +# site +# pkg +# module +from passlib.utils.compat import u, JYTHON +from passlib.tests.utils import TestCase, hb + +#============================================================================= +# test assorted crypto helpers +#============================================================================= +class UtilsTest(TestCase): + """test various utils functions""" + descriptionPrefix = "passlib.utils.pbkdf2" + + ndn_formats = ["hashlib", "iana"] + ndn_values = [ + # (iana name, hashlib name, ... other unnormalized names) + ("md5", "md5", "SCRAM-MD5-PLUS", "MD-5"), + ("sha1", "sha-1", "SCRAM-SHA-1", "SHA1"), + ("sha256", "sha-256", "SHA_256", "sha2-256"), + ("ripemd160", "ripemd-160", "SCRAM-RIPEMD-160", "RIPEmd160", + # NOTE: there was an older "RIPEMD" & "RIPEMD-128", but python treates "RIPEMD" + # as alias for "RIPEMD-160" + "ripemd", "SCRAM-RIPEMD"), + ("test128", "test-128", "TEST128"), + ("test2", "test2", "TEST-2"), + ("test3_128", "test3-128", "TEST-3-128"), + ] + + def setUp(self): + super(UtilsTest, self).setUp() + warnings.filterwarnings("ignore", ".*passlib.utils.pbkdf2.*deprecated", DeprecationWarning) + + def test_norm_hash_name(self): + """norm_hash_name()""" + from itertools import chain + from passlib.utils.pbkdf2 import norm_hash_name + from passlib.crypto.digest import _known_hash_names + + # test formats + for format in self.ndn_formats: + norm_hash_name("md4", format) + self.assertRaises(ValueError, norm_hash_name, "md4", None) + self.assertRaises(ValueError, norm_hash_name, "md4", "fake") + + # test types + self.assertEqual(norm_hash_name(u("MD4")), "md4") + self.assertEqual(norm_hash_name(b"MD4"), "md4") + self.assertRaises(TypeError, norm_hash_name, None) + + # test selected results + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", '.*unknown hash') + for row in chain(_known_hash_names, self.ndn_values): + for idx, format in enumerate(self.ndn_formats): + correct = row[idx] + for value in row: + result = norm_hash_name(value, format) + self.assertEqual(result, correct, + "name=%r, format=%r:" % (value, + format)) + +#============================================================================= +# test PBKDF1 support +#============================================================================= +class Pbkdf1_Test(TestCase): + """test kdf helpers""" + descriptionPrefix = "passlib.utils.pbkdf2.pbkdf1()" + + pbkdf1_tests = [ + # (password, salt, rounds, keylen, hash, result) + + # + # from http://www.di-mgt.com.au/cryptoKDFs.html + # + (b'password', hb('78578E5A5D63CB06'), 1000, 16, 'sha1', hb('dc19847e05c64d2faf10ebfb4a3d2a20')), + + # + # custom + # + (b'password', b'salt', 1000, 0, 'md5', b''), + (b'password', b'salt', 1000, 1, 'md5', hb('84')), + (b'password', b'salt', 1000, 8, 'md5', hb('8475c6a8531a5d27')), + (b'password', b'salt', 1000, 16, 'md5', hb('8475c6a8531a5d27e386cd496457812c')), + (b'password', b'salt', 1000, None, 'md5', hb('8475c6a8531a5d27e386cd496457812c')), + (b'password', b'salt', 1000, None, 'sha1', hb('4a8fd48e426ed081b535be5769892fa396293efb')), + ] + if not JYTHON: + pbkdf1_tests.append( + (b'password', b'salt', 1000, None, 'md4', hb('f7f2e91100a8f96190f2dd177cb26453')) + ) + + def setUp(self): + super(Pbkdf1_Test, self).setUp() + warnings.filterwarnings("ignore", ".*passlib.utils.pbkdf2.*deprecated", DeprecationWarning) + + def test_known(self): + """test reference vectors""" + from passlib.utils.pbkdf2 import pbkdf1 + for secret, salt, rounds, keylen, digest, correct in self.pbkdf1_tests: + result = pbkdf1(secret, salt, rounds, keylen, digest) + self.assertEqual(result, correct) + + def test_border(self): + """test border cases""" + from passlib.utils.pbkdf2 import pbkdf1 + def helper(secret=b'secret', salt=b'salt', rounds=1, keylen=1, hash='md5'): + return pbkdf1(secret, salt, rounds, keylen, hash) + helper() + + # salt/secret wrong type + self.assertRaises(TypeError, helper, secret=1) + self.assertRaises(TypeError, helper, salt=1) + + # non-existent hashes + self.assertRaises(ValueError, helper, hash='missing') + + # rounds < 1 and wrong type + self.assertRaises(ValueError, helper, rounds=0) + self.assertRaises(TypeError, helper, rounds='1') + + # keylen < 0, keylen > block_size, and wrong type + self.assertRaises(ValueError, helper, keylen=-1) + self.assertRaises(ValueError, helper, keylen=17, hash='md5') + self.assertRaises(TypeError, helper, keylen='1') + +#============================================================================= +# test PBKDF2 support +#============================================================================= +class Pbkdf2_Test(TestCase): + """test pbkdf2() support""" + descriptionPrefix = "passlib.utils.pbkdf2.pbkdf2()" + + pbkdf2_test_vectors = [ + # (result, secret, salt, rounds, keylen, prf="sha1") + + # + # from rfc 3962 + # + + # test case 1 / 128 bit + ( + hb("cdedb5281bb2f801565a1122b2563515"), + b"password", b"ATHENA.MIT.EDUraeburn", 1, 16 + ), + + # test case 2 / 128 bit + ( + hb("01dbee7f4a9e243e988b62c73cda935d"), + b"password", b"ATHENA.MIT.EDUraeburn", 2, 16 + ), + + # test case 2 / 256 bit + ( + hb("01dbee7f4a9e243e988b62c73cda935da05378b93244ec8f48a99e61ad799d86"), + b"password", b"ATHENA.MIT.EDUraeburn", 2, 32 + ), + + # test case 3 / 256 bit + ( + hb("5c08eb61fdf71e4e4ec3cf6ba1f5512ba7e52ddbc5e5142f708a31e2e62b1e13"), + b"password", b"ATHENA.MIT.EDUraeburn", 1200, 32 + ), + + # test case 4 / 256 bit + ( + hb("d1daa78615f287e6a1c8b120d7062a493f98d203e6be49a6adf4fa574b6e64ee"), + b"password", b'\x12\x34\x56\x78\x78\x56\x34\x12', 5, 32 + ), + + # test case 5 / 256 bit + ( + hb("139c30c0966bc32ba55fdbf212530ac9c5ec59f1a452f5cc9ad940fea0598ed1"), + b"X"*64, b"pass phrase equals block size", 1200, 32 + ), + + # test case 6 / 256 bit + ( + hb("9ccad6d468770cd51b10e6a68721be611a8b4d282601db3b36be9246915ec82a"), + b"X"*65, b"pass phrase exceeds block size", 1200, 32 + ), + + # + # from rfc 6070 + # + ( + hb("0c60c80f961f0e71f3a9b524af6012062fe037a6"), + b"password", b"salt", 1, 20, + ), + + ( + hb("ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"), + b"password", b"salt", 2, 20, + ), + + ( + hb("4b007901b765489abead49d926f721d065a429c1"), + b"password", b"salt", 4096, 20, + ), + + # just runs too long - could enable if ALL option is set + ##( + ## + ## unhexlify("eefe3d61cd4da4e4e9945b3d6ba2158c2634e984"), + ## "password", "salt", 16777216, 20, + ##), + + ( + hb("3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038"), + b"passwordPASSWORDpassword", + b"saltSALTsaltSALTsaltSALTsaltSALTsalt", + 4096, 25, + ), + + ( + hb("56fa6aa75548099dcc37d7f03425e0c3"), + b"pass\00word", b"sa\00lt", 4096, 16, + ), + + # + # from example in http://grub.enbug.org/Authentication + # + ( + hb("887CFF169EA8335235D8004242AA7D6187A41E3187DF0CE14E256D85ED" + "97A97357AAA8FF0A3871AB9EEFF458392F462F495487387F685B7472FC" + "6C29E293F0A0"), + b"hello", + hb("9290F727ED06C38BA4549EF7DE25CF5642659211B7FC076F2D28FEFD71" + "784BB8D8F6FB244A8CC5C06240631B97008565A120764C0EE9C2CB0073" + "994D79080136"), + 10000, 64, "hmac-sha512" + ), + + # + # custom + # + ( + hb('e248fb6b13365146f8ac6307cc222812'), + b"secret", b"salt", 10, 16, "hmac-sha1", + ), + ( + hb('e248fb6b13365146f8ac6307cc2228127872da6d'), + b"secret", b"salt", 10, None, "hmac-sha1", + ), + + ] + + def setUp(self): + super(Pbkdf2_Test, self).setUp() + warnings.filterwarnings("ignore", ".*passlib.utils.pbkdf2.*deprecated", DeprecationWarning) + + def test_known(self): + """test reference vectors""" + from passlib.utils.pbkdf2 import pbkdf2 + for row in self.pbkdf2_test_vectors: + correct, secret, salt, rounds, keylen = row[:5] + prf = row[5] if len(row) == 6 else "hmac-sha1" + result = pbkdf2(secret, salt, rounds, keylen, prf) + self.assertEqual(result, correct) + + def test_border(self): + """test border cases""" + from passlib.utils.pbkdf2 import pbkdf2 + def helper(secret=b'password', salt=b'salt', rounds=1, keylen=None, prf="hmac-sha1"): + return pbkdf2(secret, salt, rounds, keylen, prf) + helper() + + # invalid rounds + self.assertRaises(ValueError, helper, rounds=-1) + self.assertRaises(ValueError, helper, rounds=0) + self.assertRaises(TypeError, helper, rounds='x') + + # invalid keylen + self.assertRaises(ValueError, helper, keylen=-1) + self.assertRaises(ValueError, helper, keylen=0) + helper(keylen=1) + self.assertRaises(OverflowError, helper, keylen=20*(2**32-1)+1) + self.assertRaises(TypeError, helper, keylen='x') + + # invalid secret/salt type + self.assertRaises(TypeError, helper, salt=5) + self.assertRaises(TypeError, helper, secret=5) + + # invalid hash + self.assertRaises(ValueError, helper, prf='hmac-foo') + self.assertRaises(NotImplementedError, helper, prf='foo') + self.assertRaises(TypeError, helper, prf=5) + + def test_default_keylen(self): + """test keylen==None""" + from passlib.utils.pbkdf2 import pbkdf2 + def helper(secret=b'password', salt=b'salt', rounds=1, keylen=None, prf="hmac-sha1"): + return pbkdf2(secret, salt, rounds, keylen, prf) + self.assertEqual(len(helper(prf='hmac-sha1')), 20) + self.assertEqual(len(helper(prf='hmac-sha256')), 32) + + def test_custom_prf(self): + """test custom prf function""" + from passlib.utils.pbkdf2 import pbkdf2 + def prf(key, msg): + return hashlib.md5(key+msg+b'fooey').digest() + self.assertRaises(NotImplementedError, pbkdf2, b'secret', b'salt', 1000, 20, prf) + +#============================================================================= +# eof +#============================================================================= diff --git a/venv/Lib/site-packages/passlib/tests/test_win32.py b/venv/Lib/site-packages/passlib/tests/test_win32.py new file mode 100644 index 0000000..e818b62 --- /dev/null +++ b/venv/Lib/site-packages/passlib/tests/test_win32.py @@ -0,0 +1,50 @@ +"""tests for passlib.win32 -- (c) Assurance Technologies 2003-2009""" +#============================================================================= +# imports +#============================================================================= +# core +import warnings +# site +# pkg +from passlib.tests.utils import TestCase +# module +from passlib.utils.compat import u + +#============================================================================= +# +#============================================================================= +class UtilTest(TestCase): + """test util funcs in passlib.win32""" + + ##test hashes from http://msdn.microsoft.com/en-us/library/cc245828(v=prot.10).aspx + ## among other places + + def setUp(self): + super(UtilTest, self).setUp() + warnings.filterwarnings("ignore", + "the 'passlib.win32' module is deprecated") + + def test_lmhash(self): + from passlib.win32 import raw_lmhash + for secret, hash in [ + ("OLDPASSWORD", u("c9b81d939d6fd80cd408e6b105741864")), + ("NEWPASSWORD", u('09eeab5aa415d6e4d408e6b105741864')), + ("welcome", u("c23413a8a1e7665faad3b435b51404ee")), + ]: + result = raw_lmhash(secret, hex=True) + self.assertEqual(result, hash) + + def test_nthash(self): + warnings.filterwarnings("ignore", + r"nthash\.raw_nthash\(\) is deprecated") + from passlib.win32 import raw_nthash + for secret, hash in [ + ("OLDPASSWORD", u("6677b2c394311355b54f25eec5bfacf5")), + ("NEWPASSWORD", u("256781a62031289d3c2c98c14f1efc8c")), + ]: + result = raw_nthash(secret, hex=True) + self.assertEqual(result, hash) + +#============================================================================= +# eof +#============================================================================= diff --git a/venv/Lib/site-packages/passlib/tests/tox_support.py b/venv/Lib/site-packages/passlib/tests/tox_support.py new file mode 100644 index 0000000..43170bc --- /dev/null +++ b/venv/Lib/site-packages/passlib/tests/tox_support.py @@ -0,0 +1,83 @@ +"""passlib.tests.tox_support - helper script for tox tests""" +#============================================================================= +# init script env +#============================================================================= +import os, sys +root_dir = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir) +sys.path.insert(0, root_dir) + +#============================================================================= +# imports +#============================================================================= +# core +import re +import logging; log = logging.getLogger(__name__) +# site +# pkg +from passlib.utils.compat import print_ +# local +__all__ = [ +] + +#============================================================================= +# main +#============================================================================= +TH_PATH = "passlib.tests.test_handlers" + +def do_hash_tests(*args): + """return list of hash algorithm tests that match regexes""" + if not args: + print(TH_PATH) + return + suffix = '' + args = list(args) + while True: + if args[0] == "--method": + suffix = '.' + args[1] + del args[:2] + else: + break + from passlib.tests import test_handlers + names = [TH_PATH + ":" + name + suffix for name in dir(test_handlers) + if not name.startswith("_") and any(re.match(arg,name) for arg in args)] + print_("\n".join(names)) + return not names + +def do_preset_tests(name): + """return list of preset test names""" + if name == "django" or name == "django-hashes": + do_hash_tests("django_.*_test", "hex_md5_test") + if name == "django": + print_("passlib.tests.test_ext_django") + else: + raise ValueError("unknown name: %r" % name) + +def do_setup_gae(path, runtime): + """write fake GAE ``app.yaml`` to current directory so nosegae will work""" + from passlib.tests.utils import set_file + set_file(os.path.join(path, "app.yaml"), """\ +application: fake-app +version: 2 +runtime: %s +api_version: 1 +threadsafe: no + +handlers: +- url: /.* + script: dummy.py + +libraries: +- name: django + version: "latest" +""" % runtime) + +def main(cmd, *args): + return globals()["do_" + cmd](*args) + +if __name__ == "__main__": + import sys + sys.exit(main(*sys.argv[1:]) or 0) + +#============================================================================= +# eof +#=============================================================================