diff options
Diffstat (limited to 'passlib/totp.py')
-rw-r--r-- | passlib/totp.py | 74 |
1 files changed, 27 insertions, 47 deletions
diff --git a/passlib/totp.py b/passlib/totp.py index 9ad5000..a133354 100644 --- a/passlib/totp.py +++ b/passlib/totp.py @@ -2,8 +2,6 @@ #============================================================================= # imports #============================================================================= -from __future__ import absolute_import, division, print_function -from passlib.utils.compat import PY3 # core import base64 import calendar @@ -14,11 +12,7 @@ import struct import sys import time as _time import re -if PY3: - from urllib.parse import urlparse, parse_qsl, quote, unquote -else: - from urllib import quote, unquote - from urlparse import urlparse, parse_qsl +from urllib.parse import urlparse, parse_qsl, quote, unquote from warnings import warn # site try: @@ -37,8 +31,7 @@ from passlib.exc import TokenError, MalformedTokenError, InvalidTokenError, Used from passlib.utils import (to_unicode, to_bytes, consteq, getrandbytes, rng, SequenceMixin, xor_bytes, getrandstr) from passlib.utils.binary import BASE64_CHARS, b32encode, b32decode -from passlib.utils.compat import (u, unicode, native_string_types, bascii_to_str, int_types, num_types, - irange, byte_elem_value, UnicodeIO, suppress_cause) +from passlib.utils.compat import bascii_to_str, num_types from passlib.utils.decor import hybrid_method, memoized_property from passlib.crypto.digest import lookup_hash, compile_hmac, pbkdf2_hmac from passlib.hash import pbkdf2_sha256 @@ -60,20 +53,6 @@ __all__ = [ ] #============================================================================= -# HACK: python < 2.7.4's urlparse() won't parse query strings unless the url scheme -# is one of the schemes in the urlparse.uses_query list. 2.7 abandoned -# this, and parses query if present, regardless of the scheme. -# as a workaround for older versions, we add "otpauth" to the known list. -# this was fixed by https://bugs.python.org/issue9374, in 2.7.4 release. -#============================================================================= -if sys.version_info < (2,7,4): - from urlparse import uses_query - if "otpauth" not in uses_query: - uses_query.append("otpauth") - log.debug("registered 'otpauth' scheme with urlparse.uses_query") - del uses_query - -#============================================================================= # internal helpers #============================================================================= @@ -82,7 +61,7 @@ if sys.version_info < (2,7,4): #----------------------------------------------------------------------------- #: regex used to clean whitespace from tokens & keys -_clean_re = re.compile(u(r"\s|[-=]"), re.U) +_clean_re = re.compile(r"\s|[-=]", re.U) _chunk_sizes = [4,6,5] @@ -112,7 +91,7 @@ def group_string(value, sep="-"): """ klen = len(value) size = _get_group_size(klen) - return sep.join(value[o:o+size] for o in irange(0, klen, size)) + return sep.join(value[o:o+size] for o in range(0, klen, size)) #----------------------------------------------------------------------------- # encoding helpers @@ -269,7 +248,7 @@ class AppWallet(object): # init cost # if encrypt_cost is not None: - if isinstance(encrypt_cost, native_string_types): + if isinstance(encrypt_cost, str): encrypt_cost = int(encrypt_cost) assert encrypt_cost >= 0 self.encrypt_cost = encrypt_cost @@ -311,7 +290,7 @@ class AppWallet(object): # to make this easy to pass in configuration from a separate file, # 'secrets' can be string using two formats -- json & "tag:value\n" check_type = True - if isinstance(source, native_string_types): + if isinstance(source, str): if source.lstrip().startswith(("[", "{")): # json list / dict source = json.loads(source) @@ -344,12 +323,12 @@ class AppWallet(object): for tag, value in source) def _parse_secret_pair(self, tag, value): - if isinstance(tag, native_string_types): + if isinstance(tag, str): pass elif isinstance(tag, int): tag = str(tag) else: - raise TypeError("tag must be unicode/string: %r" % (tag,)) + raise TypeError("tag must be string: %r" % (tag,)) if not _tag_re.match(tag): raise ValueError("tag contains invalid characters: %r" % (tag,)) if not isinstance(value, bytes): @@ -378,7 +357,7 @@ class AppWallet(object): try: return secrets[tag] except KeyError: - raise suppress_cause(KeyError("unknown secret tag: %r" % (tag,))) + raise KeyError("unknown secret tag: %r" % (tag,)) from None #======================================================================== # encrypted key helpers -- used internally by TOTP @@ -772,7 +751,7 @@ class TOTP(object): new=False, digits=None, alg=None, size=None, period=None, label=None, issuer=None, changed=False, **kwds): - super(TOTP, self).__init__(**kwds) + super().__init__(**kwds) if changed: self.changed = changed @@ -819,7 +798,7 @@ class TOTP(object): # validate digits if digits is None: digits = self.digits - if not isinstance(digits, int_types): + if not isinstance(digits, int): raise TypeError("digits must be an integer, not a %r" % type(digits)) if digits < 6 or digits > 10: raise ValueError("digits must in range(6,11)") @@ -849,7 +828,7 @@ class TOTP(object): """ check that serial value (e.g. 'counter') is non-negative integer """ - if not isinstance(value, int_types): + if not isinstance(value, int): raise exc.ExpectedTypeError(value, "int", param) if value < minval: raise ValueError("%s must be >= %d" % (param, minval)) @@ -995,7 +974,7 @@ class TOTP(object): :returns: unix epoch timestamp as :class:`int`. """ - if isinstance(time, int_types): + if isinstance(time, int): return time elif isinstance(time, float): return int(time) @@ -1034,20 +1013,20 @@ class TOTP(object): or use the class default. :arg token: - token as ascii bytes, unicode, or an integer. + token as ascii bytes, str, or an integer. :raises ValueError: if token has wrong number of digits, or contains non-numeric characters. :returns: - token as :class:`!unicode` string, containing only digits 0-9. + token as :class:`!str`, containing only digits 0-9. """ digits = self_or_cls.digits - if isinstance(token, int_types): - token = u("%0*d") % (digits, token) + if isinstance(token, int): + token = u"%0*d" % (digits, token) else: token = to_unicode(token, param="token") - token = _clean_re.sub(u(""), token) + token = _clean_re.sub(u"", token) if not token.isdigit(): raise MalformedTokenError("Token must contain only the digits 0-9") if len(token) != digits: @@ -1110,7 +1089,7 @@ class TOTP(object): :returns: token as unicode string """ # generate digest - assert isinstance(counter, int_types), "counter must be integer" + assert isinstance(counter, int), "counter must be integer" assert counter >= 0, "counter must be non-negative" keyed_hmac = self._keyed_hmac if keyed_hmac is None: @@ -1120,8 +1099,9 @@ class TOTP(object): assert len(digest) == digest_size, "digest_size: sanity check failed" # derive 31-bit token value + # assert isinstance(digest, bytes) assert digest_size >= 20, "digest_size: sanity check 2 failed" # otherwise 0xF+4 will run off end of hash. - offset = byte_elem_value(digest[-1]) & 0xF + offset = digest[-1] & 0xF value = _unpack_uint32(digest[offset:offset+4])[0] & 0x7fffffff # render to decimal string, return last <digits> chars @@ -1130,7 +1110,7 @@ class TOTP(object): # if 31-bit mask removed (which breaks spec), would only get values 0-4. digits = self.digits assert 0 < digits < 11, "digits: sanity check failed" - return (u("%0*d") % (digits, value))[-digits:] + return (u"%0*d" % (digits, value))[-digits:] #============================================================================= # token verification @@ -1293,8 +1273,8 @@ class TOTP(object): # XXX: if (end - start) is very large (e.g. for resync purposes), # could start with expected value, and work outward from there, # alternately checking before & after it until match is found. - # XXX: can't use irange(start, end) here since py2x/win32 - # throws error on values >= (1<<31), which 'end' can be. + # TODO: replace counter loop with "for counter in range(start, end)"; + # think this was holding from PY2+win32 issue with values > 32 bit (e.g. 'end'). counter = start while counter < end: if consteq(token, generate(counter)): @@ -1550,11 +1530,11 @@ class TOTP(object): # NOTE: not using urllib.urlencode() because it encodes ' ' as '+'; # but spec says to use '%20', and not sure how fragile # the various totp clients' parsers are. - param_str = u("&").join(u("%s=%s") % (key, quote(value, '')) for key, value in params) + param_str = u"&".join(u"%s=%s" % (key, quote(value, '')) for key, value in params) assert param_str, "param_str should never be empty" # render uri - return u("otpauth://totp/%s?%s") % (label, param_str) + return u"otpauth://totp/%s?%s" % (label, param_str) def _to_uri_params(self): """return list of (key, param) entries for URI""" @@ -1578,7 +1558,7 @@ class TOTP(object): (as generated by :meth:`to_json`). :arg json: - Serialized output from :meth:`to_json`, as unicode or ascii bytes. + Serialized output from :meth:`to_json`, as str or ascii bytes. :raises ValueError: If the key has been encrypted, but the application secret isn't available; |