summaryrefslogtreecommitdiff
path: root/passlib/totp.py
diff options
context:
space:
mode:
Diffstat (limited to 'passlib/totp.py')
-rw-r--r--passlib/totp.py74
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;