diff options
| author | Eli Collins <elic@assurancetechnologies.com> | 2012-01-18 20:01:20 -0500 |
|---|---|---|
| committer | Eli Collins <elic@assurancetechnologies.com> | 2012-01-18 20:01:20 -0500 |
| commit | bebb8be9cca116e1331cdb9154b225a69fa9b8b7 (patch) | |
| tree | c34ef49378a1c9f8059ceadf9f39493dff181ef9 | |
| parent | c1927edb87df4f22c5d5471e88f42b085a1a946a (diff) | |
| download | passlib-bebb8be9cca116e1331cdb9154b225a69fa9b8b7.tar.gz | |
misc bugfixes from round of changes
* added str_to_[ub]ascii to wrap hexdigest() calls
* fixed some h64big calls I missed
* some py3 fixes
* removed utils.compat.aliases, using overlay
to replace real compat module instead
(to agree w/ imports already in code)
| -rw-r--r-- | passlib/apache.py | 4 | ||||
| -rw-r--r-- | passlib/handlers/bcrypt.py | 3 | ||||
| -rw-r--r-- | passlib/handlers/des_crypt.py | 6 | ||||
| -rw-r--r-- | passlib/handlers/digests.py | 2 | ||||
| -rw-r--r-- | passlib/handlers/django.py | 6 | ||||
| -rw-r--r-- | passlib/handlers/mysql.py | 5 | ||||
| -rw-r--r-- | passlib/handlers/nthash.py | 4 | ||||
| -rw-r--r-- | passlib/handlers/oracle.py | 6 | ||||
| -rw-r--r-- | passlib/handlers/postgres.py | 2 | ||||
| -rw-r--r-- | passlib/handlers/scram.py | 5 | ||||
| -rw-r--r-- | passlib/tests/test_handlers.py | 6 | ||||
| -rw-r--r-- | passlib/tests/test_utils.py | 30 | ||||
| -rw-r--r-- | passlib/tests/test_utils_handlers.py | 37 | ||||
| -rw-r--r-- | passlib/utils/__init__.py | 17 | ||||
| -rw-r--r-- | passlib/utils/compat.py | 130 | ||||
| -rw-r--r-- | passlib/utils/handlers.py | 2 |
16 files changed, 174 insertions, 91 deletions
diff --git a/passlib/apache.py b/passlib/apache.py index ecde0ba..63fa39b 100644 --- a/passlib/apache.py +++ b/passlib/apache.py @@ -12,7 +12,7 @@ import sys #libs from passlib.context import CryptContext from passlib.utils import consteq, render_bytes -from passlib.utils.compat import b, bytes, bjoin, lmap, u, unicode +from passlib.utils.compat import b, bytes, bjoin, lmap, str_to_bascii, u, unicode #pkg #local __all__ = [ @@ -450,7 +450,7 @@ class HtdigestFile(_CommonFile): if isinstance(password, unicode): password = password.encode(self.password_encoding) #NOTE: encode('ascii') is noop under py2, required under py3 - return md5(render_bytes("%s:%s:%s", user, realm, password)).hexdigest().encode("ascii") + return str_to_bascii(md5(render_bytes("%s:%s:%s", user, realm, password)).hexdigest()) def realms(self): "return all realms listed in file" diff --git a/passlib/handlers/bcrypt.py b/passlib/handlers/bcrypt.py index 8c1a1d8..e30ffc3 100644 --- a/passlib/handlers/bcrypt.py +++ b/passlib/handlers/bcrypt.py @@ -26,7 +26,8 @@ try: except ImportError: #pragma: no cover - though should run whole suite w/o bcryptor installed bcryptor_engine = None #libs -from passlib.utils import safe_os_crypt, classproperty, rng, getrandstr +from passlib.utils import BCRYPT_CHARS as BCHARS, safe_os_crypt, \ + classproperty, rng, getrandstr from passlib.utils.compat import bytes, u, uascii_to_str, unicode import passlib.utils.handlers as uh diff --git a/passlib/handlers/des_crypt.py b/passlib/handlers/des_crypt.py index da64560..976625a 100644 --- a/passlib/handlers/des_crypt.py +++ b/passlib/handlers/des_crypt.py @@ -101,7 +101,7 @@ def raw_crypt(secret, salt): result = mdes_encrypt_int_block(key_value, 0, salt_value, 25) #run h64 encode on result - return h64.encode_dc_int64(result) + return h64big.encode_int64(result) def raw_ext_crypt(secret, rounds, salt): "ext_crypt() helper which returns checksum only" @@ -132,7 +132,7 @@ def raw_ext_crypt(secret, rounds, salt): result = mdes_encrypt_int_block(key_value, 0, salt_value, rounds) #run h64 encode on result - return h64.encode_dc_int64(result) + return h64big.encode_int64(result) #========================================================= #handler @@ -531,7 +531,7 @@ class crypt16(uh.HasSalt, uh.GenericHandler): result2 = mdes_encrypt_int_block(key2, 0, salt_value, 5) #done - chk = h64.encode_dc_int64(result1) + h64.encode_dc_int64(result2) + chk = h64big.encode_int64(result1) + h64big.encode_int64(result2) return chk.decode("ascii") #========================================================= diff --git a/passlib/handlers/digests.py b/passlib/handlers/digests.py index 45c978f..80b4371 100644 --- a/passlib/handlers/digests.py +++ b/passlib/handlers/digests.py @@ -53,7 +53,7 @@ class HexDigestHash(uh.StaticHandler): raise TypeError("no secret provided") if isinstance(secret, unicode): secret = secret.encode("utf-8") - return bascii_to_str(cls._hash_func(secret).hexdigest()) + return cls._hash_func(secret).hexdigest() @classmethod def _norm_hash(cls, hash): diff --git a/passlib/handlers/django.py b/passlib/handlers/django.py index 2c4452b..26754a3 100644 --- a/passlib/handlers/django.py +++ b/passlib/handlers/django.py @@ -10,7 +10,7 @@ from warnings import warn #site #libs from passlib.utils import to_unicode -from passlib.utils.compat import b, bytes, uascii_to_str, unicode, u +from passlib.utils.compat import b, bytes, str_to_uascii, uascii_to_str, unicode, u import passlib.utils.handlers as uh #pkg #local @@ -93,7 +93,7 @@ class django_salted_sha1(DjangoSaltedHash): def calc_checksum(self, secret): if isinstance(secret, unicode): secret = secret.encode("utf-8") - return to_unicode(sha1(self.salt.encode("ascii") + secret).hexdigest(), "ascii") + return str_to_uascii(sha1(self.salt.encode("ascii") + secret).hexdigest()) class django_salted_md5(DjangoSaltedHash): """This class implements Django's Salted MD5 hash, and follows the :ref:`password-hash-api`. @@ -119,7 +119,7 @@ class django_salted_md5(DjangoSaltedHash): def calc_checksum(self, secret): if isinstance(secret, unicode): secret = secret.encode("utf-8") - return to_unicode(md5(self.salt.encode("ascii") + secret).hexdigest(), "ascii") + return str_to_uascii(md5(self.salt.encode("ascii") + secret).hexdigest()) #========================================================= #other diff --git a/passlib/handlers/mysql.py b/passlib/handlers/mysql.py index 3b6d951..cab378b 100644 --- a/passlib/handlers/mysql.py +++ b/passlib/handlers/mysql.py @@ -31,7 +31,7 @@ from warnings import warn #libs #pkg from passlib.utils import to_native_str -from passlib.utils.compat import b, bytes, unicode, u, belem_ord +from passlib.utils.compat import b, bascii_to_str, bytes, unicode, u, belem_ord import passlib.utils.handlers as uh #local __all__ = [ @@ -128,8 +128,7 @@ class mysql41(uh.StaticHandler): # FIXME: no idea if mysql has a policy about handling unicode passwords if isinstance(secret, unicode): secret = secret.encode("utf-8") - chk = bascii_to_str(sha1(sha1(secret).digest()).hexdigest()) - return '*' + chk.upper() + return '*' + sha1(sha1(secret).digest()).hexdigest().upper() @classmethod def _norm_hash(cls, hash): diff --git a/passlib/handlers/nthash.py b/passlib/handlers/nthash.py index 1576a32..df0021c 100644 --- a/passlib/handlers/nthash.py +++ b/passlib/handlers/nthash.py @@ -9,7 +9,7 @@ from warnings import warn #site #libs from passlib.utils import to_unicode -from passlib.utils.compat import bytes, u, uascii_to_str +from passlib.utils.compat import bytes, str_to_uascii, u, uascii_to_str from passlib.utils.md4 import md4 import passlib.utils.handlers as uh #pkg @@ -90,7 +90,7 @@ class nthash(uh.HasStubChecksum, uh.HasManyIdents, uh.GenericHandler): secret = to_unicode(secret, "utf-8", errname="secret") hash = md4(secret.encode("utf-16le")) if hex: - return to_unicode(hash.hexdigest(), 'ascii') + return str_to_uascii(hash.hexdigest()) else: return hash.digest() diff --git a/passlib/handlers/oracle.py b/passlib/handlers/oracle.py index ac33bc3..73b74ed 100644 --- a/passlib/handlers/oracle.py +++ b/passlib/handlers/oracle.py @@ -12,8 +12,8 @@ from warnings import warn #libs #pkg from passlib.utils import to_unicode, to_native_str, xor_bytes -from passlib.utils.compat import bytes, bascii_to_str, irange, u, \ - uascii_to_str, unicode +from passlib.utils.compat import b, bytes, bascii_to_str, irange, u, \ + uascii_to_str, unicode, str_to_uascii from passlib.utils.des import des_encrypt_block import passlib.utils.handlers as uh #local @@ -186,7 +186,7 @@ class oracle11(uh.HasStubChecksum, uh.HasSalt, uh.GenericHandler): if isinstance(secret, unicode): secret = secret.encode("utf-8") chk = sha1(secret + unhexlify(self.salt.encode("ascii"))).hexdigest() - return to_unicode(chk, 'ascii').upper() + return str_to_uascii(chk).upper() #========================================================= #eoc diff --git a/passlib/handlers/postgres.py b/passlib/handlers/postgres.py index b12db22..c4b5f9a 100644 --- a/passlib/handlers/postgres.py +++ b/passlib/handlers/postgres.py @@ -62,7 +62,7 @@ class postgres_md5(uh.StaticHandler): secret = secret.encode("utf-8") if isinstance(user, unicode): user = user.encode("utf-8") - return "md5" + bascii_to_str(md5(secret + user).hexdigest()) + return "md5" + md5(secret + user).hexdigest() #========================================================= #eoc diff --git a/passlib/handlers/scram.py b/passlib/handlers/scram.py index 80031ed..1ff33b1 100644 --- a/passlib/handlers/scram.py +++ b/passlib/handlers/scram.py @@ -503,14 +503,15 @@ class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): def _test_reference_scram(): "quick hack testing scram reference vectors" # NOTE: "n,," is GS2 header - see https://tools.ietf.org/html/rfc5801 + from passlib.utils.compat import print_ engine = _scram_engine( alg="sha-1", salt='QSXCR+Q6sek8bf92'.decode("base64"), rounds=4096, - password=u"pencil", + password=u("pencil"), ) - print engine.digest.encode("base64").rstrip() + print_(engine.digest.encode("base64").rstrip()) msg = engine.format_auth_msg( username="user", diff --git a/passlib/tests/test_handlers.py b/passlib/tests/test_handlers.py index 702c1e3..414be37 100644 --- a/passlib/tests/test_handlers.py +++ b/passlib/tests/test_handlers.py @@ -1097,9 +1097,9 @@ class ScramTest(HandlerCase): # return appropriate value or throw KeyError h = "$scram$10$AAAAAA$sha-1=AQ,bbb=Ag,ccc=Aw" s = b('\x00')*4 - self.assertEqual(edi(h,"SHA1"), (s,10,'\x01')) - self.assertEqual(edi(h,"bbb"), (s,10,'\x02')) - self.assertEqual(edi(h,"ccc"), (s,10,'\x03')) + self.assertEqual(edi(h,"SHA1"), (s,10, b('\x01'))) + self.assertEqual(edi(h,"bbb"), (s,10, b('\x02'))) + self.assertEqual(edi(h,"ccc"), (s,10, b('\x03'))) self.assertRaises(KeyError, edi, h, "ddd") # config strings should cause value error. diff --git a/passlib/tests/test_utils.py b/passlib/tests/test_utils.py index a4e021d..d0609b7 100644 --- a/passlib/tests/test_utils.py +++ b/passlib/tests/test_utils.py @@ -12,7 +12,7 @@ import warnings #pkg #module from passlib.utils.compat import b, bytes, bascii_to_str, irange, PY2, PY3, u, \ - unicode + unicode, bjoin from passlib.tests.utils import TestCase, Params as ak, enable_option, catch_warnings def hb(source): @@ -350,8 +350,6 @@ class CodecTest(TestCase): #check byte inputs ignores enocding self.assertEqual(to_bytes(b('\x00\xc3\xbf'), "latin-1"), b('\x00\xc3\xbf')) - self.assertEqual(to_bytes(b('\x00\xc3\xbf'), None, "utf-8"), - b('\x00\xc3\xbf')) #check bytes transcoding self.assertEqual(to_bytes(b('\x00\xc3\xbf'), "latin-1", "utf-8"), @@ -388,8 +386,8 @@ class CodecTest(TestCase): from passlib.utils import to_native_str # test plain ascii - self.assertEqual(to_native_str(u('abc', 'ascii')), 'abc') - self.assertEqual(to_native_str(b('abc', 'ascii')), 'abc') + self.assertEqual(to_native_str(u('abc'), 'ascii'), 'abc') + self.assertEqual(to_native_str(b('abc'), 'ascii'), 'abc') # test invalid ascii if PY3: @@ -540,7 +538,7 @@ class _Base64Test(TestCase): # helper to generate bytemap-specific strings def m(self, *offsets): "generate byte string from offsets" - return b("").join(self.engine.bytemap[o:o+1] for o in offsets) + return bjoin(self.engine.bytemap[o:o+1] for o in offsets) #========================================================= # test encode_bytes @@ -712,12 +710,14 @@ class _Base64Test(TestCase): encode = getattr(engine, "encode_int%s" % bits) decode = getattr(engine, "decode_int%s" % bits) pad = -bits % 6 - chars = (bits+pad)/6 + chars = (bits+pad)//6 upper = 1<<bits # test encode func for value, encoded in encoded_pairs: - self.assertEqual(encode(value), encoded) + result = encode(value) + self.assertIsInstance(result, bytes) + self.assertEqual(result, encoded) self.assertRaises(ValueError, encode, -1) self.assertRaises(ValueError, encode, upper) @@ -785,11 +785,11 @@ class _Base64Test(TestCase): if not self.encoded_ints: raise self.skipTests("none defined for class") engine = self.engine - for encoded, value, bits in self.encoded_ints: + for data, value, bits in self.encoded_ints: encode = getattr(engine, "encode_int%d" % bits) decode = getattr(engine, "decode_int%d" % bits) - self.assertEqual(encode(value), encoded) - self.assertEqual(decode(encoded), value) + self.assertEqual(encode(value), data) + self.assertEqual(decode(data), value) #========================================================= # eoc @@ -820,8 +820,8 @@ class H64_Test(_Base64Test): ] encoded_ints = [ - ("z.", 63, 12), - (".z", 4032, 12), + (b("z."), 63, 12), + (b(".z"), 4032, 12), ] class H64Big_Test(_Base64Test): @@ -845,8 +845,8 @@ class H64Big_Test(_Base64Test): ] encoded_ints = [ - (".z", 63, 12), - ("z.", 4032, 12), + (b(".z"), 63, 12), + (b("z."), 4032, 12), ] #========================================================= diff --git a/passlib/tests/test_utils_handlers.py b/passlib/tests/test_utils_handlers.py index 9028df4..af7d540 100644 --- a/passlib/tests/test_utils_handlers.py +++ b/passlib/tests/test_utils_handlers.py @@ -14,7 +14,8 @@ from passlib.hash import ldap_md5, sha256_crypt from passlib.registry import _unload_handler_name as unload_handler_name, \ register_crypt_handler, get_crypt_handler from passlib.utils import getrandstr, JYTHON, rng, to_unicode, MissingBackendError -from passlib.utils.compat import b, bytes, bascii_to_str, uascii_to_str, unicode +from passlib.utils.compat import b, bytes, bascii_to_str, str_to_uascii, \ + uascii_to_str, unicode import passlib.utils.handlers as uh from passlib.tests.utils import HandlerCase, TestCase, catch_warnings, \ dummy_handler_in_registry @@ -42,11 +43,11 @@ class SkeletonTest(TestCase): def genhash(cls, secret, hash, flag=False): if isinstance(hash, bytes): hash = hash.decode("ascii") - if hash not in (u('a'),u('b')): - raise ValueError + if hash not in (u('a'), u('b'), None): + raise ValueError("unknown hash %r" % (hash,)) return 'b' if flag else 'a' - #check default identify method + # check default identify method self.assertTrue(d1.identify(u('a'))) self.assertTrue(d1.identify(b('a'))) self.assertTrue(d1.identify(u('b'))) @@ -55,20 +56,26 @@ class SkeletonTest(TestCase): self.assertFalse(d1.identify(u(''))) self.assertFalse(d1.identify(None)) - #check default genconfig method + # check default genconfig method self.assertIs(d1.genconfig(), None) d1._stub_config = u('b') self.assertEqual(d1.genconfig(), 'b') - #check default verify method - self.assertTrue(d1.verify('s','a')) + # check config string is rejected + self.assertRaises(ValueError, d1.verify, 's', b('b')) + self.assertRaises(ValueError, d1.verify, 's', u('b')) + del d1._stub_config + + # check default verify method + self.assertTrue(d1.verify('s', b('a'))) self.assertTrue(d1.verify('s',u('a'))) - self.assertFalse(d1.verify('s','b')) + self.assertFalse(d1.verify('s', b('b'))) self.assertFalse(d1.verify('s',u('b'))) - self.assertTrue(d1.verify('s', 'b', flag=True)) - self.assertRaises(ValueError, d1.verify, 's', 'c') + 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 encrypt method + # check default encrypt method self.assertEqual(d1.encrypt('s'), 'a') self.assertEqual(d1.encrypt('s'), 'a') self.assertEqual(d1.encrypt('s', flag=True), 'b') @@ -411,9 +418,9 @@ class UnsaltedHash(uh.StaticHandler): if isinstance(secret, unicode): secret = secret.encode("utf-8") data = b("boblious") + secret - return bascii_to_str(hashlib.sha1(data).hexdigest()) + return hashlib.sha1(data).hexdigest() -class SaltedHash(uh.HasSalt, uh.GenericHandler): +class SaltedHash(uh.HasStubChecksum, uh.HasSalt, uh.GenericHandler): "test algorithm with a salt" name = "salted_test_hash" setting_kwds = ("salt",) @@ -435,7 +442,7 @@ class SaltedHash(uh.HasSalt, uh.GenericHandler): hash = hash.decode("ascii") return cls(salt=hash[5:-40], checksum=hash[-40:], strict=True) - _stub_checksum = '0' * 40 + _stub_checksum = u('0') * 40 def to_string(self): hash = u("@salt%s%s") % (self.salt, self.checksum or self._stub_checksum) @@ -445,7 +452,7 @@ class SaltedHash(uh.HasSalt, uh.GenericHandler): if isinstance(secret, unicode): secret = secret.encode("utf-8") data = self.salt.encode("ascii") + secret + self.salt.encode("ascii") - return hashlib.sha1(data).hexdigest().decode("ascii") + return str_to_uascii(hashlib.sha1(data).hexdigest()) #========================================================= #test sample algorithms - really a self-test of HandlerCase diff --git a/passlib/utils/__init__.py b/passlib/utils/__init__.py index 74d53c2..a8e3d9c 100644 --- a/passlib/utils/__init__.py +++ b/passlib/utils/__init__.py @@ -325,7 +325,7 @@ def saslprep(source, errname="value"): and passwords before performing byte-value sensitive operations such as hashing. Among other things, it normalizes diacritic representations, removes non-printing characters, and forbids - invalid characters such as ``\n``. + invalid characters such as ``\\n``. :arg source: unicode string to normalize & validate @@ -451,7 +451,7 @@ def render_bytes(source, *args): it converts everything to unicode (decode bytes instances as latin-1), performs the required formatting, then encodes the result to latin-1. - calling ``render_bytes(source, *args)`` should function the same as + calling ``render_bytes(source, *args)`` should function roughly the same as ``source % args`` under python 2. """ if isinstance(source, bytes): @@ -636,13 +636,16 @@ class Base64Engine(object): Informational Attributes ======================== .. attribute:: charmap + unicode string containing list of characters used in encoding; position in string matches 6bit value of character. .. attribute:: bytemap + bytes version of :attr:`charmap` .. attribute:: big + boolean flag indicating this using big-endian encoding. """ @@ -654,10 +657,9 @@ class Base64Engine(object): big = None # little or big endian # filled in by init based on charmap. - # encode: maps 6bit value -> byte_elem, decode: the reverse. - # byte_elem is 1-byte under py2, and 0-255 int under py3. - _encode64 = None - _decode64 = None + # (byte elem: single byte under py2, 8bit int under py3) + _encode64 = None # maps 6bit value -> byte elem + _decode64 = None # maps byte elem -> 6bit value # helpers filled in by init based on endianness _encode_bytes = None # throws IndexError if bad value (shouldn't happen) @@ -975,6 +977,9 @@ class Base64Engine(object): raise TypeError("source must be bytes, not %s" % (type(source),)) if len(source) != 1: raise ValueError("source must be exactly 1 byte") + if PY3: + # convert to 8bit int before doing lookup + source = source[0] try: return self._decode64(source) except KeyError: diff --git a/passlib/utils/compat.py b/passlib/utils/compat.py index 26892ea..8523cc5 100644 --- a/passlib/utils/compat.py +++ b/passlib/utils/compat.py @@ -31,6 +31,7 @@ __all__ = [ 'PY2', 'PY3', 'PY_MAX_25', 'PY27', 'PY_MIN_32', # io + 'BytesIO', 'StringIO', 'SafeConfigParser', 'print_', # type detection @@ -43,6 +44,7 @@ __all__ = [ 'u', 'b', 'unicode', 'bytes', 'sb_types', 'uascii_to_str', 'bascii_to_str', + 'str_to_uascii', 'str_to_bascii', 'ujoin', 'bjoin', 'bjoin_ints', 'bjoin_elems', 'belem_ord', # iteration helpers @@ -55,51 +57,24 @@ __all__ = [ ] #============================================================================= -# lazy import aliases +# lazy-loaded aliases (see LazyOverlayModule at bottom) #============================================================================= if PY3: - _aliases = dict( + _lazy_attrs = dict( BytesIO="io.BytesIO", StringIO="io.StringIO", SafeConfigParser="configparser.SafeConfigParser", ) if PY_MIN_32: # py32 renamed this, removing old ConfigParser - _aliases["SafeConfigParser"] = "configparser.ConfigParser" + _lazy_attrs["SafeConfigParser"] = "configparser.ConfigParser" else: - _aliases = dict( + _lazy_attrs = dict( BytesIO="cStringIO.StringIO", StringIO="StringIO.StringIO", SafeConfigParser="ConfigParser.SafeConfigParser", ) -from types import ModuleType -class _AliasesModule(ModuleType): - "fake module that does lazy importing of attributes" - - def __init__(self, name, **source): - ModuleType.__init__(self, name) - self._source = source - - def __getattr__(self, attr): - source = self._source - if attr in source: - modname, modattr = source[attr].rsplit(".",1) - mod = __import__(modname, fromlist=[modattr], level=0) - value = getattr(mod, modattr) - setattr(self, attr, value) - return value - return ModuleType.__getattr__(self, attr) - - def __dir__(self): - attrs = set(dir(self.__class__)) - attrs.update(self.__dict__) - attrs.update(self._source) - return list(attrs) - -aliases = _AliasesModule(__name__ + ".aliases", **_aliases) -sys.modules[aliases.__name__] = aliases - #============================================================================= # typing #============================================================================= @@ -139,6 +114,10 @@ if PY3: return s.encode("latin-1") else: + unicode = builtins.unicode + bytes = str if PY_MAX_25 else builtins.bytes +# string_types = (unicode, bytes) + def u(s): assert isinstance(s, str) return s.decode("unicode_escape") @@ -167,6 +146,14 @@ if PY3: assert isinstance(s, bytes) return s.decode("ascii") + def str_to_uascii(s): + assert isinstance(s, str) + return s + + def str_to_bascii(s): + assert isinstance(s, str) + return s.encode("ascii") + bjoin_ints = bjoin_elems = bytes def belem_ord(elem): @@ -181,6 +168,14 @@ else: assert isinstance(s, bytes) return s + def str_to_uascii(s): + assert isinstance(s, str) + return s.decode("ascii") + + def str_to_bascii(s): + assert isinstance(s, str) + return s + def bjoin_ints(values): return bjoin(chr(v) for v in values) @@ -190,6 +185,8 @@ else: _add_doc(uascii_to_str, "helper to convert ascii unicode -> native str") _add_doc(bascii_to_str, "helper to convert ascii bytes -> native str") +_add_doc(str_to_uascii, "helper to convert ascii native str -> unicode") +_add_doc(str_to_bascii, "helper to convert ascii native str -> bytes") # bjoin_ints -- function to convert list of ordinal integers to byte string. @@ -299,5 +296,76 @@ else: write(end) #============================================================================= +# lazy overlay module +#============================================================================= +from types import ModuleType + +def import_object(source): + "helper to import object from module; accept format `path.to.object`" + modname, modattr = source.rsplit(".",1) + mod = __import__(modname, fromlist=[modattr], level=0) + return getattr(mod, modattr) + +class LazyOverlayModule(ModuleType): + """proxy module which overlays original module, + and lazily imports specified attributes. + + this is mainly used to prevent importing of resources + that are only needed by certain password hashes, + yet allow them to be imported from a single location. + + used by :mod:`passlib.utils`, :mod:`passlib.utils.crypto`, + and :mod:`passlib.utils.compat`. + """ + + @classmethod + def replace_module(cls, name, attrmap): + orig = sys.modules[name] + self = cls(name, attrmap, orig) + sys.modules[name] = self + return self + + def __init__(self, name, attrmap, proxy=None): + ModuleType.__init__(self, name) + self.__attrmap = attrmap + self.__proxy = proxy + self.__log = logging.getLogger(name) + + def __getattr__(self, attr): + proxy = self.__proxy + if proxy and hasattr(proxy, attr): + return getattr(proxy, attr) + attrmap = self.__attrmap + if attr in attrmap: + source = attrmap[attr] + if callable(source): + value = source() + else: + value = import_object(source) + setattr(self, attr, value) + self.__log.debug("loaded lazy attr %r: %r", attr, value) + return value + raise AttributeError("'module' object has no attribute '%s'" % (attr,)) + + def __repr__(self): + proxy = self.__proxy + if proxy: + return repr(proxy) + else: + return ModuleType.__repr__(self) + + def __dir__(self): + attrs = set(dir(self.__class__)) + attrs.update(self.__dict__) + attrs.update(self.__attrmap) + proxy = self.__proxy + if proxy: + attrs.update(dir(proxy)) + return list(attrs) + +# replace this module with overlay that will lazily import attributes. +LazyOverlayModule.replace_module(__name__, _lazy_attrs) + +#============================================================================= # eof #============================================================================= diff --git a/passlib/utils/handlers.py b/passlib/utils/handlers.py index 8b664b4..c46b473 100644 --- a/passlib/utils/handlers.py +++ b/passlib/utils/handlers.py @@ -210,6 +210,8 @@ class StaticHandler(object): #NOTE: this relys on genhash() throwing error for invalid hashes. # this approach is bad because genhash may take a long time on valid hashes, # so subclasses *really* should override this. + if hash is None: + return False try: cls.genhash('fakesecret', hash) return True |
