Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-pyotp for openSUSE:Factory 
checked in at 2021-03-25 14:52:48
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pyotp (Old)
 and      /work/SRC/openSUSE:Factory/.python-pyotp.new.2401 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-pyotp"

Thu Mar 25 14:52:48 2021 rev:4 rq:881277 version:2.6.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pyotp/python-pyotp.changes        
2020-08-17 14:39:50.622568939 +0200
+++ /work/SRC/openSUSE:Factory/.python-pyotp.new.2401/python-pyotp.changes      
2021-03-25 14:52:49.552516010 +0100
@@ -1,0 +2,11 @@
+Wed Mar 24 10:47:36 UTC 2021 - Martin Hauke <mar...@gmx.de>
+
+- update to 2.6.0
+  * Raise default and minimum base32 secret length to 32, and hex
+    secret length to 40 (160 bits as recommended by the RFC).
+  * Fix issue where provisioning_uri would return invalid results
+    after calling verify().
+- update to 2.5.0
+  * parse_uri accepts and ignores optional image parameter.
+
+-------------------------------------------------------------------

Old:
----
  pyotp-2.4.0.tar.gz

New:
----
  pyotp-2.6.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-pyotp.spec ++++++
--- /var/tmp/diff_new_pack.86bdO9/_old  2021-03-25 14:52:50.148516616 +0100
+++ /var/tmp/diff_new_pack.86bdO9/_new  2021-03-25 14:52:50.152516621 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-pyotp
 #
-# Copyright (c) 2020 SUSE LLC
+# Copyright (c) 2021 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -19,7 +19,7 @@
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %define skip_python2 1
 Name:           python-pyotp
-Version:        2.4.0
+Version:        2.6.0
 Release:        0
 Summary:        Python One Time Password Library
 License:        MIT

++++++ pyotp-2.4.0.tar.gz -> pyotp-2.6.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyotp-2.4.0/LICENSE new/pyotp-2.6.0/LICENSE
--- old/pyotp-2.4.0/LICENSE     2017-11-06 17:59:14.000000000 +0100
+++ new/pyotp-2.6.0/LICENSE     2021-02-04 20:19:37.000000000 +0100
@@ -1,4 +1,4 @@
-Copyright (C) 2011-2017 Mark Percival <m...@mdp.im>,
+Copyright (C) 2011-2021 Mark Percival <m...@mdp.im>,
 Nathan Reynolds <em...@nreynolds.co.uk>, Andrey Kislyuk <kisl...@gmail.com>,
 and PyOTP contributors
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyotp-2.4.0/PKG-INFO new/pyotp-2.6.0/PKG-INFO
--- old/pyotp-2.4.0/PKG-INFO    2020-07-29 22:06:57.000000000 +0200
+++ new/pyotp-2.6.0/PKG-INFO    2021-02-04 20:47:47.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: pyotp
-Version: 2.4.0
+Version: 2.6.0
 Summary: Python One Time Password Library
 Home-page: https://github.com/pyotp/pyotp
 Author: PyOTP contributors
@@ -138,7 +138,7 @@
         ~~~~~
         
         * `Project home page (GitHub) <https://github.com/pyauth/pyotp>`_
-        * `Documentation (Read the Docs) 
<https://pyotp.readthedocs.io/en/latest/>`_
+        * `Documentation <https://pyauth.github.io/pyotp/>`_
         * `Package distribution (PyPI) <https://pypi.python.org/pypi/pyotp>`_
         * `Change log 
<https://github.com/pyauth/pyotp/blob/master/Changes.rst>`_
         * `RFC 4226: HOTP: An HMAC-Based One-Time Password 
<https://tools.ietf.org/html/rfc4226>`_
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyotp-2.4.0/README.rst new/pyotp-2.6.0/README.rst
--- old/pyotp-2.4.0/README.rst  2020-07-29 22:05:34.000000000 +0200
+++ new/pyotp-2.6.0/README.rst  2020-10-17 06:27:44.000000000 +0200
@@ -130,7 +130,7 @@
 ~~~~~
 
 * `Project home page (GitHub) <https://github.com/pyauth/pyotp>`_
