summaryrefslogtreecommitdiff
path: root/passlib/utils
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2012-04-12 21:52:26 -0400
committerEli Collins <elic@assurancetechnologies.com>2012-04-12 21:52:26 -0400
commitc0f420bf7d7659ee110432f7cbb0233554dfd32a (patch)
treed8416c7cd9b5f5d54e5fcb58fafa02f64da07352 /passlib/utils
parente71ddce83853566311effebf68b9bbbdebf4c2ab (diff)
downloadpasslib-c0f420bf7d7659ee110432f7cbb0233554dfd32a.tar.gz
assorted bugfixes, tweaks, and tests added; based on coverage examination
* test os_crypt backend has functional fallback * test handler methods accept all unicode/bytes combinations for secret & hash * fixed some incorrect error messages & types being caught & raised * other minor cleanups
Diffstat (limited to 'passlib/utils')
-rw-r--r--passlib/utils/__init__.py117
-rw-r--r--passlib/utils/compat.py10
-rw-r--r--passlib/utils/handlers.py22
-rw-r--r--passlib/utils/md4.py6
4 files changed, 73 insertions, 82 deletions
diff --git a/passlib/utils/__init__.py b/passlib/utils/__init__.py
index e0b9e75..9eb8eab 100644
--- a/passlib/utils/__init__.py
+++ b/passlib/utils/__init__.py
@@ -162,50 +162,50 @@ def deprecated_function(msg=None, deprecated=None, removed=None, updoc=True):
return wrapper
return build
-def relocated_function(target, msg=None, name=None, deprecated=None, mod=None,
- removed=None, updoc=True):
- """constructor to create alias for relocated function.
-
- :arg target: import path to target
- :arg msg: optional msg, default chosen if omitted
- :kwd deprecated: release where function was first deprecated
- :kwd removed: release where function will be removed
- :kwd updoc: add notice to docstring (default ``True``)
- """
- target_mod, target_name = target.rsplit(".",1)
- if mod is None:
- import inspect
- mod = inspect.currentframe(1).f_globals["__name__"]
- if not name:
- name = target_name
- if msg is None:
- msg = ("the function %(mod)s.%(name)s() has been moved to "
- "%(target_mod)s.%(target_name)s(), the old location is deprecated")
- if deprecated:
- msg += " as of Passlib %(deprecated)s"
- if removed:
- msg += ", and will be removed in Passlib %(removed)s"
- msg += "."
- msg %= dict(
- mod=mod,
- name=name,
- target_mod=target_mod,
- target_name=target_name,
- deprecated=deprecated,
- removed=removed,
- )
- state = [None]
- def wrapper(*args, **kwds):
- warn(msg, DeprecationWarning, stacklevel=2)
- func = state[0]
- if func is None:
- module = __import__(target_mod, fromlist=[target_name], level=0)
- func = state[0] = getattr(module, target_name)
- return func(*args, **kwds)
- wrapper.__module__ = mod
- wrapper.__name__ = name
- wrapper.__doc__ = msg
- return wrapper
+##def relocated_function(target, msg=None, name=None, deprecated=None, mod=None,
+## removed=None, updoc=True):
+## """constructor to create alias for relocated function.
+##
+## :arg target: import path to target
+## :arg msg: optional msg, default chosen if omitted
+## :kwd deprecated: release where function was first deprecated
+## :kwd removed: release where function will be removed
+## :kwd updoc: add notice to docstring (default ``True``)
+## """
+## target_mod, target_name = target.rsplit(".",1)
+## if mod is None:
+## import inspect
+## mod = inspect.currentframe(1).f_globals["__name__"]
+## if not name:
+## name = target_name
+## if msg is None:
+## msg = ("the function %(mod)s.%(name)s() has been moved to "
+## "%(target_mod)s.%(target_name)s(), the old location is deprecated")
+## if deprecated:
+## msg += " as of Passlib %(deprecated)s"
+## if removed:
+## msg += ", and will be removed in Passlib %(removed)s"
+## msg += "."
+## msg %= dict(
+## mod=mod,
+## name=name,
+## target_mod=target_mod,
+## target_name=target_name,
+## deprecated=deprecated,
+## removed=removed,
+## )
+## state = [None]
+## def wrapper(*args, **kwds):
+## warn(msg, DeprecationWarning, stacklevel=2)
+## func = state[0]
+## if func is None:
+## module = __import__(target_mod, fromlist=[target_name], level=0)
+## func = state[0] = getattr(module, target_name)
+## return func(*args, **kwds)
+## wrapper.__module__ = mod
+## wrapper.__name__ = name
+## wrapper.__doc__ = msg
+## return wrapper
class memoized_property(object):
"""decorator which invokes method once, then replaces attr with result"""
@@ -309,7 +309,7 @@ def consteq(left, right):
return result == 0
@deprecated_function(deprecated="1.6", removed="1.8")
-def splitcomma(source, sep=","):
+def splitcomma(source, sep=","): # pragma: no cover
"""split comma-separated string into list of elements,
stripping whitespace and discarding empty elements.
"""
@@ -465,7 +465,7 @@ def render_bytes(source, *args):
# NOTE: deprecating bytes<->int in favor of just using struct module.
@deprecated_function(deprecated="1.6", removed="1.8")
-def bytes_to_int(value):
+def bytes_to_int(value): # pragma: no cover
"decode string of bytes as single big-endian integer"
from passlib.utils.compat import byte_elem_value
out = 0
@@ -474,7 +474,7 @@ def bytes_to_int(value):
return out
@deprecated_function(deprecated="1.6", removed="1.8")
-def int_to_bytes(value, count):
+def int_to_bytes(value, count): # pragma: no cover
"encodes integer into single big-endian byte string"
assert value < (1<<(8*count)), "value too large for %d bytes: %d" % (count, value)
return join_byte_values(
@@ -577,10 +577,10 @@ def to_unicode(source, source_encoding="utf-8", errname="value"):
* returns unicode strings unchanged.
* returns bytes strings decoded using *source_encoding*
"""
+ assert source_encoding
if isinstance(source, unicode):
return source
elif isinstance(source, bytes):
- assert source_encoding
return source.decode(source_encoding)
else:
raise ExpectedStringError(source, errname)
@@ -624,7 +624,7 @@ add_doc(to_native_str,
""")
@deprecated_function(deprecated="1.6", removed="1.7")
-def to_hash_str(source, encoding="ascii"):
+def to_hash_str(source, encoding="ascii"): # pragma: no cover
"deprecated, use to_native_str() instead"
return to_native_str(source, encoding, errname="hash")
@@ -698,7 +698,7 @@ class Base64Engine(object):
if isinstance(charmap, unicode):
charmap = charmap.encode("latin-1")
elif not isinstance(charmap, bytes):
- raise TypeError("charmap must be unicode/bytes string")
+ raise ExpectedStringError(charmap, "charmap")
if len(charmap) != 64:
raise ValueError("charmap must be 64 characters in length")
if len(set(charmap)) != 64:
@@ -1145,8 +1145,7 @@ class Base64Engine(object):
:returns:
a string of length ``int(ceil(bits/6.0))``.
"""
- if value < 0:
- raise ValueError("value cannot be negative")
+ assert value >= 0, "caller did not sanitize input"
pad = -bits % 6
bits += pad
if self.big:
@@ -1298,22 +1297,14 @@ else:
if isinstance(secret, bytes):
# Python 3's crypt() only accepts unicode, which is then
# encoding using utf-8 before passing to the C-level crypt().
- # so we have to decode the secret, but also check that it
- # re-encodes to the original sequence of bytes... otherwise
- # the call to crypt() will digest the wrong value.
+ # so we have to decode the secret.
orig = secret
try:
secret = secret.decode("utf-8")
except UnicodeDecodeError:
return None
- if secret.encode("utf-8") != orig:
- # just in case original encoding wouldn't be reproduced
- # during call to os_crypt. not sure if/how this could
- # happen, but being paranoid.
- from passlib.exc import PasslibRuntimeWarning
- warn("utf-8 password didn't re-encode correctly!",
- PasslibRuntimeWarning)
- return None
+ assert secret.encode("utf-8") == orig, \
+ "utf-8 spec says this can't happen!"
if _NULL in secret:
raise ValueError("null character in secret")
if isinstance(hash, bytes):
diff --git a/passlib/utils/compat.py b/passlib/utils/compat.py
index 7bffb15..77f9c3c 100644
--- a/passlib/utils/compat.py
+++ b/passlib/utils/compat.py
@@ -34,7 +34,7 @@ __all__ = [
'print_',
# type detection
- 'is_mapping',
+## 'is_mapping',
'callable',
'int_types',
'num_types',
@@ -234,9 +234,9 @@ else:
#=============================================================================
# typing
#=============================================================================
-def is_mapping(obj):
- # non-exhaustive check, enough to distinguish from lists, etc
- return hasattr(obj, "items")
+##def is_mapping(obj):
+## # non-exhaustive check, enough to distinguish from lists, etc
+## return hasattr(obj, "items")
if (3,0) <= sys.version_info < (3,2):
# callable isn't dead, it's just resting
@@ -390,7 +390,7 @@ class _LazyOverlayModule(ModuleType):
attrs.update(self.__dict__)
attrs.update(self.__attrmap)
proxy = self.__proxy
- if proxy:
+ if proxy is not None:
attrs.update(dir(proxy))
return list(attrs)
diff --git a/passlib/utils/handlers.py b/passlib/utils/handlers.py
index fbf7b69..47e83b0 100644
--- a/passlib/utils/handlers.py
+++ b/passlib/utils/handlers.py
@@ -19,11 +19,11 @@ from passlib.exc import MissingBackendError, PasslibConfigWarning, \
from passlib.registry import get_crypt_handler
from passlib.utils import classproperty, consteq, getrandstr, getrandbytes,\
BASE64_CHARS, HASH64_CHARS, rng, to_native_str, \
- is_crypt_handler, deprecated_function, to_unicode, \
+ is_crypt_handler, to_unicode, \
MAX_PASSWORD_SIZE
from passlib.utils.compat import b, join_byte_values, bytes, irange, u, \
uascii_to_str, join_unicode, unicode, str_to_uascii, \
- join_unicode, base_string_types
+ join_unicode, base_string_types, PY2
# local
__all__ = [
# helpers for implementing MCF handlers
@@ -173,7 +173,7 @@ def parse_mc3(hash, prefix, sep=_UDOLLAR, rounds_base=10,
elif rounds:
rounds = int(rounds, rounds_base)
elif default_rounds is None:
- raise exc.MalformedHashError(handler, "missing rounds field")
+ raise exc.MalformedHashError(handler, "empty rounds field")
else:
rounds = default_rounds
@@ -411,15 +411,15 @@ class GenericHandler(object):
# NOTE: no clear route to reasonbly convert unicode -> raw bytes,
# so relaxed does nothing here
if not isinstance(checksum, bytes):
- raise TypeError("checksum must be byte string")
+ raise exc.ExpectedTypeError(checksum, "bytes", "checksum")
elif not isinstance(checksum, unicode):
- if self.relaxed:
+ if isinstance(checksum, bytes) and self.relaxed:
warn("checksum should be unicode, not bytes",
PasslibHashWarning)
checksum = checksum.decode("ascii")
else:
- raise TypeError("checksum must be unicode string")
+ raise exc.ExpectedTypeError(checksum, "unicode", "checksum")
# handle stub
if checksum == self._stub_checksum:
@@ -960,14 +960,14 @@ class HasSalt(GenericHandler):
# check type
if self._salt_is_bytes:
if not isinstance(salt, bytes):
- raise TypeError("salt must be specified as bytes")
+ raise exc.ExpectedTypeError(salt, "bytes", "salt")
else:
if not isinstance(salt, unicode):
- # XXX: should we disallow bytes here?
- if isinstance(salt, bytes):
+ # NOTE: allowing bytes under py2 so salt can be native str.
+ if isinstance(salt, bytes) and (PY2 or self.relaxed):
salt = salt.decode("ascii")
else:
- raise TypeError("salt must be specified as unicode")
+ raise exc.ExpectedTypeError(salt, "unicode", "salt")
# check charset
sc = self.salt_chars
@@ -1139,7 +1139,7 @@ class HasRounds(GenericHandler):
# check type
if not isinstance(rounds, int):
- raise TypeError("rounds must be an integer")
+ raise exc.ExpectedTypeError(rounds, "integer", "rounds")
# check bounds
mn = self.min_rounds
diff --git a/passlib/utils/md4.py b/passlib/utils/md4.py
index cd3d012..4389a09 100644
--- a/passlib/utils/md4.py
+++ b/passlib/utils/md4.py
@@ -242,14 +242,14 @@ def _has_native_md4():
try:
h = hashlib.new("md4")
except ValueError:
- #not supported
+ # not supported - ssl probably missing (e.g. ironpython)
return False
result = h.hexdigest()
if result == '31d6cfe0d16ae931b73c59d7e0c089c0':
return True
if PYPY and result == '':
- #as of 1.5, pypy md4 just returns null!
- #since this is expected, don't bother w/ warning.
+ # as of pypy 1.5-1.7, this returns empty string!
+ # since this is expected, don't bother w/ warning.
return False
#anything else should alert user
from passlib.exc import PasslibRuntimeWarning