[Python-modules-commits] [python-pyotp] 01/03: Imported Upstream version 2.2.1

Hugo Lefeuvre hle at moszumanska.debian.org
Fri Sep 9 16:46:15 UTC 2016


This is an automated email from the git hooks/post-receive script.

hle pushed a commit to branch master
in repository python-pyotp.

commit 5cfd91b036162b0dd6d189943de55255841b2499
Author: Hugo Lefeuvre <hle at debian.org>
Date:   Fri Sep 9 17:41:48 2016 +0200

    Imported Upstream version 2.2.1
---
 LICENSE                         |   7 +-
 PKG-INFO                        |  12 ++-
 README.rst                      |  10 +--
 requirements.txt                |   1 -
 setup.cfg                       |   4 +
 setup.py                        |   7 +-
 src/pyotp.egg-info/PKG-INFO     |  12 ++-
 src/pyotp.egg-info/SOURCES.txt  |   2 +-
 src/pyotp.egg-info/requires.txt |   1 -
 src/pyotp/__init__.py           |  12 +--
 src/pyotp/compat.py             |  10 +++
 src/pyotp/hotp.py               |  10 ++-
 src/pyotp/otp.py                |  18 ++---
 src/pyotp/totp.py               |  21 ++---
 src/pyotp/utils.py              |  89 +++++++++++++---------
 test.py                         | 164 ++++++++++++++++++++++++++++++++--------
 16 files changed, 256 insertions(+), 124 deletions(-)

diff --git a/LICENSE b/LICENSE
index c6b2e22..6e2abc1 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,5 @@
-Copyright (C) 2011 by Mark Percival <m at mdp.im>
-
-Python port copyright 2011 by Nathan Reynolds <email at nreynolds.co.uk>
+Copyright (C) 2011-2016 Mark Percival <m at mdp.im>,
+Nathan Reynolds <email at nreynolds.co.uk>, and PyOTP contributors
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +17,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
\ No newline at end of file
+THE SOFTWARE.
diff --git a/PKG-INFO b/PKG-INFO
index 301da3e..1a62fa1 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: pyotp
-Version: 2.1.1
+Version: 2.2.1
 Summary: Python One Time Password Library
 Home-page: https://github.com/pyotp/pyotp
 Author: PyOTP contributors
@@ -84,13 +84,13 @@ Description: PyOTP - The Python One-Time Password Library
         
         Scan the following barcode with your phone, using Google Authenticator
         