-* `Documentation (Read the Docs) <https://pyotp.readthedocs.io/en/latest/>`_
+* `Documentation <https://pyauth.github.io/pyotp/>`_
 * `Package distribution (PyPI) <https://pypi.python.org/pypi/pyotp>`_
 * `Change log <https://github.com/pyauth/pyotp/blob/master/Changes.rst>`_
 * `RFC 4226: HOTP: An HMAC-Based One-Time Password 
<https://tools.ietf.org/html/rfc4226>`_
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyotp-2.4.0/setup.py new/pyotp-2.6.0/setup.py
--- old/pyotp-2.4.0/setup.py    2020-07-29 22:05:54.000000000 +0200
+++ new/pyotp-2.6.0/setup.py    2021-02-04 20:45:36.000000000 +0100
@@ -7,7 +7,7 @@
 
 setup(
     name="pyotp",
-    version="2.4.0",
+    version="2.6.0",
     url="https://github.com/pyotp/pyotp";,
     license="MIT License",
     author="PyOTP contributors",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyotp-2.4.0/src/pyotp/__init__.py 
new/pyotp-2.6.0/src/pyotp/__init__.py
--- old/pyotp-2.4.0/src/pyotp/__init__.py       2020-07-29 22:05:34.000000000 
+0200
+++ new/pyotp-2.6.0/src/pyotp/__init__.py       2021-02-04 20:42:19.000000000 
+0100
@@ -1,7 +1,6 @@
-from typing import Any, Dict, Sequence
-
 import hashlib
 from re import split
+from typing import Any, Dict, Sequence
 from urllib.parse import unquote, urlparse, parse_qsl
 
 from .compat import random
@@ -10,11 +9,12 @@
 from .totp import TOTP as TOTP
 
 
-def random_base32(
-        length: int = 16,
-        chars: Sequence[str] = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567')) -> 
str:
-    if length < 16:
-        raise Exception("Secrets should be at least 128 bits")
+def random_base32(length: int = 32, chars: Sequence[str] = 
list('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567')) -> str:
+    # Note: the otpauth scheme DOES NOT use base32 padding for secret lengths 
not divisible by 8.
+    # Some third-party tools have bugs when dealing with such secrets.
+    # We might consider warning the user when generating a secret of length 
not divisible by 8.
+    if length < 32:
+        raise ValueError("Secrets should be at least 160 bits")
 
     return ''.join(
         random.choice(chars)
@@ -22,11 +22,9 @@
     )
 
 
-def random_hex(
-        length: int = 32,
-        chars: Sequence[str] = list('ABCDEF0123456789')) -> str:
-    if length < 32:
-        raise Exception("Secrets should be at least 128 bits")
+def random_hex(length: int = 40, chars: Sequence[str] = 
list('ABCDEF0123456789')) -> str:
+    if length < 40:
+        raise ValueError("Secrets should be at least 160 bits")
     return random_base32(length=length, chars=chars)
 
 
@@ -80,14 +78,14 @@
                 raise ValueError('Invalid value for algorithm, must be SHA1, 
SHA256 or SHA512')
         elif key == 'digits':
             digits = int(value)
-            if digits not in [6, 8]:
-                raise ValueError('Digits may only be 6 or 8')
+            if digits not in [6, 7, 8]:
+                raise ValueError('Digits may only be 6, 7, or 8')
             otp_data['digits'] = digits
         elif key == 'period':
-            otp_data['interval'] = value
+            otp_data['interval'] = int(value)
         elif key == 'counter':
-            otp_data['initial_count'] = value
-        else:
+            otp_data['initial_count'] = int(value)
+        elif key != 'image':
             raise ValueError('{} is not a valid parameter'.format(key))
 
     if not secret:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyotp-2.4.0/src/pyotp/hotp.py 
new/pyotp-2.6.0/src/pyotp/hotp.py
--- old/pyotp-2.4.0/src/pyotp/hotp.py   2020-07-29 22:05:34.000000000 +0200
+++ new/pyotp-2.6.0/src/pyotp/hotp.py   2021-01-30 00:12:46.000000000 +0100
@@ -1,3 +1,4 @@
+import hashlib
 from typing import Any, Optional
 
 from . import utils
@@ -8,12 +9,18 @@
     """
     Handler for HMAC-based OTP counters.
     """
-    def __init__(self, *args: Any, initial_count: int = 0, **kwargs: Any) -> 
None:
+    def __init__(self, s: str, digits: int = 6, digest: Any = hashlib.sha1, 
name: Optional[str] = None,
+                 issuer: Optional[str] = None, initial_count: int = 0) -> None:
         """
+        :param s: secret in base32 format
         :param initial_count: starting HMAC counter value, defaults to 0
+        :param digits: number of integers in the OTP. Some apps expect this to 
be 6 digits, others support more.
+        :param digest: digest function to use in the HMAC (expected to be sha1)
+        :param name: account name
+        :param issuer: issuer
         """
         self.initial_count = initial_count
-        super(HOTP, self).__init__(*args, **kwargs)
+        super().__init__(s=s, digits=digits, digest=digest, name=name, 
issuer=issuer)
 
     def at(self, count: int) -> str:
         """
@@ -22,7 +29,7 @@
         :param count: the OTP HMAC counter
         :returns: OTP
         """
-        return self.generate_otp(count)
+        return self.generate_otp(self.initial_count + count)
 
     def verify(self, otp: str, counter: int) -> bool:
         """
@@ -37,7 +44,8 @@
             self,
             name: Optional[str] = None,
             initial_count: Optional[int] = None,
-            issuer_name: Optional[str] = None) -> str:
+            issuer_name: Optional[str] = None,
+            image: Optional[str] = None) -> str:
         """
         Returns the provisioning URI for the OTP.  This can then be
         encoded in a QR Code and used to provision an OTP app like
@@ -58,5 +66,6 @@
             initial_count=initial_count if initial_count else 
self.initial_count,
             issuer=issuer_name if issuer_name else self.issuer,
             algorithm=self.digest().name,
-            digits=self.digits
+            digits=self.digits,
+            image=image,
         )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyotp-2.4.0/src/pyotp/otp.py 
new/pyotp-2.6.0/src/pyotp/otp.py
--- old/pyotp-2.4.0/src/pyotp/otp.py    2020-07-29 22:05:34.000000000 +0200
+++ new/pyotp-2.6.0/src/pyotp/otp.py    2021-02-04 20:19:45.000000000 +0100
@@ -1,29 +1,15 @@
-from typing import Any, Optional
-
 import base64
 import hashlib
 import hmac
+from typing import Any, Optional
 
 
 class OTP(object):
     """
     Base class for OTP handlers.
     """
-    def __init__(
-        self,
-        s: str,
-        digits: int = 6,
-        digest: Any = hashlib.sha1,
-        name: Optional[str] = None,
-        issuer: Optional[str] = None
-    ) -> None:
-        """
-        :param s: secret in base32 format
-        :param digits: number of integers in the OTP. Some apps expect this to 
be 6 digits, others support more.
-        :param digest: digest function to use in the HMAC (expected to be sha1)
-        :param name: account name
-        :param issuer: issuer
-        """
+    def __init__(self, s: str, digits: int = 6, digest: Any = hashlib.sha1, 
name: Optional[str] = None,
+                 issuer: Optional[str] = None) -> None:
         self.digits = digits
         self.digest = digest
         self.secret = s
@@ -51,10 +37,11 @@
         return str_code
 
     def byte_secret(self) -> bytes:
-        missing_padding = len(self.secret) % 8
+        secret = self.secret
+        missing_padding = len(secret) % 8
         if missing_padding != 0:
-            self.secret += '=' * (8 - missing_padding)
-        return base64.b32decode(self.secret, casefold=True)
+            secret += '=' * (8 - missing_padding)
+        return base64.b32decode(secret, casefold=True)
 
     @staticmethod
     def int_to_bytestring(i: int, padding: int = 8) -> bytes:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyotp-2.4.0/src/pyotp/totp.py 
new/pyotp-2.6.0/src/pyotp/totp.py
--- old/pyotp-2.4.0/src/pyotp/totp.py   2020-07-29 22:05:34.000000000 +0200
+++ new/pyotp-2.6.0/src/pyotp/totp.py   2021-01-30 00:12:46.000000000 +0100
@@ -1,7 +1,8 @@
-from typing import Any, Union, Optional
-
+import calendar
 import datetime
+import hashlib
 import time
+from typing import Any, Union, Optional
 
 from . import utils
 from .otp import OTP
@@ -11,13 +12,18 @@
     """
     Handler for time-based OTP counters.
     """
-    def __init__(self, *args: Any, interval: int = 30, **kwargs: Any) -> None:
+    def __init__(self, s: str, digits: int = 6, digest: Any = hashlib.sha1, 
name: Optional[str] = None,
+                 issuer: Optional[str] = None, interval: int = 30) -> None:
         """
-        :param interval: the time interval in seconds
-            for OTP. This defaults to 30.
+        :param s: secret in base32 format
+        :param interval: the time interval in seconds for OTP. This defaults 
to 30.
+        :param digits: number of integers in the OTP. Some apps expect this to 
be 6 digits, others support more.
+        :param digest: digest function to use in the HMAC (expected to be sha1)
+        :param name: account name
+        :param issuer: issuer
         """
         self.interval = interval
-        super(TOTP, self).__init__(*args, **kwargs)
+        super().__init__(s=s, digits=digits, digest=digest, name=name, 
issuer=issuer)
 
     def at(self, for_time: Union[int, datetime.datetime], counter_offset: int 
= 0) -> str:
         """
@@ -64,7 +70,9 @@
 
         return utils.strings_equal(str(otp), str(self.at(for_time)))
 
-    def provisioning_uri(self, name: Optional[str] = None, issuer_name: 
Optional[str] = None) -> str:
+    def provisioning_uri(self, name: Optional[str] = None, issuer_name: 
Optional[str] = None,
+                         image: Optional[str] = None) -> str:
+
         """
         Returns the provisioning URI for the OTP.  This can then be
         encoded in a QR Code and used to provision an OTP app like
@@ -77,8 +85,16 @@
         return utils.build_uri(self.secret, name if name else self.name,
                                issuer=issuer_name if issuer_name else 
self.issuer,
                                algorithm=self.digest().name,
-                               digits=self.digits, period=self.interval)
+                               digits=self.digits, period=self.interval, 
image=image)
 
     def timecode(self, for_time: datetime.datetime) -> int:
-        i = time.mktime(for_time.timetuple())
-        return int(i / self.interval)
+        """
+        Accepts either a timezone naive (`for_time.tzinfo is None`) or
+        a timezone aware datetime as argument and returns the
+        corresponding counter value (timecode).
+
+        """
+        if for_time.tzinfo:
+            return int(calendar.timegm(for_time.utctimetuple()) / 
self.interval)
+        else:
+            return int(time.mktime(for_time.timetuple()) / self.interval)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyotp-2.4.0/src/pyotp/utils.py 
new/pyotp-2.6.0/src/pyotp/utils.py
--- old/pyotp-2.4.0/src/pyotp/utils.py  2020-07-29 22:05:34.000000000 +0200
+++ new/pyotp-2.6.0/src/pyotp/utils.py  2021-01-30 00:12:46.000000000 +0100
@@ -1,18 +1,12 @@
-from typing import Dict, Optional, Union
-
 import unicodedata
 from hmac import compare_digest
-from urllib.parse import quote, urlencode
+from typing import Dict, Optional, Union
+from urllib.parse import quote, urlencode, urlparse
 
 
-def build_uri(
-        secret: str,
-        name: str,
-        initial_count: Optional[int] = None,
-        issuer: Optional[str] = None,
-        algorithm: Optional[str] = None,
-        digits: Optional[int] = None,
-        period: Optional[int] = None) -> str:
+def build_uri(secret: str, name: str, initial_count: Optional[int] = None, 
issuer: Optional[str] = None,
+              algorithm: Optional[str] = None, digits: Optional[int] = None, 
period: Optional[int] = None,
+              image: Optional[str] = None) -> str:
     """
     Returns the provisioning URI for the OTP; works for either TOTP or HOTP.
 
@@ -34,6 +28,7 @@
     :param digits: the length of the OTP generated code.
     :param period: the number of seconds the OTP generator is set to
         expire every code.
+    :param image: optional logo image url
     :returns: provisioning uri
     """
     # initial_count may be 0 as a valid param
@@ -62,6 +57,11 @@
         url_args['digits'] = digits
     if is_period_set:
         url_args['period'] = period
+    if image:
+        image_uri = urlparse(image)
+        if image_uri.scheme != 'https' or not image_uri.netloc or not 
image_uri.path:
+            raise ValueError('{} is not a valid url'.format(image_uri))
+        url_args['image'] = image
 
     uri = base_uri.format(otp_type, label, urlencode(url_args).replace("+", 
"%20"))
     return uri
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyotp-2.4.0/src/pyotp.egg-info/PKG-INFO 
new/pyotp-2.6.0/src/pyotp.egg-info/PKG-INFO
--- old/pyotp-2.4.0/src/pyotp.egg-info/PKG-INFO 2020-07-29 22:06:57.000000000 
+0200
+++ new/pyotp-2.6.0/src/pyotp.egg-info/PKG-INFO 2021-02-04 20:47:46.000000000 
+0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: pyotp
-Version: 2.4.0
+Version: 2.6.0
 Summary: Python One Time Password Library
 Home-page: https://github.com/pyotp/pyotp
 Author: PyOTP contributors
@@ -138,7 +138,7 @@
         ~~~~~
         
         * `Project home page (GitHub) <https://github.com/pyauth/pyotp>`_
-        * `Documentation (Read the Docs) 
<https://pyotp.readthedocs.io/en/latest/>`_
+        * `Documentation <https://pyauth.github.io/pyotp/>`_
         * `Package distribution (PyPI) <https://pypi.python.org/pypi/pyotp>`_
         * `Change log 
<https://github.com/pyauth/pyotp/blob/master/Changes.rst>`_
         * `RFC 4226: HOTP: An HMAC-Based One-Time Password 
<https://tools.ietf.org/html/rfc4226>`_
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyotp-2.4.0/test.py new/pyotp-2.6.0/test.py
--- old/pyotp-2.4.0/test.py     2020-07-29 22:05:34.000000000 +0200
+++ new/pyotp-2.6.0/test.py     2021-02-04 20:33:11.000000000 +0100
@@ -7,7 +7,7 @@
 from urllib.parse 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):
