diff options
Diffstat (limited to 'passlib/utils')
-rw-r--r-- | passlib/utils/__init__.py | 185 | ||||
-rw-r--r-- | passlib/utils/binary.py | 88 | ||||
-rw-r--r-- | passlib/utils/compat/__init__.py | 284 | ||||
-rw-r--r-- | passlib/utils/compat/_ordered_dict.py | 242 | ||||
-rw-r--r-- | passlib/utils/decor.py | 27 | ||||
-rw-r--r-- | passlib/utils/des.py | 21 | ||||
-rw-r--r-- | passlib/utils/handlers.py | 167 | ||||
-rw-r--r-- | passlib/utils/pbkdf2.py | 19 |
8 files changed, 194 insertions, 839 deletions
diff --git a/passlib/utils/__init__.py b/passlib/utils/__init__.py index 6147886..94833ad 100644 --- a/passlib/utils/__init__.py +++ b/passlib/utils/__init__.py @@ -61,11 +61,10 @@ from passlib.utils.decor import ( hybrid_method, ) from passlib.exc import ExpectedStringError, ExpectedTypeError -from passlib.utils.compat import (add_doc, join_bytes, join_byte_values, - join_byte_elems, irange, imap, PY3, u, - join_unicode, unicode, byte_elem_value, nextgetter, - unicode_or_str, unicode_or_bytes_types, - get_method_function, suppress_cause, PYPY) +from passlib.utils.compat import (add_doc, join_bytes, + join_unicode, + unicode_or_bytes, + get_method_function, PYPY) # local __all__ = [ # constants @@ -113,7 +112,7 @@ __all__ = [ #============================================================================= # bitsize of system architecture (32 or 64) -sys_bits = int(math.log(sys.maxsize if PY3 else sys.maxint, 2) + 1.5) +sys_bits = int(math.log(sys.maxsize, 2) + 1.5) # list of hashes algs supported by crypt() on at least one OS. # XXX: move to .registry for passlib 2.0? @@ -128,13 +127,10 @@ unix_crypt_schemes = [ # list of rounds_cost constants rounds_cost_values = [ "linear", "log2" ] -# legacy import, will be removed in 1.8 -from passlib.exc import MissingBackendError - # internal helpers _BEMPTY = b'' -_UEMPTY = u("") -_USPACE = u(" ") +_UEMPTY = u"" +_USPACE = u" " # maximum password size which passlib will allow; see exc.PasswordSizeError MAX_PASSWORD_SIZE = int(os.environ.get("PASSLIB_MAX_PASSWORD_SIZE") or 4096) @@ -169,30 +165,23 @@ class SequenceMixin(object): def __ne__(self, other): return not self.__eq__(other) -if PY3: - # getargspec() is deprecated, use this under py3. - # even though it's a lot more awkward to get basic info :| - - _VAR_KEYWORD = inspect.Parameter.VAR_KEYWORD - _VAR_ANY_SET = set([_VAR_KEYWORD, inspect.Parameter.VAR_POSITIONAL]) +# getargspec() is deprecated, use this under py3. +# even though it's a lot more awkward to get basic info :| - def accepts_keyword(func, key): - """test if function accepts specified keyword""" - params = inspect.signature(get_method_function(func)).parameters - if not params: - return False - arg = params.get(key) - if arg and arg.kind not in _VAR_ANY_SET: - return True - # XXX: annoying what we have to do to determine if VAR_KWDS in use. - return params[list(params)[-1]].kind == _VAR_KEYWORD +_VAR_KEYWORD = inspect.Parameter.VAR_KEYWORD +_VAR_ANY_SET = {_VAR_KEYWORD, inspect.Parameter.VAR_POSITIONAL} -else: +def accepts_keyword(func, key): + """test if function accepts specified keyword""" + params = inspect.signature(get_method_function(func)).parameters + if not params: + return False + arg = params.get(key) + if arg and arg.kind not in _VAR_ANY_SET: + return True + # XXX: annoying what we have to do to determine if VAR_KWDS in use. + return params[list(params)[-1]].kind == _VAR_KEYWORD - def accepts_keyword(func, key): - """test if function accepts specified keyword""" - spec = inspect.getargspec(get_method_function(func)) - return key in spec.args or spec.keywords is not None def update_mixin_classes(target, add=None, remove=None, append=False, before=None, after=None, dryrun=False): @@ -333,16 +322,16 @@ def consteq(left, right): # http://bugs.python.org/issue14955 # validate types - if isinstance(left, unicode): - if not isinstance(right, unicode): - raise TypeError("inputs must be both unicode or both bytes") - is_py3_bytes = False + if isinstance(left, str): + if not isinstance(right, str): + raise TypeError("inputs must be both str or both bytes") + is_bytes = False elif isinstance(left, bytes): if not isinstance(right, bytes): - raise TypeError("inputs must be both unicode or both bytes") - is_py3_bytes = PY3 + raise TypeError("inputs must be both str or both bytes") + is_bytes = True else: - raise TypeError("inputs must be both unicode or both bytes") + raise TypeError("inputs must be both str or both bytes") # do size comparison. # NOTE: the double-if construction below is done deliberately, to ensure @@ -361,8 +350,7 @@ def consteq(left, right): result = 1 # run constant-time string comparision - # TODO: use izip instead (but first verify it's faster than zip for this case) - if is_py3_bytes: + if is_bytes: for l,r in zip(tmp, right): result |= l ^ r else: @@ -442,8 +430,8 @@ def saslprep(source, param="value"): # validate type # XXX: support bytes (e.g. run through want_unicode)? # might be easier to just integrate this into cryptcontext. - if not isinstance(source, unicode): - raise TypeError("input must be unicode string, not %s" % + if not isinstance(source, str): + raise TypeError("input must be string, not %s" % (type(source),)) # mapping stage @@ -557,19 +545,11 @@ def render_bytes(source, *args): else arg for arg in args) return result.encode("latin-1") -if PY3: - # new in py32 - def bytes_to_int(value): - return int.from_bytes(value, 'big') - def int_to_bytes(value, count): - return value.to_bytes(count, 'big') -else: - # XXX: can any of these be sped up? - from binascii import hexlify, unhexlify - def bytes_to_int(value): - return int(hexlify(value),16) - def int_to_bytes(value, count): - return unhexlify(('%%0%dx' % (count<<1)) % value) +def bytes_to_int(value): + return int.from_bytes(value, 'big') + +def int_to_bytes(value, count): + return value.to_bytes(count, 'big') add_doc(bytes_to_int, "decode byte string as single big-endian integer") add_doc(int_to_bytes, "encode integer as single big-endian byte string") @@ -595,14 +575,14 @@ def utf8_repeat_string(source, size): _BNULL = b"\x00" -_UNULL = u("\x00") +_UNULL = u"\x00" def right_pad_string(source, size, pad=None): """right-pad or truncate <source> string, so it has length <size>""" cur = len(source) if size > cur: if pad is None: - pad = _UNULL if isinstance(source, unicode) else _BNULL + pad = _UNULL if isinstance(source, str) else _BNULL return source+pad*(size-cur) else: return source[:size] @@ -648,7 +628,7 @@ def utf8_truncate(source, index): # loop until we find non-continuation byte while index < end: - if byte_elem_value(source[index]) & 0xC0 != 0x80: + if source[index] & 0xC0 != 0x80: # found single-char byte, or start-char byte. break # else: found continuation byte. @@ -694,7 +674,7 @@ def is_same_codec(left, right): return _lookup_codec(left).name == _lookup_codec(right).name _B80 = b'\x80'[0] -_U80 = u('\x80') +_U80 = u'\x80' def is_ascii_safe(source): """Check if string (bytes or unicode) contains only 7-bit ascii""" r = _B80 if isinstance(source, bytes) else _U80 @@ -717,7 +697,7 @@ def to_bytes(source, encoding="utf-8", param="value", source_encoding=None): the source will be transcoded from *source_encoding* to *encoding* (via unicode). - :raises TypeError: if source is not unicode or bytes. + :raises TypeError: if source is not str or bytes. :returns: * unicode strings will be encoded using *encoding*, and returned. @@ -732,7 +712,7 @@ def to_bytes(source, encoding="utf-8", param="value", source_encoding=None): return source.decode(source_encoding).encode(encoding) else: return source - elif isinstance(source, unicode): + elif isinstance(source, str): return source.encode(encoding) else: raise ExpectedStringError(source, param) @@ -749,42 +729,34 @@ def to_unicode(source, encoding="utf-8", param="value"): :param param: optional name of variable/noun to reference when raising errors. - :raises TypeError: if source is not unicode or bytes. + :raises TypeError: if source is not str or bytes. :returns: * returns unicode strings unchanged. * returns bytes strings decoded using *encoding* """ assert encoding - if isinstance(source, unicode): + if isinstance(source, str): return source elif isinstance(source, bytes): return source.decode(encoding) else: raise ExpectedStringError(source, param) -if PY3: - def to_native_str(source, encoding="utf-8", param="value"): - if isinstance(source, bytes): - return source.decode(encoding) - elif isinstance(source, unicode): - return source - else: - raise ExpectedStringError(source, param) -else: - def to_native_str(source, encoding="utf-8", param="value"): - if isinstance(source, bytes): - return source - elif isinstance(source, unicode): - return source.encode(encoding) - else: - raise ExpectedStringError(source, param) + +def to_native_str(source, encoding="utf-8", param="value"): + if isinstance(source, bytes): + return source.decode(encoding) + elif isinstance(source, str): + return source + else: + raise ExpectedStringError(source, param) + add_doc(to_native_str, - """Take in unicode or bytes, return native string. + """Take in str or bytes, returns str. - Python 2: encodes unicode using specified encoding, leaves bytes alone. - Python 3: leaves unicode alone, decodes bytes using specified encoding. + leaves str alone, decodes bytes using specified encoding. :raises TypeError: if source is not unicode or bytes. @@ -816,7 +788,7 @@ def as_bool(value, none=None, param="boolean"): recognizes strings such as "true", "false" """ assert none in [True, False, None] - if isinstance(value, unicode_or_bytes_types): + if isinstance(value, unicode_or_bytes): clean = value.lower().strip() if clean in _true_set: return True @@ -840,7 +812,7 @@ def is_safe_crypt_input(value): """ UT helper -- test if value is safe to pass to crypt.crypt(); - under PY3, can't pass non-UTF8 bytes to crypt.crypt. + since PY3 won't let us pass non-UTF8 bytes to crypt.crypt. """ if crypt_accepts_bytes or not isinstance(value, bytes): return True @@ -883,9 +855,9 @@ else: # returning NULL / None. examples include ":", ":0", "*0", etc. # safe_crypt() returns None for any string starting with one of the # chars in this string... - _invalid_prefixes = u("*:!") + _invalid_prefixes = u"*:!" - if PY3: + if True: # legacy block from PY3 compat # * pypy3 (as of v7.3.1) has a crypt which accepts bytes, or ASCII-only unicode. # * whereas CPython3 (as of v3.9) has a crypt which doesn't take bytes, @@ -905,11 +877,11 @@ else: if crypt_accepts_bytes: # PyPy3 -- all bytes accepted, but unicode encoded to ASCII, # so handling that ourselves. - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") if _BNULL in secret: raise ValueError("null character in secret") - if isinstance(hash, unicode): + if isinstance(hash, str): hash = hash.encode("ascii") else: # CPython3's crypt() doesn't take bytes, only unicode; unicode which is then @@ -945,27 +917,7 @@ else: if not result or result[0] in _invalid_prefixes: return None return result - else: - #: see feature-detection in PY3 fork above - crypt_accepts_bytes = True - - # Python 2 crypt handler - def safe_crypt(secret, hash): - if isinstance(secret, unicode): - secret = secret.encode("utf-8") - if _NULL in secret: - raise ValueError("null character in secret") - if isinstance(hash, unicode): - hash = hash.encode("ascii") - with _safe_crypt_lock: - result = _crypt(secret, hash) - if not result: - return None - result = result.decode("ascii") - if result[0] in _invalid_prefixes: - return None - return result add_doc(safe_crypt, """Wrapper around stdlib's crypt. @@ -1006,9 +958,9 @@ def test_crypt(secret, hash): # safe_crypt() always returns unicode, which means that for py3, # 'hash' can't be bytes, or "== hash" will never be True. # under py2 unicode & str(bytes) will compare fine; - # so just enforcing "unicode_or_str" limitation - assert isinstance(hash, unicode_or_str), \ - "hash must be unicode_or_str, got %s" % type(hash) + # so just enforcing "str" limitation + assert isinstance(hash, str), \ + "hash must be str, got %s" % type(hash) assert hash, "hash must be non-empty" return safe_crypt(secret, hash) == hash @@ -1055,7 +1007,7 @@ def genseed(value=None): # this method throws error for e.g. SystemRandom instances, # so fall back to extracting 4k of state value = value.getrandbits(1 << 15) - text = u("%s %s %s %.15f %.15f %s") % ( + text = u"%s %s %s %.15f %.15f %s" % ( # if caller specified a seed value, mix it in value, @@ -1106,7 +1058,8 @@ def getrandbytes(rng, count): yield value & 0xff value >>= 3 i += 1 - return join_byte_values(helper()) + return bytes(helper()) + def getrandstr(rng, charset, count): """return string containing *count* number of chars/bytes, whose elements are drawn from specified charset, using specified rng""" @@ -1132,10 +1085,10 @@ def getrandstr(rng, charset, count): value //= letters i += 1 - if isinstance(charset, unicode): + if isinstance(charset, str): return join_unicode(helper()) else: - return join_byte_elems(helper()) + return bytes(helper()) _52charset = '2346789ABCDEFGHJKMNPQRTUVWXYZabcdefghjkmnpqrstuvwxyz' diff --git a/passlib/utils/binary.py b/passlib/utils/binary.py index 521b64a..f60ddf2 100644 --- a/passlib/utils/binary.py +++ b/passlib/utils/binary.py @@ -5,7 +5,6 @@ passlib.utils.binary - binary data encoding/decoding/manipulation # imports #============================================================================= # core -from __future__ import absolute_import, division, print_function from base64 import ( b64encode, b64decode, @@ -19,10 +18,9 @@ log = logging.getLogger(__name__) # pkg from passlib import exc from passlib.utils.compat import ( - PY3, bascii_to_str, - irange, imap, iter_byte_chars, join_byte_values, join_byte_elems, - nextgetter, suppress_cause, - u, unicode, unicode_or_bytes_types, + bascii_to_str, + iter_byte_chars, + unicode_or_bytes, ) from passlib.utils.decor import memoized_property # from passlib.utils import BASE64_CHARS, HASH64_CHARS @@ -64,28 +62,28 @@ __all__ = [ #------------------------------------------------------------- #: standard base64 charmap -BASE64_CHARS = u("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") +BASE64_CHARS = u"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" #: alt base64 charmap -- "." instead of "+" -AB64_CHARS = u("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./") +AB64_CHARS = u"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./" #: charmap used by HASH64 encoding. -HASH64_CHARS = u("./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") +HASH64_CHARS = u"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" #: charmap used by BCrypt -BCRYPT_CHARS = u("./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") +BCRYPT_CHARS = u"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" #: std base64 chars + padding char -PADDED_BASE64_CHARS = BASE64_CHARS + u("=") +PADDED_BASE64_CHARS = BASE64_CHARS + u"=" #: all hex chars -HEX_CHARS = u("0123456789abcdefABCDEF") +HEX_CHARS = u"0123456789abcdefABCDEF" #: upper case hex chars -UPPER_HEX_CHARS = u("0123456789ABCDEF") +UPPER_HEX_CHARS = u"0123456789ABCDEF" #: lower case hex chars -LOWER_HEX_CHARS = u("0123456789abcdef") +LOWER_HEX_CHARS = u"0123456789abcdef" #------------------------------------------------------------- # byte strings @@ -93,7 +91,7 @@ LOWER_HEX_CHARS = u("0123456789abcdef") #: special byte string containing all possible byte values #: NOTE: for efficiency, this is treated as singleton by some of the code -ALL_BYTE_VALUES = join_byte_values(irange(256)) +ALL_BYTE_VALUES = bytes(range(256)) #: some string constants we reuse B_EMPTY = b'' @@ -128,10 +126,10 @@ def compile_byte_translation(mapping, source=None): assert isinstance(source, bytes) and len(source) == 255 target = list(iter_byte_chars(source)) for k, v in mapping.items(): - if isinstance(k, unicode_or_bytes_types): + if isinstance(k, unicode_or_bytes): k = ord(k) assert isinstance(k, int) and 0 <= k < 256 - if isinstance(v, unicode): + if isinstance(v, str): v = v.encode("ascii") assert isinstance(v, bytes) and len(v) == 1 target[k] = v @@ -152,12 +150,12 @@ def b64s_decode(data): decode from shortened base64 format which omits padding & whitespace. uses default ``+/`` altchars. """ - if isinstance(data, unicode): + if isinstance(data, str): # needs bytes for replace() call, but want to accept ascii-unicode ala a2b_base64() try: data = data.encode("ascii") except UnicodeEncodeError: - raise suppress_cause(ValueError("string argument should contain only ASCII characters")) + raise ValueError("string argument should contain only ASCII characters") from None off = len(data) & 3 if off == 0: pass @@ -170,7 +168,7 @@ def b64s_decode(data): try: return a2b_base64(data) except _BinAsciiError as err: - raise suppress_cause(TypeError(err)) + raise TypeError(err) from None #============================================================================= # adapted-base64 encoding @@ -198,12 +196,12 @@ def ab64_decode(data): it is primarily used by Passlib's custom pbkdf2 hashes. """ - if isinstance(data, unicode): + if isinstance(data, str): # needs bytes for replace() call, but want to accept ascii-unicode ala a2b_base64() try: data = data.encode("ascii") except UnicodeEncodeError: - raise suppress_cause(ValueError("string argument should contain only ASCII characters")) + raise ValueError("string argument should contain only ASCII characters") from None return b64s_decode(data.replace(b".", b"+")) #============================================================================= @@ -233,7 +231,7 @@ def b32decode(source): padding optional, ignored if present. """ # encode & correct for typos - if isinstance(source, unicode): + if isinstance(source, str): source = source.encode("ascii") source = source.translate(_b32_translate) @@ -336,7 +334,7 @@ class Base64Engine(object): #=================================================================== def __init__(self, charmap, big=False): # validate charmap, generate encode64/decode64 helper functions. - if isinstance(charmap, unicode): + if isinstance(charmap, str): charmap = charmap.encode("latin-1") elif not isinstance(charmap, bytes): raise exc.ExpectedStringError(charmap, "charmap") @@ -385,12 +383,9 @@ class Base64Engine(object): if not isinstance(source, bytes): raise TypeError("source must be bytes, not %s" % (type(source),)) chunks, tail = divmod(len(source), 3) - if PY3: - next_value = nextgetter(iter(source)) - else: - next_value = nextgetter(ord(elem) for elem in source) + next_value = iter(source).__next__ gen = self._encode_bytes(next_value, chunks, tail) - out = join_byte_elems(imap(self._encode64, gen)) + out = bytes(map(self._encode64, gen)) ##if tail: ## padding = self.padding ## if padding: @@ -495,9 +490,9 @@ class Base64Engine(object): if tail == 1: # only 6 bits left, can't encode a whole byte! raise ValueError("input string length cannot be == 1 mod 4") - next_value = nextgetter(imap(self._decode64, source)) + next_value = map(self._decode64, source).__next__ try: - return join_byte_values(self._decode_bytes(next_value, chunks, tail)) + return bytes(self._decode_bytes(next_value, chunks, tail)) except KeyError as err: raise ValueError("invalid character: %r" % (err.args[0],)) @@ -626,7 +621,7 @@ class Base64Engine(object): # we have dirty bits - repair the string by decoding last char, # clearing the padding bits via <mask>, and encoding new char. - if isinstance(source, unicode): + if isinstance(source, str): cm = self.charmap last = cm[cm.index(last) & mask] assert last in padset, "failed to generate valid padding char" @@ -635,8 +630,7 @@ class Base64Engine(object): # all chars used by encoding are 7-bit ascii. last = self._encode64(self._decode64(last) & mask) assert last in padset, "failed to generate valid padding char" - if PY3: - last = bytes([last]) + last = bytes([last]) return True, source[:-1] + last def repair_unused(self, source): @@ -661,19 +655,19 @@ class Base64Engine(object): """encode byte string, first transposing source using offset list""" if not isinstance(source, bytes): raise TypeError("source must be bytes, not %s" % (type(source),)) - tmp = join_byte_elems(source[off] for off in offsets) + tmp = bytes(source[off] for off in offsets) return self.encode_bytes(tmp) def decode_transposed_bytes(self, source, offsets): """decode byte string, then reverse transposition described by offset list""" # NOTE: if transposition does not use all bytes of source, - # the original can't be recovered... and join_byte_elems() will throw + # the original can't be recovered... and bytes() will throw # an error because 1+ values in <buf> will be None. tmp = self.decode_bytes(source) buf = [None] * len(offsets) for off, char in zip(offsets, tmp): buf[off] = char - return join_byte_elems(buf) + return bytes(buf) #=================================================================== # integer decoding helpers - mainly used by des_crypt family @@ -724,9 +718,8 @@ 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] + # convert to 8bit int before doing lookup + source = source[0] try: return self._decode64(source) except KeyError: @@ -792,13 +785,13 @@ class Base64Engine(object): pad = -bits % 6 bits += pad if self.big: - itr = irange(bits-6, -6, -6) + itr = range(bits-6, -6, -6) # shift to add lsb padding. value <<= pad else: - itr = irange(0, bits, 6) + itr = range(0, bits, 6) # padding is msb, so no change needed. - return join_byte_elems(imap(self._encode64, + return bytes(map(self._encode64, ((value>>off) & 0x3f for off in itr))) #--------------------------------------------------------------- @@ -809,10 +802,7 @@ class Base64Engine(object): """encodes 6-bit integer -> single hash64 character""" if value < 0 or value > 63: raise ValueError("value out of range") - if PY3: - return self.bytemap[value:value+1] - else: - return self._encode64(value) + return self.bytemap[value:value+1] def encode_int12(self, value): """encodes 12-bit integer -> 2 char string""" @@ -821,7 +811,7 @@ class Base64Engine(object): raw = [value & 0x3f, (value>>6) & 0x3f] if self.big: raw = reversed(raw) - return join_byte_elems(imap(self._encode64, raw)) + return bytes(map(self._encode64, raw)) def encode_int24(self, value): """encodes 24-bit integer -> 4 char string""" @@ -831,7 +821,7 @@ class Base64Engine(object): (value>>12) & 0x3f, (value>>18) & 0x3f] if self.big: raw = reversed(raw) - return join_byte_elems(imap(self._encode64, raw)) + return bytes(map(self._encode64, raw)) def encode_int30(self, value): """decode 5 char string -> 30 bit integer""" @@ -862,7 +852,7 @@ class LazyBase64Engine(Base64Engine): def _lazy_init(self): args, kwds = self._lazy_opts - super(LazyBase64Engine, self).__init__(*args, **kwds) + super().__init__(*args, **kwds) del self._lazy_opts self.__class__ = Base64Engine diff --git a/passlib/utils/compat/__init__.py b/passlib/utils/compat/__init__.py index f6ead24..06bd266 100644 --- a/passlib/utils/compat/__init__.py +++ b/passlib/utils/compat/__init__.py @@ -7,14 +7,10 @@ # python version #------------------------------------------------------------------------ import sys -PY2 = sys.version_info < (3,0) -PY3 = sys.version_info >= (3,0) # make sure it's not an unsupported version, even if we somehow got this far -if sys.version_info < (2,6) or (3,0) <= sys.version_info < (3,2): - raise RuntimeError("Passlib requires Python 2.6, 2.7, or >= 3.2 (as of passlib 1.7)") - -PY26 = sys.version_info < (2,7) +if sys.version_info < (3, 5): + raise RuntimeError("Passlib requires Python >= 3.5 (as of passlib 1.8)") #------------------------------------------------------------------------ # python implementation @@ -26,18 +22,10 @@ PYPY = hasattr(sys, "pypy_version_info") if PYPY and sys.pypy_version_info < (2,0): raise RuntimeError("passlib requires pypy >= 2.0 (as of passlib 1.7)") -# e.g. '2.7.7\n[Pyston 0.5.1]' -# NOTE: deprecated support 2019-11 -PYSTON = "Pyston" in sys.version - #============================================================================= # common imports #============================================================================= import logging; log = logging.getLogger(__name__) -if PY3: - import builtins -else: - import __builtin__ as builtins def add_doc(obj, doc): """add docstring to an object""" @@ -47,38 +35,15 @@ def add_doc(obj, doc): # the default exported vars #============================================================================= __all__ = [ - # python versions - 'PY2', 'PY3', 'PY26', - - # io - 'BytesIO', 'StringIO', 'NativeStringIO', 'SafeConfigParser', - 'print_', - # type detection ## 'is_mapping', - 'int_types', 'num_types', - 'unicode_or_bytes_types', - 'native_string_types', + 'unicode_or_bytes', # unicode/bytes types & helpers - 'u', - 'unicode', - 'uascii_to_str', 'bascii_to_str', - 'str_to_uascii', 'str_to_bascii', + 'bascii_to_str', + 'str_to_bascii', 'join_unicode', 'join_bytes', - 'join_byte_values', 'join_byte_elems', - 'byte_elem_value', - 'iter_byte_values', - - # iteration helpers - 'irange', #'lrange', - 'imap', 'lmap', - 'iteritems', 'itervalues', - 'next', - - # collections - 'OrderedDict', # context helpers 'nullcontext', @@ -94,179 +59,47 @@ _lazy_attrs = dict() #============================================================================= # unicode & bytes types #============================================================================= -if PY3: - unicode = str - - # TODO: once we drop python 3.2 support, can use u'' again! - def u(s): - assert isinstance(s, str) - return s - - unicode_or_bytes_types = (str, bytes) - native_string_types = (unicode,) -else: - unicode = builtins.unicode - - def u(s): - assert isinstance(s, str) - return s.decode("unicode_escape") - - unicode_or_bytes_types = (basestring,) - native_string_types = (basestring,) - -# shorter preferred aliases -unicode_or_bytes = unicode_or_bytes_types -unicode_or_str = native_string_types - -# unicode -- unicode type, regardless of python version -# bytes -- bytes type, regardless of python version -# unicode_or_bytes_types -- types that text can occur in, whether encoded or not -# native_string_types -- types that native python strings (dict keys etc) can occur in. +#: alias for isinstance() tests to detect any string type +unicode_or_bytes = (str, bytes) #============================================================================= # unicode & bytes helpers #============================================================================= # function to join list of unicode strings -join_unicode = u('').join +join_unicode = u''.join # function to join list of byte strings join_bytes = b''.join -if PY3: - def uascii_to_str(s): - assert isinstance(s, unicode) - return s +if True: # legacy PY3 indent def bascii_to_str(s): 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") - join_byte_values = join_byte_elems = bytes - - def byte_elem_value(elem): - assert isinstance(elem, int) - return elem - - def iter_byte_values(s): - assert isinstance(s, bytes) - return s - def iter_byte_chars(s): assert isinstance(s, bytes) # FIXME: there has to be a better way to do this return (bytes([c]) for c in s) -else: - def uascii_to_str(s): - assert isinstance(s, unicode) - return s.encode("ascii") - - def bascii_to_str(s): - 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 join_byte_values(values): - return join_bytes(chr(v) for v in values) - - join_byte_elems = join_bytes - - byte_elem_value = ord - - def iter_byte_values(s): - assert isinstance(s, bytes) - return (ord(c) for c in s) - - def iter_byte_chars(s): - assert isinstance(s, bytes) - return s - -add_doc(uascii_to_str, "helper to convert ascii unicode -> native str") +# TODO: move docstrings to funcs... 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") -# join_byte_values -- function to convert list of ordinal integers to byte string. - -# join_byte_elems -- function to convert list of byte elements to byte string; -# i.e. what's returned by ``b('a')[0]``... -# this is b('a') under PY2, but 97 under PY3. - # byte_elem_value -- function to convert byte element to integer -- a noop under PY3 -add_doc(iter_byte_values, "iterate over byte string as sequence of ints 0-255") add_doc(iter_byte_chars, "iterate over byte string as sequence of 1-byte strings") #============================================================================= # numeric #============================================================================= -if PY3: - int_types = (int,) - num_types = (int, float) -else: - int_types = (int, long) - num_types = (int, long, float) - -#============================================================================= -# iteration helpers -# -# irange - range iterable / view (xrange under py2, range under py3) -# lrange - range list (range under py2, list(range()) under py3) -# -# imap - map to iterator -# lmap - map to list -#============================================================================= -if PY3: - irange = range - ##def lrange(*a,**k): - ## return list(range(*a,**k)) - - def lmap(*a, **k): - return list(map(*a,**k)) - imap = map - - def iteritems(d): - return d.items() - def itervalues(d): - return d.values() - - def nextgetter(obj): - return obj.__next__ - - izip = zip - -else: - irange = xrange - ##lrange = range - lmap = map - from itertools import imap, izip - - def iteritems(d): - return d.iteritems() - def itervalues(d): - return d.itervalues() - - def nextgetter(obj): - return obj.next - -add_doc(nextgetter, "return function that yields successive values from iterable") +num_types = (int, float) #============================================================================= # typing @@ -278,103 +111,10 @@ add_doc(nextgetter, "return function that yields successive values from iterable #============================================================================= # introspection #============================================================================= -if PY3: - method_function_attr = "__func__" -else: - method_function_attr = "im_func" def get_method_function(func): """given (potential) method, return underlying function""" - return getattr(func, method_function_attr, func) - -def get_unbound_method_function(func): - """given unbound method, return underlying function""" - return func if PY3 else func.__func__ - -def error_from(exc, # *, - cause=None): - """ - backward compat hack to suppress exception cause in python3.3+ - - one python < 3.3 support is dropped, can replace all uses with "raise exc from None" - """ - exc.__cause__ = cause - exc.__suppress_context__ = True - return exc - -# legacy alias -suppress_cause = error_from - -#============================================================================= -# input/output -#============================================================================= -if PY3: - _lazy_attrs = dict( - BytesIO="io.BytesIO", - UnicodeIO="io.StringIO", - NativeStringIO="io.StringIO", - SafeConfigParser="configparser.ConfigParser", - ) - - print_ = getattr(builtins, "print") - -else: - _lazy_attrs = dict( - BytesIO="cStringIO.StringIO", - UnicodeIO="StringIO.StringIO", - NativeStringIO="cStringIO.StringIO", - SafeConfigParser="ConfigParser.SafeConfigParser", - ) - - def print_(*args, **kwds): - """The new-style print function.""" - # extract kwd args - fp = kwds.pop("file", sys.stdout) - sep = kwds.pop("sep", None) - end = kwds.pop("end", None) - if kwds: - raise TypeError("invalid keyword arguments") - - # short-circuit if no target - if fp is None: - return - - # use unicode or bytes ? - want_unicode = isinstance(sep, unicode) or isinstance(end, unicode) or \ - any(isinstance(arg, unicode) for arg in args) - - # pick default end sequence - if end is None: - end = u("\n") if want_unicode else "\n" - elif not isinstance(end, unicode_or_bytes_types): - raise TypeError("end must be None or a string") - - # pick default separator - if sep is None: - sep = u(" ") if want_unicode else " " - elif not isinstance(sep, unicode_or_bytes_types): - raise TypeError("sep must be None or a string") - - # write to buffer - first = True - write = fp.write - for arg in args: - if first: - first = False - else: - write(sep) - if not isinstance(arg, basestring): - arg = str(arg) - write(arg) - write(end) - -#============================================================================= -# collections -#============================================================================= -if PY26: - _lazy_attrs['OrderedDict'] = 'passlib.utils.compat._ordered_dict.OrderedDict' -else: - _lazy_attrs['OrderedDict'] = 'collections.OrderedDict' + return getattr(func, "__func__", func) #============================================================================= # context managers diff --git a/passlib/utils/compat/_ordered_dict.py b/passlib/utils/compat/_ordered_dict.py deleted file mode 100644 index cfd766d..0000000 --- a/passlib/utils/compat/_ordered_dict.py +++ /dev/null @@ -1,242 +0,0 @@ -"""passlib.utils.compat._ordered_dict -- backport of collections.OrderedDict for py26 - -taken from stdlib-suggested recipe at http://code.activestate.com/recipes/576693/ - -this should be imported from passlib.utils.compat.OrderedDict, not here. -""" - -try: - from thread import get_ident as _get_ident -except ImportError: - from dummy_thread import get_ident as _get_ident - -class OrderedDict(dict): - """Dictionary that remembers insertion order""" - # An inherited dict maps keys to values. - # The inherited dict provides __getitem__, __len__, __contains__, and get. - # The remaining methods are order-aware. - # Big-O running times for all methods are the same as for regular dictionaries. - - # The internal self.__map dictionary maps keys to links in a doubly linked list. - # The circular doubly linked list starts and ends with a sentinel element. - # The sentinel element never gets deleted (this simplifies the algorithm). - # Each link is stored as a list of length three: [PREV, NEXT, KEY]. - - def __init__(self, *args, **kwds): - '''Initialize an ordered dictionary. Signature is the same as for - regular dictionaries, but keyword arguments are not recommended - because their insertion order is arbitrary. - - ''' - if len(args) > 1: - raise TypeError('expected at most 1 arguments, got %d' % len(args)) - try: - self.__root - except AttributeError: - self.__root = root = [] # sentinel node - root[:] = [root, root, None] - self.__map = {} - self.__update(*args, **kwds) - - def __setitem__(self, key, value, dict_setitem=dict.__setitem__): - 'od.__setitem__(i, y) <==> od[i]=y' - # Setting a new item creates a new link which goes at the end of the linked - # list, and the inherited dictionary is updated with the new key/value pair. - if key not in self: - root = self.__root - last = root[0] - last[1] = root[0] = self.__map[key] = [last, root, key] - dict_setitem(self, key, value) - - def __delitem__(self, key, dict_delitem=dict.__delitem__): - 'od.__delitem__(y) <==> del od[y]' - # Deleting an existing item uses self.__map to find the link which is - # then removed by updating the links in the predecessor and successor nodes. - dict_delitem(self, key) - link_prev, link_next, key = self.__map.pop(key) - link_prev[1] = link_next - link_next[0] = link_prev - - def __iter__(self): - 'od.__iter__() <==> iter(od)' - root = self.__root - curr = root[1] - while curr is not root: - yield curr[2] - curr = curr[1] - - def __reversed__(self): - 'od.__reversed__() <==> reversed(od)' - root = self.__root - curr = root[0] - while curr is not root: - yield curr[2] - curr = curr[0] - - def clear(self): - 'od.clear() -> None. Remove all items from od.' - try: - for node in self.__map.itervalues(): - del node[:] - root = self.__root - root[:] = [root, root, None] - self.__map.clear() - except AttributeError: - pass - dict.clear(self) - - def popitem(self, last=True): - '''od.popitem() -> (k, v), return and remove a (key, value) pair. - Pairs are returned in LIFO order if last is true or FIFO order if false. - - ''' - if not self: - raise KeyError('dictionary is empty') - root = self.__root - if last: - link = root[0] - link_prev = link[0] - link_prev[1] = root - root[0] = link_prev - else: - link = root[1] - link_next = link[1] - root[1] = link_next - link_next[0] = root - key = link[2] - del self.__map[key] - value = dict.pop(self, key) - return key, value - - # -- the following methods do not depend on the internal structure -- - - def keys(self): - 'od.keys() -> list of keys in od' - return list(self) - - def values(self): - 'od.values() -> list of values in od' - return [self[key] for key in self] - - def items(self): - 'od.items() -> list of (key, value) pairs in od' - return [(key, self[key]) for key in self] - - def iterkeys(self): - 'od.iterkeys() -> an iterator over the keys in od' - return iter(self) - - def itervalues(self): - 'od.itervalues -> an iterator over the values in od' - for k in self: - yield self[k] - - def iteritems(self): - 'od.iteritems -> an iterator over the (key, value) items in od' - for k in self: - yield (k, self[k]) - - def update(*args, **kwds): - '''od.update(E, **F) -> None. Update od from dict/iterable E and F. - - If E is a dict instance, does: for k in E: od[k] = E[k] - If E has a .keys() method, does: for k in E.keys(): od[k] = E[k] - Or if E is an iterable of items, does: for k, v in E: od[k] = v - In either case, this is followed by: for k, v in F.items(): od[k] = v - - ''' - if len(args) > 2: - raise TypeError('update() takes at most 2 positional ' - 'arguments (%d given)' % (len(args),)) - elif not args: - raise TypeError('update() takes at least 1 argument (0 given)') - self = args[0] - # Make progressively weaker assumptions about "other" - other = () - if len(args) == 2: - other = args[1] - if isinstance(other, dict): - for key in other: - self[key] = other[key] - elif hasattr(other, 'keys'): - for key in other.keys(): - self[key] = other[key] - else: - for key, value in other: - self[key] = value - for key, value in kwds.items(): - self[key] = value - - __update = update # let subclasses override update without breaking __init__ - - __marker = object() - - def pop(self, key, default=__marker): - '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value. - If key is not found, d is returned if given, otherwise KeyError is raised. - - ''' - if key in self: - result = self[key] - del self[key] - return result - if default is self.__marker: - raise KeyError(key) - return default - - def setdefault(self, key, default=None): - 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od' - if key in self: - return self[key] - self[key] = default - return default - - def __repr__(self, _repr_running={}): - 'od.__repr__() <==> repr(od)' - call_key = id(self), _get_ident() - if call_key in _repr_running: - return '...' - _repr_running[call_key] = 1 - try: - if not self: - return '%s()' % (self.__class__.__name__,) - return '%s(%r)' % (self.__class__.__name__, self.items()) - finally: - del _repr_running[call_key] - - def __reduce__(self): - 'Return state information for pickling' - items = [[k, self[k]] for k in self] - inst_dict = vars(self).copy() - for k in vars(OrderedDict()): - inst_dict.pop(k, None) - if inst_dict: - return (self.__class__, (items,), inst_dict) - return self.__class__, (items,) - - def copy(self): - 'od.copy() -> a shallow copy of od' - return self.__class__(self) - - @classmethod - def fromkeys(cls, iterable, value=None): - '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S - and values equal to v (which defaults to None). - - ''' - d = cls() - for key in iterable: - d[key] = value - return d - - def __eq__(self, other): - '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive - while comparison to a regular mapping is order-insensitive. - - ''' - if isinstance(other, OrderedDict): - return len(self)==len(other) and self.items() == other.items() - return dict.__eq__(self, other) - - def __ne__(self, other): - return not self == other diff --git a/passlib/utils/decor.py b/passlib/utils/decor.py index 9041d5d..e1fd601 100644 --- a/passlib/utils/decor.py +++ b/passlib/utils/decor.py @@ -5,7 +5,6 @@ passlib.utils.decor -- helper decorators & properties # imports #============================================================================= # core -from __future__ import absolute_import, division, print_function import logging log = logging.getLogger(__name__) from functools import wraps, update_wrapper @@ -13,7 +12,6 @@ import types from warnings import warn # site # pkg -from passlib.utils.compat import PY3 # local __all__ = [ "classproperty", @@ -33,15 +31,12 @@ class classproperty(object): """Function decorator which acts like a combination of classmethod+property (limited to read-only properties)""" def __init__(self, func): - self.im_func = func + # XXX: rename to .fget to match property? + self.__func__ = func def __get__(self, obj, cls): - return self.im_func(cls) + return self.__func__(cls) - @property - def __func__(self): - """py3 compatible alias""" - return self.im_func class hybrid_method(object): """ @@ -50,16 +45,14 @@ class hybrid_method(object): """ def __init__(self, func): + # XXX: rename to .fget to match property? self.func = func update_wrapper(self, func) def __get__(self, obj, cls): if obj is None: obj = cls - if PY3: - return types.MethodType(self.func, obj) - else: - return types.MethodType(self.func, obj, cls) + return types.MethodType(self.func, obj) #============================================================================= # memoization @@ -104,13 +97,6 @@ class memoized_property(object): setattr(obj, self.__name__, value) return value - if not PY3: - - @property - def im_func(self): - """py2 alias""" - return self.__func__ - def clear_cache(self, obj): """ class-level helper to clear stored value (if any). @@ -174,8 +160,7 @@ def deprecated_function(msg=None, deprecated=None, removed=None, updoc=True, def build(func): is_classmethod = _is_method and isinstance(func, classmethod) if is_classmethod: - # NOTE: PY26 doesn't support "classmethod().__func__" directly... - func = func.__get__(None, type).__func__ + func = func.__func__ opts = dict( mod=func_module or func.__module__, name=func.__name__, diff --git a/passlib/utils/des.py b/passlib/utils/des.py index 034bfc4..1c407b9 100644 --- a/passlib/utils/des.py +++ b/passlib/utils/des.py @@ -17,30 +17,15 @@ warn("the 'passlib.utils.des' module has been relocated to 'passlib.crypto.des' from passlib.utils.decor import deprecated_function from passlib.crypto.des import expand_des_key, des_encrypt_block, des_encrypt_int_block -expand_des_key = deprecated_function(deprecated="1.7", removed="1.8", +expand_des_key = deprecated_function(deprecated="1.7", removed="2.0", replacement="passlib.crypto.des.expand_des_key")(expand_des_key) -des_encrypt_block = deprecated_function(deprecated="1.7", removed="1.8", +des_encrypt_block = deprecated_function(deprecated="1.7", removed="2.0", replacement="passlib.crypto.des.des_encrypt_block")(des_encrypt_block) -des_encrypt_int_block = deprecated_function(deprecated="1.7", removed="1.8", +des_encrypt_int_block = deprecated_function(deprecated="1.7", removed="2.0", replacement="passlib.crypto.des.des_encrypt_int_block")(des_encrypt_int_block) #============================================================================= -# deprecated functions -- not carried over to passlib.crypto.des -#============================================================================= -import struct -_unpack_uint64 = struct.Struct(">Q").unpack - -@deprecated_function(deprecated="1.6", removed="1.8", - replacement="passlib.crypto.des.des_encrypt_int_block()") -def mdes_encrypt_int_block(key, input, salt=0, rounds=1): # pragma: no cover -- deprecated & unused - if isinstance(key, bytes): - if len(key) == 7: - key = expand_des_key(key) - key = _unpack_uint64(key)[0] - return des_encrypt_int_block(key, input, salt, rounds) - -#============================================================================= # eof #============================================================================= diff --git a/passlib/utils/handlers.py b/passlib/utils/handlers.py index f8681fa..cea096c 100644 --- a/passlib/utils/handlers.py +++ b/passlib/utils/handlers.py @@ -2,7 +2,6 @@ #============================================================================= # imports #============================================================================= -from __future__ import with_statement # core import inspect import logging; log = logging.getLogger(__name__) @@ -27,9 +26,7 @@ from passlib.utils.binary import ( HEX_CHARS, UPPER_HEX_CHARS, LOWER_HEX_CHARS, ALL_BYTE_VALUES, ) -from passlib.utils.compat import join_byte_values, irange, u, native_string_types, \ - uascii_to_str, join_unicode, unicode, str_to_uascii, \ - join_unicode, unicode_or_bytes_types, PY2, int_types +from passlib.utils.compat import join_unicode, unicode_or_bytes from passlib.utils.decor import classproperty, deprecated_method # local __all__ = [ @@ -113,19 +110,19 @@ def extract_settings_kwds(handler, kwds): #============================================================================= # parsing helpers #============================================================================= -_UDOLLAR = u("$") -_UZERO = u("0") +_UDOLLAR = u"$" +_UZERO = u"0" def validate_secret(secret): """ensure secret has correct type & size""" - if not isinstance(secret, unicode_or_bytes_types): + if not isinstance(secret, unicode_or_bytes): raise exc.ExpectedStringError(secret, "secret") if len(secret) > MAX_PASSWORD_SIZE: raise exc.PasswordSizeError(MAX_PASSWORD_SIZE) def to_unicode_for_identify(hash): """convert hash to unicode for identify method""" - if isinstance(hash, unicode): + if isinstance(hash, str): return hash elif isinstance(hash, bytes): # try as utf-8, but if it fails, use foolproof latin-1, @@ -144,8 +141,8 @@ def parse_mc2(hash, prefix, sep=_UDOLLAR, handler=None): this expects a hash of the format :samp:`{prefix}{salt}[${checksum}]`, such as md5_crypt, and parses it into salt / checksum portions. - :arg hash: the hash to parse (bytes or unicode) - :arg prefix: the identifying prefix (unicode) + :arg hash: the hash to parse (bytes or str) + :arg prefix: the identifying prefix (str) :param sep: field separator (unicode, defaults to ``$``). :param handler: handler class to pass to error constructors. @@ -154,12 +151,12 @@ def parse_mc2(hash, prefix, sep=_UDOLLAR, handler=None): """ # detect prefix hash = to_unicode(hash, "ascii", "hash") - assert isinstance(prefix, unicode) + assert isinstance(prefix, str) if not hash.startswith(prefix): raise exc.InvalidHashError(handler) # parse 2-part hash or 1-part config string - assert isinstance(sep, unicode) + assert isinstance(sep, str) parts = hash[len(prefix):].split(sep) if len(parts) == 2: salt, chk = parts @@ -193,12 +190,12 @@ def parse_mc3(hash, prefix, sep=_UDOLLAR, rounds_base=10, """ # detect prefix hash = to_unicode(hash, "ascii", "hash") - assert isinstance(prefix, unicode) + assert isinstance(prefix, str) if not hash.startswith(prefix): raise exc.InvalidHashError(handler) # parse 3-part hash or 2-part config string - assert isinstance(sep, unicode) + assert isinstance(sep, str) parts = hash[len(prefix):].split(sep) if len(parts) == 3: rounds, salt, chk = parts @@ -266,7 +263,7 @@ def parse_int(source, base=10, default=None, param="value", handler=None): #============================================================================= # formatting helpers #============================================================================= -def render_mc2(ident, salt, checksum, sep=u("$")): +def render_mc2(ident, salt, checksum, sep=u"$"): """format hash using 2-part modular crypt format; inverse of parse_mc2() returns native string with format :samp:`{ident}{salt}[${checksum}]`, @@ -284,9 +281,9 @@ def render_mc2(ident, salt, checksum, sep=u("$")): parts = [ident, salt, sep, checksum] else: parts = [ident, salt] - return uascii_to_str(join_unicode(parts)) + return join_unicode(parts) -def render_mc3(ident, rounds, salt, checksum, sep=u("$"), rounds_base=10): +def render_mc3(ident, rounds, salt, checksum, sep=u"$", rounds_base=10): """format hash using 3-part modular crypt format; inverse of parse_mc3() returns native string with format :samp:`{ident}[{rounds}$]{salt}[${checksum}]`, @@ -303,17 +300,17 @@ def render_mc3(ident, rounds, salt, checksum, sep=u("$"), rounds_base=10): config or hash (native str) """ if rounds is None: - rounds = u('') + rounds = u'' elif rounds_base == 16: - rounds = u("%x") % rounds + rounds = u"%x" % rounds else: assert rounds_base == 10 - rounds = unicode(rounds) + rounds = str(rounds) if checksum: parts = [ident, rounds, sep, salt, sep, checksum] else: parts = [ident, rounds, sep, salt] - return uascii_to_str(join_unicode(parts)) + return join_unicode(parts) def mask_value(value, show=4, pct=0.125, char=u"*"): @@ -336,12 +333,12 @@ def mask_value(value, show=4, pct=0.125, char=u"*"): """ if value is None: return None - if not isinstance(value, unicode): + if not isinstance(value, str): if isinstance(value, bytes): from passlib.utils.binary import ab64_encode value = ab64_encode(value).decode("ascii") else: - value = unicode(value) + value = str(value) size = len(value) show = min(show, int(size * pct)) return value[:show] + char * (size - show) @@ -372,7 +369,7 @@ def norm_integer(handler, value, min=1, max=None, # * :returns: validated value """ # check type - if not isinstance(value, int_types): + if not isinstance(value, int): raise exc.ExpectedTypeError(value, "integer", param) # check minimum @@ -452,7 +449,7 @@ class TruncateMixin(MinimalHandler): @classmethod def using(cls, truncate_error=None, **kwds): - subcls = super(TruncateMixin, cls).using(**kwds) + subcls = super().using(**kwds) if truncate_error is not None: truncate_error = as_bool(truncate_error, param="truncate_error") if truncate_error is not None: @@ -618,7 +615,7 @@ class GenericHandler(MinimalHandler): #=================================================================== def __init__(self, checksum=None, use_defaults=False, **kwds): self.use_defaults = use_defaults - super(GenericHandler, self).__init__(**kwds) + super().__init__(**kwds) if checksum is not None: # XXX: do we need to set .relaxed for checksum coercion? self.checksum = self._norm_checksum(checksum) @@ -641,12 +638,12 @@ class GenericHandler(MinimalHandler): if not isinstance(checksum, bytes): raise exc.ExpectedTypeError(checksum, "bytes", "checksum") - elif not isinstance(checksum, unicode): + elif not isinstance(checksum, str): if isinstance(checksum, bytes) and relaxed: warn("checksum should be unicode, not bytes", PasslibHashWarning) checksum = checksum.decode("ascii") else: - raise exc.ExpectedTypeError(checksum, "unicode", "checksum") + raise exc.ExpectedTypeError(checksum, "str", "checksum") # check size cc = self.checksum_size @@ -713,8 +710,7 @@ class GenericHandler(MinimalHandler): :returns: hash string with salt & digest included. - should return native string type (ascii-bytes under python 2, - unicode under python 3) + should return native str. """ raise NotImplementedError("%s must implement from_string()" % (self.__class__,)) @@ -752,7 +748,7 @@ class GenericHandler(MinimalHandler): string, taking config from object state calc checksum implementations may assume secret is always - either unicode or bytes, checks are performed by verify/etc. + either str or bytes, checks are performed by verify/etc. """ raise NotImplementedError("%s must implement _calc_checksum()" % (self.__class__,)) @@ -906,7 +902,7 @@ class GenericHandler(MinimalHandler): def bitsize(cls, **kwds): """[experimental method] return info about bitsizes of hash""" try: - info = super(GenericHandler, cls).bitsize(**kwds) + info = super().bitsize(**kwds) except AttributeError: info = {} cc = ALL_BYTE_VALUES if cls._checksum_is_bytes else cls.checksum_chars @@ -940,7 +936,7 @@ class StaticHandler(GenericHandler): setting_kwds = () # optional constant prefix subclasses can specify - _hash_prefix = u("") + _hash_prefix = u"" @classmethod def from_string(cls, hash, **context): @@ -966,46 +962,7 @@ class StaticHandler(GenericHandler): return hash def to_string(self): - return uascii_to_str(self._hash_prefix + self.checksum) - - # per-subclass: stores dynamically created subclass used by _calc_checksum() stub - __cc_compat_hack = None - - def _calc_checksum(self, secret): - """given secret; calcuate and return encoded checksum portion of hash - string, taking config from object state - """ - # NOTE: prior to 1.6, StaticHandler required classes implement genhash - # instead of this method. so if we reach here, we try calling genhash. - # if that succeeds, we issue deprecation warning. if it fails, - # we'll just recurse back to here, but in a different instance. - # so before we call genhash, we create a subclass which handles - # throwing the NotImplementedError. - cls = self.__class__ - assert cls.__module__ != __name__ - wrapper_cls = cls.__cc_compat_hack - if wrapper_cls is None: - def inner(self, secret): - raise NotImplementedError("%s must implement _calc_checksum()" % - (cls,)) - wrapper_cls = cls.__cc_compat_hack = type(cls.__name__ + "_wrapper", - (cls,), dict(_calc_checksum=inner, __module__=cls.__module__)) - context = dict((k,getattr(self,k)) for k in self.context_kwds) - # NOTE: passing 'config=None' here even though not currently allowed by ifc, - # since it *is* allowed under the old 1.5 ifc we're checking for here. - try: - hash = wrapper_cls.genhash(secret, None, **context) - except TypeError as err: - if str(err) == "config must be string": - raise NotImplementedError("%s must implement _calc_checksum()" % - (cls,)) - else: - raise - warn("%r should be updated to implement StaticHandler._calc_checksum() " - "instead of StaticHandler.genhash(), support for the latter " - "style will be removed in Passlib 1.8" % cls, - DeprecationWarning) - return str_to_uascii(hash) + return self._hash_prefix + self.checksum #============================================================================= # GenericHandler mixin classes @@ -1016,7 +973,7 @@ class HasEncodingContext(GenericHandler): default_encoding = "utf-8" def __init__(self, encoding=None, **kwds): - super(HasEncodingContext, self).__init__(**kwds) + super().__init__(**kwds) self.encoding = encoding or self.default_encoding class HasUserContext(GenericHandler): @@ -1024,7 +981,7 @@ class HasUserContext(GenericHandler): context_kwds = ("user",) def __init__(self, user=None, **kwds): - super(HasUserContext, self).__init__(**kwds) + super().__init__(**kwds) self.user = user # XXX: would like to validate user input here, but calls to from_string() @@ -1033,16 +990,16 @@ class HasUserContext(GenericHandler): # wrap funcs to accept 'user' as positional arg for ease of use. @classmethod def hash(cls, secret, user=None, **context): - return super(HasUserContext, cls).hash(secret, user=user, **context) + return super().hash(secret, user=user, **context) @classmethod def verify(cls, secret, hash, user=None, **context): - return super(HasUserContext, cls).verify(secret, hash, user=user, **context) + return super().verify(secret, hash, user=user, **context) @deprecated_method(deprecated="1.7", removed="2.0") @classmethod def genhash(cls, secret, config, user=None, **context): - return super(HasUserContext, cls).genhash(secret, config, user=user, **context) + return super().genhash(secret, config, user=user, **context) # XXX: how to guess the entropy of a username? # most of these hashes are for a system (e.g. Oracle) @@ -1051,7 +1008,7 @@ class HasUserContext(GenericHandler): # need to find good reference about this. ##@classmethod ##def bitsize(cls, **kwds): - ## info = super(HasUserContext, cls).bitsize(**kwds) + ## info = super().bitsize(**kwds) ## info['user'] = xxx ## return info @@ -1097,7 +1054,7 @@ class HasManyIdents(GenericHandler): #=================================================================== # class attrs #=================================================================== - default_ident = None # should be unicode + default_ident = None # should be str ident_values = None # should be list of unicode strings ident_aliases = None # should be dict of unicode -> unicode # NOTE: any aliases provided to norm_ident() as bytes @@ -1134,7 +1091,7 @@ class HasManyIdents(GenericHandler): default_ident = ident # create subclass - subcls = super(HasManyIdents, cls).using(**kwds) + subcls = super().using(**kwds) # add custom default ident # (NOTE: creates instance to run value through _norm_ident()) @@ -1146,7 +1103,7 @@ class HasManyIdents(GenericHandler): # init #=================================================================== def __init__(self, ident=None, **kwds): - super(HasManyIdents, self).__init__(**kwds) + super().__init__(**kwds) # init ident if ident is not None: @@ -1333,12 +1290,12 @@ class HasSalt(GenericHandler): default_salt_size = salt_size # generate new subclass - subcls = super(HasSalt, cls).using(**kwds) + subcls = super().using(**kwds) # replace default_rounds relaxed = kwds.get("relaxed") if default_salt_size is not None: - if isinstance(default_salt_size, native_string_types): + if isinstance(default_salt_size, str): default_salt_size = int(default_salt_size) subcls.default_salt_size = subcls._clip_to_valid_salt_size(default_salt_size, param="salt_size", @@ -1406,7 +1363,7 @@ class HasSalt(GenericHandler): # init #=================================================================== def __init__(self, salt=None, **kwds): - super(HasSalt, self).__init__(**kwds) + super().__init__(**kwds) if salt is not None: salt = self._parse_salt(salt) elif self.use_defaults: @@ -1446,12 +1403,12 @@ class HasSalt(GenericHandler): if not isinstance(salt, bytes): raise exc.ExpectedTypeError(salt, "bytes", "salt") else: - if not isinstance(salt, unicode): + if not isinstance(salt, str): # NOTE: allowing bytes under py2 so salt can be native str. - if isinstance(salt, bytes) and (PY2 or relaxed): + if relaxed and isinstance(salt, bytes): salt = salt.decode("ascii") else: - raise exc.ExpectedTypeError(salt, "unicode", "salt") + raise exc.ExpectedTypeError(salt, "str", "salt") # check charset sc = cls.salt_chars @@ -1495,7 +1452,7 @@ class HasSalt(GenericHandler): @classmethod def bitsize(cls, salt_size=None, **kwds): """[experimental method] return info about bitsizes of hash""" - info = super(HasSalt, cls).bitsize(**kwds) + info = super().bitsize(**kwds) if salt_size is None: salt_size = cls.default_salt_size # FIXME: this may overestimate size due to padding bits @@ -1650,7 +1607,7 @@ class HasRounds(GenericHandler): default_rounds = rounds # generate new subclass - subcls = super(HasRounds, cls).using(**kwds) + subcls = super().using(**kwds) # replace min_desired_rounds relaxed = kwds.get("relaxed") @@ -1659,7 +1616,7 @@ class HasRounds(GenericHandler): min_desired_rounds = cls.min_desired_rounds else: explicit_min_rounds = True - if isinstance(min_desired_rounds, native_string_types): + if isinstance(min_desired_rounds, str): min_desired_rounds = int(min_desired_rounds) subcls.min_desired_rounds = subcls._norm_rounds(min_desired_rounds, param="min_desired_rounds", @@ -1669,7 +1626,7 @@ class HasRounds(GenericHandler): if max_desired_rounds is None: max_desired_rounds = cls.max_desired_rounds else: - if isinstance(max_desired_rounds, native_string_types): + if isinstance(max_desired_rounds, str): max_desired_rounds = int(max_desired_rounds) if min_desired_rounds and max_desired_rounds < min_desired_rounds: msg = "%s: max_desired_rounds (%r) below min_desired_rounds (%r)" % \ @@ -1685,7 +1642,7 @@ class HasRounds(GenericHandler): # replace default_rounds if default_rounds is not None: - if isinstance(default_rounds, native_string_types): + if isinstance(default_rounds, str): default_rounds = int(default_rounds) if min_desired_rounds and default_rounds < min_desired_rounds: raise ValueError("%s: default_rounds (%r) below min_desired_rounds (%r)" % @@ -1703,7 +1660,7 @@ class HasRounds(GenericHandler): # replace / set vary_rounds if vary_rounds is not None: - if isinstance(vary_rounds, native_string_types): + if isinstance(vary_rounds, str): if vary_rounds.endswith("%"): vary_rounds = float(vary_rounds[:-1]) * 0.01 elif "." in vary_rounds: @@ -1782,7 +1739,7 @@ class HasRounds(GenericHandler): vary_rounds = int(default_rounds * vary_rounds) # calculate bounds based on default_rounds +/- vary_rounds - assert vary_rounds >= 0 and isinstance(vary_rounds, int_types) + assert vary_rounds >= 0 and isinstance(vary_rounds, int) lower = linear_to_native(default_rounds - vary_rounds, False) upper = linear_to_native(default_rounds + vary_rounds, True) return cls._clip_to_desired_rounds(lower), cls._clip_to_desired_rounds(upper) @@ -1791,7 +1748,7 @@ class HasRounds(GenericHandler): # init #=================================================================== def __init__(self, rounds=None, **kwds): - super(HasRounds, self).__init__(**kwds) + super().__init__(**kwds) if rounds is not None: rounds = self._parse_rounds(rounds) elif self.use_defaults: @@ -1873,7 +1830,7 @@ class HasRounds(GenericHandler): max_desired_rounds = self.max_desired_rounds if max_desired_rounds and self.rounds > max_desired_rounds: return True - return super(HasRounds, self)._calc_needs_update(**kwds) + return super()._calc_needs_update(**kwds) #=================================================================== # experimental methods @@ -1881,7 +1838,7 @@ class HasRounds(GenericHandler): @classmethod def bitsize(cls, rounds=None, vary_rounds=.1, **kwds): """[experimental method] return info about bitsizes of hash""" - info = super(HasRounds, cls).bitsize(**kwds) + info = super().bitsize(**kwds) # NOTE: this essentially estimates how many bits of "salt" # can be added by varying the rounds value just a little bit. if cls.rounds_cost != "log2": @@ -1930,9 +1887,9 @@ class ParallelismMixin(GenericHandler): @classmethod def using(cls, parallelism=None, **kwds): - subcls = super(ParallelismMixin, cls).using(**kwds) + subcls = super().using(**kwds) if parallelism is not None: - if isinstance(parallelism, native_string_types): + if isinstance(parallelism, str): parallelism = int(parallelism) subcls.parallelism = subcls._norm_parallelism(parallelism, relaxed=kwds.get("relaxed")) return subcls @@ -1941,7 +1898,7 @@ class ParallelismMixin(GenericHandler): # init #=================================================================== def __init__(self, parallelism=None, **kwds): - super(ParallelismMixin, self).__init__(**kwds) + super().__init__(**kwds) # init parallelism if parallelism is None: @@ -1965,7 +1922,7 @@ class ParallelismMixin(GenericHandler): # XXX: for now, marking all hashes which don't have matching parallelism setting if self.parallelism != type(self).parallelism: return True - return super(ParallelismMixin, self)._calc_needs_update(**kwds) + return super()._calc_needs_update(**kwds) #=================================================================== # eoc @@ -2308,7 +2265,7 @@ class SubclassBackendMixin(BackendMixin): @classmethod def _set_backend(cls, name, dryrun): # invoke backend loader (will throw error if fails) - super(SubclassBackendMixin, cls)._set_backend(name, dryrun) + super()._set_backend(name, dryrun) # sanity check call args (should trust .set_backend, but will really # foul things up if this isn't the owner) @@ -2486,7 +2443,7 @@ class PrefixWrapper(object): #: list of attributes which should be cloned by .using() _using_clone_attrs = () - def __init__(self, name, wrapped, prefix=u(''), orig_prefix=u(''), lazy=False, + def __init__(self, name, wrapped, prefix=u'', orig_prefix=u'', lazy=False, doc=None, ident=None): self.name = name if isinstance(prefix, bytes): @@ -2652,7 +2609,7 @@ class PrefixWrapper(object): if not hash.startswith(orig_prefix): raise exc.InvalidHashError(self.wrapped) wrapped = self.prefix + hash[len(orig_prefix):] - return uascii_to_str(wrapped) + return wrapped #: set by _using(), helper for test harness' handler_derived_from() _derived_from = None diff --git a/passlib/utils/pbkdf2.py b/passlib/utils/pbkdf2.py index 273143b..d9bd083 100644 --- a/passlib/utils/pbkdf2.py +++ b/passlib/utils/pbkdf2.py @@ -6,20 +6,14 @@ maybe rename to "kdf" since it's getting more key derivation functions added. #============================================================================= # imports #============================================================================= -from __future__ import division # core import logging; log = logging.getLogger(__name__) # site # pkg from passlib.exc import ExpectedTypeError -from passlib.utils.decor import deprecated_function -from passlib.utils.compat import native_string_types -from passlib.crypto.digest import norm_hash_name, lookup_hash, pbkdf1 as _pbkdf1, pbkdf2_hmac, compile_hmac +from passlib.crypto.digest import lookup_hash, pbkdf1 as _pbkdf1, pbkdf2_hmac, compile_hmac # local __all__ = [ - # hash utils - "norm_hash_name", - # prf utils "get_prf", @@ -38,13 +32,6 @@ warn("the module 'passlib.utils.pbkdf2' is deprecated as of Passlib 1.7, " DeprecationWarning) #============================================================================= -# hash helpers -#============================================================================= - -norm_hash_name = deprecated_function(deprecated="1.7", removed="1.8", func_module=__name__, - replacement="passlib.crypto.digest.norm_hash_name")(norm_hash_name) - -#============================================================================= # prf lookup #============================================================================= @@ -97,7 +84,7 @@ def get_prf(name): global _prf_cache if name in _prf_cache: return _prf_cache[name] - if isinstance(name, native_string_types): + if isinstance(name, str): if not name.startswith(_HMAC_PREFIXES): raise ValueError("unknown prf algorithm: %r" % (name,)) digest = lookup_hash(name[5:]).name @@ -183,7 +170,7 @@ def pbkdf2(secret, salt, rounds, keylen=None, prf="hmac-sha1"): This has been deprecated in favor of :func:`passlib.crypto.digest.pbkdf2_hmac`, and will be removed in Passlib 2.0. *Note the call signature has changed.* """ - if callable(prf) or (isinstance(prf, native_string_types) and not prf.startswith(_HMAC_PREFIXES)): + if callable(prf) or (isinstance(prf, str) and not prf.startswith(_HMAC_PREFIXES)): raise NotImplementedError("non-HMAC prfs are not supported as of Passlib 1.7") digest = prf[5:] return pbkdf2_hmac(digest, secret, salt, rounds, keylen) |