summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2012-01-18 20:01:20 -0500
committerEli Collins <elic@assurancetechnologies.com>2012-01-18 20:01:20 -0500
commitbebb8be9cca116e1331cdb9154b225a69fa9b8b7 (patch)
treec34ef49378a1c9f8059ceadf9f39493dff181ef9
parentc1927edb87df4f22c5d5471e88f42b085a1a946a (diff)
downloadpasslib-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.py4
-rw-r--r--passlib/handlers/bcrypt.py3
-rw-r--r--passlib/handlers/des_crypt.py6
-rw-r--r--passlib/handlers/digests.py2
-rw-r--r--passlib/handlers/django.py6
-rw-r--r--passlib/handlers/mysql.py5
-rw-r--r--passlib/handlers/nthash.py4
-rw-r--r--passlib/handlers/oracle.py6
-rw-r--r--passlib/handlers/postgres.py2
-rw-r--r--passlib/handlers/scram.py5
-rw-r--r--passlib/tests/test_handlers.py6
-rw-r--r--passlib/tests/test_utils.py30
-rw-r--r--passlib/tests/test_utils_handlers.py37
-rw-r--r--passlib/utils/__init__.py17
-rw-r--r--passlib/utils/compat.py130
-rw-r--r--passlib/utils/handlers.py2
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