@@ -118,6 +118,11 @@
             ).provisioning_uri()
         )
 
+        code = pyotp.totp.TOTP("S46SQCPPTCNPROMHWYBDCTBZXV")
+        self.assertEqual(code.provisioning_uri(), 
"otpauth://totp/Secret?secret=S46SQCPPTCNPROMHWYBDCTBZXV")
+        code.verify("123456")
+        self.assertEqual(code.provisioning_uri(), 
"otpauth://totp/Secret?secret=S46SQCPPTCNPROMHWYBDCTBZXV")
+
     def test_other_secret(self):
         hotp = pyotp.HOTP(
             'N3OVNIBRERIO5OHGVCMDGS4V4RJ3AUZOUN34J6FRM4P6JIFCG3ZA')
@@ -291,14 +296,14 @@
         )
 
     def test_random_key_generation(self):
-        self.assertEqual(len(pyotp.random_base32()), 16)
-        self.assertEqual(len(pyotp.random_base32(length=20)), 20)
-        self.assertEqual(len(pyotp.random_hex()), 32)
-        self.assertEqual(len(pyotp.random_hex(length=64)), 64)
-        with self.assertRaises(Exception):
-            pyotp.random_base32(length=15)
-        with self.assertRaises(Exception):
-            pyotp.random_hex(length=24)
+        self.assertEqual(len(pyotp.random_base32()), 32)
+        self.assertEqual(len(pyotp.random_base32(length=34)), 34)
+        self.assertEqual(len(pyotp.random_hex()), 40)
+        self.assertEqual(len(pyotp.random_hex(length=42)), 42)
+        with self.assertRaises(ValueError):
+            pyotp.random_base32(length=31)
+        with self.assertRaises(ValueError):
+            pyotp.random_hex(length=39)
 
 
 class CompareDigestTest(unittest.TestCase):