-        ![QR Code for OTP](http://chart.apis.google.com/chart?cht=qr&chs=250x250&chl=otpauth%3A%2F%2Ftotp%2Falice%40google.com%3Fsecret%3DJBSWY3DPEHPK3PXP)
+        .. image:: http://chart.apis.google.com/chart?cht=qr&chs=250x250&chl=otpauth%3A%2F%2Ftotp%2Falice%40google.com%3Fsecret%3DJBSWY3DPEHPK3PXP
         
         Now run the following and compare the output::
         
             import pyotp
             totp = pyotp.TOTP("JBSWY3DPEHPK3PXP")
-            print "Current OTP: %s" % totp.now()
+            print("Current OTP:", totp.now())
         
         Links
         ~~~~~
@@ -106,12 +106,10 @@ Description: PyOTP - The Python One-Time Password Library
         
         .. image:: https://img.shields.io/travis/pyotp/pyotp.svg
                 :target: https://travis-ci.org/pyotp/pyotp
-        .. image:: https://img.shields.io/coveralls/pyotp/pyotp.svg
-                :target: https://coveralls.io/r/pyotp/pyotp?branch=master
+        .. image:: https://img.shields.io/codecov/c/github/pyotp/pyotp/master.svg
+                :target: https://codecov.io/github/pyotp/pyotp?branch=master
         .. image:: https://img.shields.io/pypi/v/pyotp.svg
                 :target: https://pypi.python.org/pypi/pyotp
-        .. image:: https://img.shields.io/pypi/dm/pyotp.svg
-                :target: https://pypi.python.org/pypi/pyotp
         .. image:: https://img.shields.io/pypi/l/pyotp.svg
                 :target: https://pypi.python.org/pypi/pyotp
         .. image:: https://readthedocs.org/projects/pyotp/badge/?version=latest
diff --git a/README.rst b/README.rst
index 8d89f0b..7c5d90a 100644
--- a/README.rst
+++ b/README.rst
@@ -76,13 +76,13 @@ Working example
 
 Scan the following barcode with your phone, using Google Authenticator
 
-![QR Code for OTP](http://chart.apis.google.com/chart?cht=qr&chs=250x250&chl=otpauth%3A%2F%2Ftotp%2Falice%40google.com%3Fsecret%3DJBSWY3DPEHPK3PXP)
+.. image:: http://chart.apis.google.com/chart?cht=qr&chs=250x250&chl=otpauth%3A%2F%2Ftotp%2Falice%40google.com%3Fsecret%3DJBSWY3DPEHPK3PXP
 
 Now run the following and compare the output::
 
     import pyotp
     totp = pyotp.TOTP("JBSWY3DPEHPK3PXP")
-    print "Current OTP: %s" % totp.now()
+    print("Current OTP:", totp.now())
 
 Links
 ~~~~~
@@ -98,12 +98,10 @@ Links
 
 .. image:: https://img.shields.io/travis/pyotp/pyotp.svg
         :target: https://travis-ci.org/pyotp/pyotp
-.. image:: https://img.shields.io/coveralls/pyotp/pyotp.svg
-        :target: https://coveralls.io/r/pyotp/pyotp?branch=master
+.. image:: https://img.shields.io/codecov/c/github/pyotp/pyotp/master.svg
+        :target: https://codecov.io/github/pyotp/pyotp?branch=master
 .. image:: https://img.shields.io/pypi/v/pyotp.svg
         :target: https://pypi.python.org/pypi/pyotp
-.. image:: https://img.shields.io/pypi/dm/pyotp.svg
-        :target: https://pypi.python.org/pypi/pyotp
 .. image:: https://img.shields.io/pypi/l/pyotp.svg
         :target: https://pypi.python.org/pypi/pyotp
 .. image:: https://readthedocs.org/projects/pyotp/badge/?version=latest
diff --git a/requirements.txt b/requirements.txt
index 970c6de..e69de29 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1 +0,0 @@
-future==0.15.2
diff --git a/setup.cfg b/setup.cfg
index 6f08d0e..4644c25 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,10 @@
 [bdist_wheel]
 universal = 1
 
+[flake8]
+max-line-length = 120
+ignore = E301, E302, E401, E226, F841
+
 [egg_info]
 tag_build = 
 tag_date = 0
diff --git a/setup.py b/setup.py
index 1d7ed2c..27cf6a3 100755
--- a/setup.py
+++ b/setup.py
@@ -1,13 +1,13 @@
 #!/usr/bin/env python
 
-import os, glob
-from setuptools import setup, find_packages
+import os
+from setuptools import setup
 
 install_requires = [line.rstrip() for line in open(os.path.join(os.path.dirname(__file__), "requirements.txt"))]
 
 setup(
     name='pyotp',
-    version='2.1.1',
+    version='2.2.1',
     url='https://github.com/pyotp/pyotp',
     license='BSD License',
     author='PyOTP contributors',
@@ -19,6 +19,7 @@ setup(
     package_dir={'': 'src'},
     platforms=['MacOS X', 'Posix'],
     zip_safe=False,
+    test_suite="test",
     classifiers=[
         'Intended Audience :: Developers',
         'License :: OSI Approved :: BSD License',
diff --git a/src/pyotp.egg-info/PKG-INFO b/src/pyotp.egg-info/PKG-INFO
index 301da3e..1a62fa1 100644
--- a/src/pyotp.egg-info/PKG-INFO
+++ b/src/pyotp.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: pyotp
-Version: 2.1.1
+Version: 2.2.1
 Summary: Python One Time Password Library
 Home-page: https://github.com/pyotp/pyotp
 Author: PyOTP contributors
@@ -84,13 +84,13 @@ Description: PyOTP - The Python One-Time Password Library
         
         Scan the following barcode with your phone, using Google Authenticator
         
-        ![QR Code for OTP](http://chart.apis.google.com/chart?cht=qr&chs=250x250&chl=otpauth%3A%2F%2Ftotp%2Falice%40google.com%3Fsecret%3DJBSWY3DPEHPK3PXP)
+        .. image:: http://chart.apis.google.com/chart?cht=qr&chs=250x250&chl=otpauth%3A%2F%2Ftotp%2Falice%40google.com%3Fsecret%3DJBSWY3DPEHPK3PXP
         
         Now run the following and compare the output::
         
             import pyotp
             totp = pyotp.TOTP("JBSWY3DPEHPK3PXP")
-            print "Current OTP: %s" % totp.now()
+            print("Current OTP:", totp.now())
         
         Links
         ~~~~~
@@ -106,12 +106,10 @@ Description: PyOTP - The Python One-Time Password Library
         
         .. image:: https://img.shields.io/travis/pyotp/pyotp.svg
                 :target: https://travis-ci.org/pyotp/pyotp
-        .. image:: https://img.shields.io/coveralls/pyotp/pyotp.svg
-                :target: https://coveralls.io/r/pyotp/pyotp?branch=master
+        .. image:: https://img.shields.io/codecov/c/github/pyotp/pyotp/master.svg
+                :target: https://codecov.io/github/pyotp/pyotp?branch=master
         .. image:: https://img.shields.io/pypi/v/pyotp.svg
                 :target: https://pypi.python.org/pypi/pyotp
-        .. image:: https://img.shields.io/pypi/dm/pyotp.svg
-                :target: https://pypi.python.org/pypi/pyotp
         .. image:: https://img.shields.io/pypi/l/pyotp.svg
                 :target: https://pypi.python.org/pypi/pyotp
         .. image:: https://readthedocs.org/projects/pyotp/badge/?version=latest
diff --git a/src/pyotp.egg-info/SOURCES.txt b/src/pyotp.egg-info/SOURCES.txt
index 2ded9a4..e1e9677 100644
--- a/src/pyotp.egg-info/SOURCES.txt
+++ b/src/pyotp.egg-info/SOURCES.txt
@@ -6,6 +6,7 @@ setup.cfg
 setup.py
 test.py
 src/pyotp/__init__.py
+src/pyotp/compat.py
 src/pyotp/hotp.py
 src/pyotp/otp.py
 src/pyotp/totp.py
@@ -14,5 +15,4 @@ src/pyotp.egg-info/PKG-INFO
 src/pyotp.egg-info/SOURCES.txt
 src/pyotp.egg-info/dependency_links.txt
 src/pyotp.egg-info/not-zip-safe
-src/pyotp.egg-info/requires.txt
 src/pyotp.egg-info/top_level.txt
\ No newline at end of file
diff --git a/src/pyotp.egg-info/requires.txt b/src/pyotp.egg-info/requires.txt
deleted file mode 100644
index 970c6de..0000000
--- a/src/pyotp.egg-info/requires.txt
+++ /dev/null
@@ -1 +0,0 @@
-future==0.15.2
diff --git a/src/pyotp/__init__.py b/src/pyotp/__init__.py
index deafdc6..35b6509 100644
--- a/src/pyotp/__init__.py
+++ b/src/pyotp/__init__.py
@@ -1,11 +1,13 @@
-from __future__ import print_function, unicode_literals, division, absolute_import
+from __future__ import (absolute_import, division,
+                        print_function, unicode_literals)
 
 import random as _random
 
-from pyotp.hotp import HOTP
-from pyotp.otp import OTP
-from pyotp.totp import TOTP
-from . import utils
+from pyotp.hotp import HOTP  # noqa
+from pyotp.otp import OTP  # noqa
+from pyotp.totp import TOTP  # noqa
+from . import utils  # noqa
+
 
 def random_base32(length=16, random=_random.SystemRandom(),
                   chars=list('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567')):
diff --git a/src/pyotp/compat.py b/src/pyotp/compat.py
new file mode 100644
index 0000000..5649909
--- /dev/null
+++ b/src/pyotp/compat.py
@@ -0,0 +1,10 @@
+from __future__ import absolute_import, division, print_function, unicode_literals
+
+import sys
+
+USING_PYTHON2 = True if sys.version_info < (3, 0) else False
+
+if USING_PYTHON2:
+    str = unicode  # noqa
+else:
+    str = str
diff --git a/src/pyotp/hotp.py b/src/pyotp/hotp.py
index 603e8a5..40ddcfa 100644
--- a/src/pyotp/hotp.py
+++ b/src/pyotp/hotp.py
@@ -1,8 +1,8 @@
-from __future__ import print_function, unicode_literals, division, absolute_import
+from __future__ import absolute_import, division, print_function, unicode_literals
 
-from pyotp.otp import OTP
-from pyotp import utils
-from future.builtins import str
+from . import utils
+from .otp import OTP
+from .compat import str
 
 class HOTP(OTP):
     def at(self, count):
@@ -37,4 +37,6 @@ class HOTP(OTP):
             name,
             initial_count=initial_count,
             issuer_name=issuer_name,
+            algorithm=self.digest().name,
+            digits=self.digits
         )
diff --git a/src/pyotp/otp.py b/src/pyotp/otp.py
index f3144d1..f0fe49f 100644
--- a/src/pyotp/otp.py
+++ b/src/pyotp/otp.py
@@ -1,9 +1,9 @@
-from __future__ import print_function, unicode_literals, division, absolute_import
+from __future__ import absolute_import, division, print_function, unicode_literals
 
 import base64
 import hashlib
 import hmac
-from future.builtins import str
+from .compat import str
 
 class OTP(object):
     def __init__(self, s, digits=6, digest=hashlib.sha1):
@@ -27,13 +27,8 @@ class OTP(object):
         Usually either the counter, or the computed integer
         based on the Unix timestamp
         """
-        hmac_hash = hmac.new(
-            self.byte_secret(),
-            self.int_to_bytestring(input),
-            self.digest,
-        ).digest()
-
-        hmac_hash = bytearray(hmac_hash)
+        hasher = hmac.new(self.byte_secret(), self.int_to_bytestring(input), self.digest)
+        hmac_hash = bytearray(hasher.digest())
         offset = hmac_hash[-1] & 0xf
         code = ((hmac_hash[offset] & 0x7f) << 24 |
                 (hmac_hash[offset + 1] & 0xff) << 16 |
@@ -62,6 +57,7 @@ class OTP(object):
         while i != 0:
             result.append(i & 0xFF)
             i >>= 8
-        # It's necessary to convert the final result from bytearray to bytes because
-        # the hmac functions in python 2.6 and 3.3 don't work with bytearray
+        # It's necessary to convert the final result from bytearray to bytes
+        # because the hmac functions in python 2.6 and 3.3 don't work with
+        # bytearray
         return bytes(bytearray(reversed(result)).rjust(padding, b'\0'))
diff --git a/src/pyotp/totp.py b/src/pyotp/totp.py
index 3026fe0..27f81b8 100644
--- a/src/pyotp/totp.py
+++ b/src/pyotp/totp.py
@@ -1,11 +1,11 @@
-from __future__ import print_function, unicode_literals, division, absolute_import
+from __future__ import absolute_import, division, print_function, unicode_literals
 
 import datetime
 import time
 
-from pyotp import utils
-from pyotp.otp import OTP
-from future.builtins import str
+from . import utils
+from .otp import OTP
+from .compat import str
 
 class TOTP(OTP):
     def __init__(self, *args, **kwargs):
@@ -19,9 +19,9 @@ class TOTP(OTP):
     def at(self, for_time, counter_offset=0):
         """
         Accepts either a Unix timestamp integer or a Time object.
-        Time objects will be adjusted to UTC automatically
         @param [Time/Integer] time the time to generate an OTP for
-        @param [Integer] counter_offset an amount of ticks to add to the time counter 
+        @param [Integer] counter_offset an amount of ticks to add to the time
+            counter
         """
         if not isinstance(for_time, datetime.datetime):
             for_time = datetime.datetime.fromtimestamp(int(for_time))
@@ -38,11 +38,12 @@ class TOTP(OTP):
         """
         Verifies the OTP passed in against the current time OTP
         @param [String/Integer] otp the OTP to check against
-        @param [Integer] valid_window extends the validity to this many counter ticks before and after the current one
+        @param [Integer] valid_window extends the validity to this many counter
+            ticks before and after the current one
         """
         if for_time is None:
             for_time = datetime.datetime.now()
-        
+
         if valid_window:
             for i in range(-valid_window, valid_window + 1):
                 if utils.strings_equal(str(otp), str(self.at(for_time, i))):
@@ -59,7 +60,9 @@ class TOTP(OTP):
         @param [String] name of the account
         @return [String] provisioning uri
         """
-        return utils.build_uri(self.secret, name, issuer_name=issuer_name)
+        return utils.build_uri(self.secret, name, issuer_name=issuer_name,
+                               algorithm=self.digest().name,
+                               digits=self.digits, period=self.interval)
 
     def timecode(self, for_time):
         i = time.mktime(for_time.timetuple())
diff --git a/src/pyotp/utils.py b/src/pyotp/utils.py
index f816703..16a13d5 100644
--- a/src/pyotp/utils.py
+++ b/src/pyotp/utils.py
@@ -1,13 +1,19 @@
-from __future__ import print_function, unicode_literals, division, absolute_import
+from __future__ import absolute_import, division, print_function, unicode_literals
 
 import unicodedata
+try:
+    from itertools import izip_longest
+except ImportError:
+    from itertools import zip_longest as izip_longest
 
 try:
-    from urllib.parse import quote
+    from urllib.parse import quote, urlencode
 except ImportError:
-    from urllib import quote
+    from urllib import quote, urlencode
+
 
-def build_uri(secret, name, initial_count=None, issuer_name=None):
+def build_uri(secret, name, initial_count=None, issuer_name=None,
+              algorithm=None, digits=None, period=None):
     """
     Returns the provisioning URI for the OTP; works for either TOTP or HOTP.
 
@@ -17,7 +23,7 @@ def build_uri(secret, name, initial_count=None, issuer_name=None):
     For module-internal use.
 
     See also:
-        http://code.google.com/p/google-authenticator/wiki/KeyUriFormat
+        https://github.com/google/google-authenticator/wiki/Key-Uri-Format
 
     @param [String] the hotp/totp secret used to generate the URI
     @param [String] name of the account
@@ -25,31 +31,60 @@ def build_uri(secret, name, initial_count=None, issuer_name=None):
         If none, the OTP type will be assumed as TOTP.
     @param [String] the name of the OTP issuer; this will be the
         organization title of the OTP entry in Authenticator
+    @param [String] the algorithm used in the OTP generation.
+    @param [Integer] the length of the OTP generated code.
+    @param [Integer] the number of seconds the OTP generator is set to
+        expire every code.
     @return [String] provisioning uri
     """
     # initial_count may be 0 as a valid param
     is_initial_count_present = (initial_count is not None)
 
+    # Handling values different from defaults
+    is_algorithm_set = (algorithm is not None and algorithm != 'sha1')
+    is_digits_set = (digits is not None and digits != 6)
+    is_period_set = (period is not None and period != 30)
+
     otp_type = 'hotp' if is_initial_count_present else 'totp'
-    base = 'otpauth://%s/' % otp_type
+    base_uri = 'otpauth://{}/{}?{}'
 
-    if issuer_name:
-        issuer_name = quote(issuer_name)
-        base += '%s:' % issuer_name
+    url_args = {'secret': secret}
 
-    uri = '%(base)s%(name)s?secret=%(secret)s' % {
-        'name': quote(name, safe='@'),
-        'secret': secret,
-        'base': base,
-    }
+    label = quote(name)
+    if issuer_name is not None:
+        label = quote(issuer_name) + ':' + label
+        url_args['issuer'] = issuer_name
 
     if is_initial_count_present:
-        uri += '&counter=%s' % initial_count
+        url_args['counter'] = initial_count
+    if is_algorithm_set:
+        url_args['algorithm'] = algorithm.upper()
+    if is_digits_set:
+        url_args['digits'] = digits
+    if is_period_set:
+        url_args['period'] = period
+
+    uri = base_uri.format(otp_type, label, urlencode(url_args))
+    return uri
 
-    if issuer_name:
-        uri += '&issuer=%s' % issuer_name
 
-    return uri
+def _compare_digest(s1, s2):
+    differences = 0
+    for c1, c2 in izip_longest(s1, s2):
+        if c1 is None or c2 is None:
+            differences = 1
+            continue
+        differences |= ord(c1) ^ ord(c2)
+    return differences == 0
+
+try:
+    # Python 3.3+ and 2.7.7+ include a timing-attack-resistant
+    # comparison function, which is probably more reliable than ours.
+    # Use it if available.
+    from hmac import compare_digest
+
+except ImportError:
+    compare_digest = _compare_digest
 
 
 def strings_equal(s1, s2):
@@ -63,20 +98,4 @@ def strings_equal(s1, s2):
     """
     s1 = unicodedata.normalize('NFKC', s1)
     s2 = unicodedata.normalize('NFKC', s2)
-    try:
-        # Python 3.3+ and 2.7.7+ include a timing-attack-resistant
-        # comparison function, which is probably more reliable than ours.
-        # Use it if available.
-        from hmac import compare_digest
-
-        return compare_digest(s1, s2)
-    except ImportError:
-        pass
-
-    if len(s1) != len(s2):
-        return False
-
-    differences = 0
-    for c1, c2 in zip(s1, s2):
-        differences |= ord(c1) ^ ord(c2)
-    return differences == 0
+    return compare_digest(s1, s2)
diff --git a/test.py b/test.py
index 3615836..395d475 100755
--- a/test.py
+++ b/test.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 # coding: utf-8
 
-from __future__ import print_function, unicode_literals, division, absolute_import
+from __future__ import absolute_import, division, print_function, unicode_literals
 
 import base64
 import datetime
@@ -9,9 +9,13 @@ import hashlib
 import os
 import sys
 import unittest
+try:
+    from urllib.parse import urlparse, parse_qsl
+except ImportError:
+    from urlparse import urlparse, parse_qsl
 
 sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
-import pyotp
+import pyotp  # noqa
 
 class HOTPExampleValuesFromTheRFC(unittest.TestCase):
     def test_match_rfc(self):
@@ -38,26 +42,65 @@ class HOTPExampleValuesFromTheRFC(unittest.TestCase):
     def test_provisioning_uri(self):
         hotp = pyotp.HOTP('wrn3pqx5uqxqvnqr')
 
-        self.assertEqual(
-            hotp.provisioning_uri('mark at percival'),
-            'otpauth://hotp/mark@percival?secret=wrn3pqx5uqxqvnqr&counter=0')
-
-        self.assertEqual(
-            hotp.provisioning_uri('mark at percival', initial_count=12),
-            'otpauth://hotp/mark@percival?secret=wrn3pqx5uqxqvnqr&counter=12')
-
-        self.assertEqual(
-            hotp.provisioning_uri('mark at percival', issuer_name='FooCorp!'),
-            'otpauth://hotp/FooCorp%21:mark@percival?secret=wrn3pqx5uqxqvnqr&counter=0&issuer=FooCorp%21')
+        url = urlparse(
+            hotp.provisioning_uri('mark at percival'))
+        self.assertEqual(url.scheme, 'otpauth')
+        self.assertEqual(url.netloc, 'hotp')
+        self.assertEqual(url.path, '/mark%40percival')
+        self.assertEqual(dict(parse_qsl(url.query)),
+                         {'secret': 'wrn3pqx5uqxqvnqr', 'counter': '0'})
+
+        url = urlparse(
+            hotp.provisioning_uri('mark at percival', initial_count=12))
+        self.assertEqual(url.scheme, 'otpauth')
+        self.assertEqual(url.netloc, 'hotp')
+        self.assertEqual(url.path, '/mark%40percival')
+        self.assertEqual(dict(parse_qsl(url.query)),
+                         {'secret': 'wrn3pqx5uqxqvnqr', 'counter': '12'})
+
+        url = urlparse(
+            hotp.provisioning_uri('mark at percival', issuer_name='FooCorp!'))
+        self.assertEqual(url.scheme, 'otpauth')
+        self.assertEqual(url.netloc, 'hotp')
+        self.assertEqual(url.path, '/FooCorp%21:mark%40percival')
+        self.assertEqual(dict(parse_qsl(url.query)),
+                         {'secret': 'wrn3pqx5uqxqvnqr', 'counter': '0',
+                          'issuer': 'FooCorp!'})
+
+        key = 'c7uxuqhgflpw7oruedmglbrk7u6242vb'
+        hotp = pyotp.HOTP(key, digits=8, digest=hashlib.sha256)
+        url = urlparse(
+            hotp.provisioning_uri('baco at peperina', issuer_name='FooCorp'))
+        self.assertEqual(url.scheme, 'otpauth')
+        self.assertEqual(url.netloc, 'hotp')
+        self.assertEqual(url.path, '/FooCorp:baco%40peperina')
+        self.assertEqual(dict(parse_qsl(url.query)),
+                         {'secret': 'c7uxuqhgflpw7oruedmglbrk7u6242vb',
+                          'counter': '0', 'issuer': 'FooCorp',
+                          'digits': '8', 'algorithm': 'SHA256'})
+
+        hotp = pyotp.HOTP(key, digits=8)
+        url = urlparse(
+            hotp.provisioning_uri('baco at peperina', issuer_name='FooCorp',
+                                  initial_count=10))
+        self.assertEqual(url.scheme, 'otpauth')
+        self.assertEqual(url.netloc, 'hotp')
+        self.assertEqual(url.path, '/FooCorp:baco%40peperina')
+        self.assertEqual(dict(parse_qsl(url.query)),
+                         {'secret': 'c7uxuqhgflpw7oruedmglbrk7u6242vb',
+                          'counter': '10', 'issuer': 'FooCorp',
+                          'digits': '8'})
 
     def test_other_secret(self):
-        hotp = pyotp.HOTP('N3OVNIBRERIO5OHGVCMDGS4V4RJ3AUZOUN34J6FRM4P6JIFCG3ZA')
+        hotp = pyotp.HOTP(
+            'N3OVNIBRERIO5OHGVCMDGS4V4RJ3AUZOUN34J6FRM4P6JIFCG3ZA')
         self.assertEqual(hotp.at(0), '737863')
         self.assertEqual(hotp.at(1), '390601')
         self.assertEqual(hotp.at(2), '363354')
         self.assertEqual(hotp.at(3), '936780')
         self.assertEqual(hotp.at(4), '654019')
 
+
 class TOTPExampleValuesFromTheRFC(unittest.TestCase):
     RFC_VALUES = {
         (hashlib.sha1, b'12345678901234567890'): (
@@ -78,7 +121,9 @@ class TOTPExampleValuesFromTheRFC(unittest.TestCase):
             (20000000000, '77737706'),
         ),
 
-        (hashlib.sha512, b'1234567890123456789012345678901234567890123456789012345678901234'): (
+        (hashlib.sha512,
+         b'1234567890123456789012345678901234567890123456789012345678901234'):
+        (
             (59, 90693936),
             (1111111109, '25091201'),
             (1111111111, '99943326'),
@@ -92,6 +137,10 @@ class TOTPExampleValuesFromTheRFC(unittest.TestCase):
         for digest, secret in self.RFC_VALUES:
             totp = pyotp.TOTP(base64.b32encode(secret), 8, digest)
             for utime, code in self.RFC_VALUES[(digest, secret)]:
+                # 32-bit platforms use native functions to handle timestamps, so they fail this test
+                # (and will fail after 19 January 2038)
+                if utime > sys.maxsize:
+                    continue
                 value = totp.at(utime)
                 msg = "%s != %s (%s, time=%d)"
                 msg %= (value, code, digest().name, utime)
@@ -125,30 +174,83 @@ class TOTPExampleValuesFromTheRFC(unittest.TestCase):
 
     def test_provisioning_uri(self):
         totp = pyotp.TOTP('wrn3pqx5uqxqvnqr')
-        self.assertEqual(
-            totp.provisioning_uri('mark at percival'),
-            'otpauth://totp/mark@percival?secret=wrn3pqx5uqxqvnqr')
-
-        self.assertEqual(
-            totp.provisioning_uri('mark at percival', issuer_name='FooCorp!'),
-            'otpauth://totp/FooCorp%21:mark@percival?secret=wrn3pqx5uqxqvnqr&issuer=FooCorp%21')
+        url = urlparse(
+            totp.provisioning_uri('mark at percival'))
+        self.assertEqual(url.scheme, 'otpauth')
+        self.assertEqual(url.netloc, 'totp')
+        self.assertEqual(url.path, '/mark%40percival')
+        self.assertEqual(dict(parse_qsl(url.query)),
+                         {'secret': 'wrn3pqx5uqxqvnqr'})
+
+        url = urlparse(
+            totp.provisioning_uri('mark at percival', issuer_name='FooCorp!'))
+        self.assertEqual(url.scheme, 'otpauth')
+        self.assertEqual(url.netloc, 'totp')
+        self.assertEqual(url.path, '/FooCorp%21:mark%40percival')
+        self.assertEqual(dict(parse_qsl(url.query)),
+                         {'secret': 'wrn3pqx5uqxqvnqr',
+                          'issuer': 'FooCorp!'})
+
+        key = 'c7uxuqhgflpw7oruedmglbrk7u6242vb'
+        totp = pyotp.TOTP(key, digits=8, interval=60, digest=hashlib.sha256)
+        url = urlparse(totp.provisioning_uri('baco at peperina', issuer_name='FooCorp'))
+        self.assertEqual(url.scheme, 'otpauth')
+        self.assertEqual(url.netloc, 'totp')
+        self.assertEqual(url.path, '/FooCorp:baco%40peperina')
+        self.assertEqual(dict(parse_qsl(url.query)),
+                         {'secret': 'c7uxuqhgflpw7oruedmglbrk7u6242vb',
+                          'issuer': 'FooCorp',
+                          'digits': '8', 'period': '60',
+                          'algorithm': 'SHA256'})
+
+        totp = pyotp.TOTP(key, digits=8, interval=60)
+        url = urlparse(totp.provisioning_uri('baco at peperina', issuer_name='FooCorp'))
+        self.assertEqual(url.scheme, 'otpauth')
+        self.assertEqual(url.netloc, 'totp')
+        self.assertEqual(url.path, '/FooCorp:baco%40peperina')
+        self.assertEqual(dict(parse_qsl(url.query)),
+                         {'secret': 'c7uxuqhgflpw7oruedmglbrk7u6242vb',
+                          'issuer': 'FooCorp',
+                          'digits': '8', 'period': '60'})
+
+        totp = pyotp.TOTP(key, digits=8)
+        url = urlparse(totp.provisioning_uri('baco at peperina', issuer_name='FooCorp'))
+        self.assertEqual(url.scheme, 'otpauth')
+        self.assertEqual(url.netloc, 'totp')
+        self.assertEqual(url.path, '/FooCorp:baco%40peperina')
+        self.assertEqual(dict(parse_qsl(url.query)),
+                         {'secret': 'c7uxuqhgflpw7oruedmglbrk7u6242vb',
+                          'issuer': 'FooCorp',
+                          'digits': '8'})
 
     def test_random_key_generation(self):
         self.assertEqual(len(pyotp.random_base32()), 16)
         self.assertEqual(len(pyotp.random_base32(length=20)), 20)
 
-class StringComparisonTest(unittest.TestCase):
+
+class CompareDigestTest(unittest.TestCase):
+    method = staticmethod(pyotp.utils.compare_digest)
+
     def test_comparisons(self):
-        self.assertTrue(pyotp.utils.strings_equal("", ""))
-        self.assertTrue(pyotp.utils.strings_equal("a", "a"))
-        self.assertTrue(pyotp.utils.strings_equal("a" * 1000, "a" * 1000))
+        self.assertTrue(self.method("", ""))
+        self.assertTrue(self.method("a", "a"))
+        self.assertTrue(self.method("a" * 1000, "a" * 1000))
+
+        self.assertFalse(self.method("", "a"))
+        self.assertFalse(self.method("a", ""))
+        self.assertFalse(self.method("a" * 999 + "b", "a" * 1000))
 
-        self.assertFalse(pyotp.utils.strings_equal("", "a"))
-        self.assertFalse(pyotp.utils.strings_equal("a", ""))
-        self.assertFalse(pyotp.utils.strings_equal("a" * 999 + "b", "a" * 1000))
+
+class FallBackCompareDigestTest(CompareDigestTest):
+    method = staticmethod(pyotp.utils._compare_digest)
+
+
+class StringComparisonTest(CompareDigestTest):
+    method = staticmethod(pyotp.utils.strings_equal)
 
     def test_fullwidth_input(self):
-        self.assertTrue(pyotp.utils.strings_equal("xs12345", "xs12345"))
+        self.assertTrue(self.method("xs12345", "xs12345"))
+
 
 class CounterOffsetTest(unittest.TestCase):
     def test_counter_offset(self):
@@ -156,6 +258,7 @@ class CounterOffsetTest(unittest.TestCase):
         self.assertEqual(totp.at(200), "028307")
         self.assertTrue(totp.at(200, 1), "681610")
 
+
 class ValidWindowTest(unittest.TestCase):
     def test_valid_window(self):
         totp = pyotp.TOTP("ABCDEFGH")
@@ -164,6 +267,7 @@ class ValidWindowTest(unittest.TestCase):
         self.assertTrue(totp.verify("681610", 200, 1))
         self.assertFalse(totp.verify("195979", 200, 1))
 
+
 class Timecop(object):
     """
     Half-assed clone of timecop.rb, just enough to pass our tests.

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-pyotp.git



More information about the Python-modules-commits mailing list