@@ -339,6 +344,7 @@
         self.assertTrue(totp.verify("681610", 200, 1))
         self.assertFalse(totp.verify("195979", 200, 1))
 
+
 class ParseUriTest(unittest.TestCase):
     def test_invalids(self):
         with self.assertRaises(ValueError) as cm:
@@ -359,7 +365,7 @@
 
         with self.assertRaises(ValueError) as cm:
             pyotp.parse_uri('otpauth://totp?digits=-1')
-        self.assertEqual('Digits may only be 6 or 8', str(cm.exception))
+        self.assertEqual('Digits may only be 6, 7, or 8', str(cm.exception))
 
         with self.assertRaises(ValueError) as cm:
             pyotp.parse_uri('otpauth://totp/SomeIssuer:?issuer=AnotherIssuer')
@@ -369,16 +375,59 @@
             pyotp.parse_uri('otpauth://totp?algorithm=aes')
         self.assertEqual('Invalid value for algorithm, must be SHA1, SHA256 or 
SHA512', str(cm.exception))
 
+    @unittest.skipIf(sys.version_info < (3, 6), "Skipping test that requires 
deterministic dict key enumeration")
     def test_algorithms(self):
-        otp = 
pyotp.parse_uri('otpauth://totp?algorithm=SHA1&secret=123456&algorithm=SHA1')
+        otp = 
pyotp.parse_uri('otpauth://totp?algorithm=SHA1&secret=GEZDGNBV&algorithm=SHA1')
         self.assertEqual(hashlib.sha1, otp.digest)
+        self.assertEqual(otp.at(0), '734055')
+        self.assertEqual(otp.at(30), '662488')
+        self.assertEqual(otp.at(60), '289363')
+        self.assertEqual(otp.provisioning_uri(), 
'otpauth://totp/Secret?secret=GEZDGNBV')
+        self.assertEqual(otp.provisioning_uri(name='n', issuer_name='i'), 
'otpauth://totp/i:n?secret=GEZDGNBV&issuer=i')
 
-        otp = 
pyotp.parse_uri('otpauth://totp?algorithm=SHA1&secret=123456&algorithm=SHA256')
+        otp = 
pyotp.parse_uri('otpauth://totp?algorithm=SHA1&secret=GEZDGNBV&algorithm=SHA1&period=60')
+        self.assertEqual(hashlib.sha1, otp.digest)
+        self.assertEqual(otp.at(30), '734055')
+        self.assertEqual(otp.at(60), '662488')
+        self.assertEqual(otp.provisioning_uri(name='n', issuer_name='i'),
+                         
'otpauth://totp/i:n?secret=GEZDGNBV&issuer=i&period=60')
+
+        otp = 
pyotp.parse_uri('otpauth://hotp?algorithm=SHA1&secret=GEZDGNBV&algorithm=SHA1')
+        self.assertEqual(hashlib.sha1, otp.digest)
+        self.assertEqual(otp.at(0), '734055')
+        self.assertEqual(otp.at(1), '662488')
+        self.assertEqual(otp.at(2), '289363')
+        self.assertEqual(otp.provisioning_uri(name='n', issuer_name='i'),
+                         
'otpauth://hotp/i:n?secret=GEZDGNBV&issuer=i&counter=0')
+
+        otp = 
pyotp.parse_uri('otpauth://hotp?algorithm=SHA1&secret=GEZDGNBV&algorithm=SHA1&counter=1')
+        self.assertEqual(hashlib.sha1, otp.digest)
+        self.assertEqual(otp.at(0), '662488')
+        self.assertEqual(otp.at(1), '289363')
+        self.assertEqual(otp.provisioning_uri(name='n', issuer_name='i'),
+                         
'otpauth://hotp/i:n?secret=GEZDGNBV&issuer=i&counter=1')
+
+        otp = 
pyotp.parse_uri('otpauth://totp?algorithm=SHA1&secret=GEZDGNBV&algorithm=SHA256')
         self.assertEqual(hashlib.sha256, otp.digest)
+        self.assertEqual(otp.at(0), '918961')
+        self.assertEqual(otp.at(9000), '934470')
+
+        otp = 
pyotp.parse_uri('otpauth://totp?algorithm=SHA1&secret=GEZDGNBV&algorithm=SHA512')
+        self.assertEqual(hashlib.sha512, otp.digest)
+        self.assertEqual(otp.at(0), '816660')
+        self.assertEqual(otp.at(9000), '524153')
+
+        self.assertEqual(
+            otp.provisioning_uri(name='n', issuer_name='i', 
image='https://test.net/test.png'),
+            
'otpauth://totp/i:n?secret=GEZDGNBV&issuer=i&algorithm=SHA512&image=https%3A%2F%2Ftest.net%2Ftest.png'
+        )
+        with self.assertRaises(ValueError):
+            otp.provisioning_uri(name='n', issuer_name='i', image='nourl')
 
-        otp = 
pyotp.parse_uri('otpauth://totp?algorithm=SHA1&secret=123456&algorithm=SHA512')
+        otp = pyotp.parse_uri(otp.provisioning_uri(name='n', issuer_name='i', 
image='https://test.net/test.png'))
         self.assertEqual(hashlib.sha512, otp.digest)
 
+
 class Timecop(object):
     """
     Half-assed clone of timecop.rb, just enough to pass our tests.

Reply via email to