[Python-modules-commits] [python-pyotp] 01/03: New upstream version 2.2.6

Hugo Lefeuvre hle at moszumanska.debian.org
Thu Oct 5 13:28:46 UTC 2017


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

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

commit ecc31e0e543a0ded4639bd5d81e5841ba223de0c
Author: Hugo Lefeuvre <hle at debian.org>
Date:   Thu Oct 5 15:18:04 2017 +0200

    New upstream version 2.2.6
---
 PKG-INFO                    | 36 ++++++++++++++++++-----------
 README.rst                  | 34 ++++++++++++++++++----------
 setup.cfg                   |  3 +--
 setup.py                    | 40 ++++++++++++++++-----------------
 src/pyotp.egg-info/PKG-INFO | 36 ++++++++++++++++++-----------
 src/pyotp/hotp.py           | 42 +++++++++++++++++++++++-----------
 src/pyotp/otp.py            | 25 ++++++++++++---------
 src/pyotp/totp.py           | 55 ++++++++++++++++++++++++++++++++-------------
 src/pyotp/utils.py          | 28 ++++++++++++++---------
 test.py                     | 32 +++++++++++++++++---------
 10 files changed, 210 insertions(+), 121 deletions(-)

diff --git a/PKG-INFO b/PKG-INFO
index 1a62fa1..7a8312d 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: pyotp
-Version: 2.2.1
+Version: 2.2.6
 Summary: Python One Time Password Library
 Home-page: https://github.com/pyotp/pyotp
 Author: PyOTP contributors
@@ -20,13 +20,15 @@ Description: PyOTP - The Python One-Time Password Library
         compatible app. Users can set up auth tokens in their apps easily by using their phone camera to scan `otpauth://
         <https://github.com/google/google-authenticator/wiki/Key-Uri-Format>`_ QR codes provided by PyOTP.
         
+        We recommend that implementers read the `OWASP Authentication Cheat Sheet <https://www.owasp.org/index.php/Authentication_Cheat_Sheet>`_ and `NIST SP 800-63-3: Digital Authentication Guideline <https://pages.nist.gov/800-63-3/>`_ for a high level overview of authentication best practices.
+        
         Quick overview of using One Time Passwords on your phone
         --------------------------------------------------------
         
         * OTPs involve a shared secret, stored both on the phone and the server
         * OTPs can be generated on a phone without internet connectivity
-        * OTPs should always be used as a second factor of authentication(if your phone is lost, you account is still secured with a password)
-        * Google Authenticator allows you to store multiple OTP secrets and provision those using a QR Code(no more typing in the secret)
+        * OTPs should always be used as a second factor of authentication (if your phone is lost, you account is still secured with a password)
+        * Google Authenticator and other OTP client apps allow you to store multiple OTP secrets and provision those using a QR Code
         
         Installation
         ------------
@@ -37,7 +39,7 @@ Description: PyOTP - The Python One-Time Password Library
         Usage
         -----
         
-        Time based OTPs
+        Time-based OTPs
         ~~~~~~~~~~~~~~~
         ::
         
@@ -66,23 +68,29 @@ Description: PyOTP - The Python One-Time Password Library
         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         ::
         
-            pyotp.random_base32() # returns a 16 character base32 secret. Compatible with Google Authenticator
+            pyotp.random_base32() # returns a 16 character base32 secret. Compatible with Google Authenticator and other OTP apps
         
         Google Authenticator Compatible
         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         
-        The library works with the Google Authenticator iPhone and Android app, and also includes the ability to generate
-        provisioning URI's for use with the QR Code scanner built into the app::
+        PyOTP works with the Google Authenticator iPhone and Android app, as well as other OTP apps like Authy. PyOTP includes the
+        ability to generate provisioning URIs for use with the QR Code scanner built into these MFA client apps::
+        
+            pyotp.totp.TOTP('JBSWY3DPEHPK3PXP').provisioning_uri("alice at google.com", issuer_name="Secure App")
+        
+            >>> 'otpauth://totp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure%20App'
+        
+            pyotp.hotp.HOTP('JBSWY3DPEHPK3PXP').provisioning_uri("alice at google.com", initial_count=0, issuer_name="Secure App")
         
-            totp.provisioning_uri("alice at google.com") # => 'otpauth://totp/alice@google.com?secret=JBSWY3DPEHPK3PXP'
-            hotp.provisioning_uri("alice at google.com", 0) # => 'otpauth://hotp/alice@google.com?secret=JBSWY3DPEHPK3PXP&counter=0'
+            >>> 'otpauth://hotp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure%20App&counter=0'
         
-        This can then be rendered as a QR Code which can then be scanned and added to the users list of OTP credentials.
+        This URL can then be rendered as a QR Code (for example, using https://github.com/neocotic/qrious) which can then be scanned
+        and added to the users list of OTP credentials.
         
         Working example
         ~~~~~~~~~~~~~~~
         
-        Scan the following barcode with your phone, using Google Authenticator
+        Scan the following barcode with your phone's OTP app (e.g. Google Authenticator):
         
         .. image:: http://chart.apis.google.com/chart?cht=qr&chs=250x250&chl=otpauth%3A%2F%2Ftotp%2Falice%40google.com%3Fsecret%3DJBSWY3DPEHPK3PXP
         
@@ -96,13 +104,15 @@ Description: PyOTP - The Python One-Time Password Library
         ~~~~~
         
         * `Project home page (GitHub) <https://github.com/pyotp/pyotp>`_
-        * `Documentation (Read the Docs) <https://pyotp.readthedocs.org/en/latest/>`_
+        * `Documentation (Read the Docs) <https://pyotp.readthedocs.io/en/latest/>`_
         * `Package distribution (PyPI) <https://pypi.python.org/pypi/pyotp>`_
         * `Change log <https://github.com/pyotp/pyotp/blob/master/Changes.rst>`_
         * `RFC 4226: HOTP: An HMAC-Based One-Time Password <https://tools.ietf.org/html/rfc4226>`_
         * `RFC 6238: TOTP: Time-Based One-Time Password Algorithm <https://tools.ietf.org/html/rfc6238>`_
         * `ROTP <https://github.com/mdp/rotp>`_ - Original Ruby OTP library by `Mark Percival <https://github.com/mdp>`_
         * `OTPHP <https://github.com/lelag/otphp>`_ - PHP port of ROTP by `Le Lag <https://github.com/lelag>`_
+        * `OWASP Authentication Cheat Sheet <https://www.owasp.org/index.php/Authentication_Cheat_Sheet>`_
+        * `NIST SP 800-63-3: Digital Authentication Guideline <https://pages.nist.gov/800-63-3/>`_
         
         .. image:: https://img.shields.io/travis/pyotp/pyotp.svg
                 :target: https://travis-ci.org/pyotp/pyotp
@@ -113,7 +123,7 @@ Description: PyOTP - The Python One-Time Password Library
         .. 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
-                :target: https://pyotp.readthedocs.org/
+                :target: https://pyotp.readthedocs.io/
         
 Platform: MacOS X
 Platform: Posix
diff --git a/README.rst b/README.rst
index 7c5d90a..32bfec0 100644
--- a/README.rst
+++ b/README.rst
@@ -12,13 +12,15 @@ Authenticator <https://en.wikipedia.org/wiki/Google_Authenticator>`_, `Authy <ht
 compatible app. Users can set up auth tokens in their apps easily by using their phone camera to scan `otpauth://
 <https://github.com/google/google-authenticator/wiki/Key-Uri-Format>`_ QR codes provided by PyOTP.
 
+We recommend that implementers read the `OWASP Authentication Cheat Sheet <https://www.owasp.org/index.php/Authentication_Cheat_Sheet>`_ and `NIST SP 800-63-3: Digital Authentication Guideline <https://pages.nist.gov/800-63-3/>`_ for a high level overview of authentication best practices.
+
 Quick overview of using One Time Passwords on your phone
 --------------------------------------------------------
 
 * OTPs involve a shared secret, stored both on the phone and the server
 * OTPs can be generated on a phone without internet connectivity
-* OTPs should always be used as a second factor of authentication(if your phone is lost, you account is still secured with a password)
-* Google Authenticator allows you to store multiple OTP secrets and provision those using a QR Code(no more typing in the secret)
+* OTPs should always be used as a second factor of authentication (if your phone is lost, you account is still secured with a password)
+* Google Authenticator and other OTP client apps allow you to store multiple OTP secrets and provision those using a QR Code
 
 Installation
 ------------
@@ -29,7 +31,7 @@ Installation
 Usage
 -----
 
-Time based OTPs
+Time-based OTPs
 ~~~~~~~~~~~~~~~
 ::
 
@@ -58,23 +60,29 @@ Generating a base32 Secret Key
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 ::
 
-    pyotp.random_base32() # returns a 16 character base32 secret. Compatible with Google Authenticator
+    pyotp.random_base32() # returns a 16 character base32 secret. Compatible with Google Authenticator and other OTP apps
 
 Google Authenticator Compatible
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-The library works with the Google Authenticator iPhone and Android app, and also includes the ability to generate
-provisioning URI's for use with the QR Code scanner built into the app::
+PyOTP works with the Google Authenticator iPhone and Android app, as well as other OTP apps like Authy. PyOTP includes the
+ability to generate provisioning URIs for use with the QR Code scanner built into these MFA client apps::
+
+    pyotp.totp.TOTP('JBSWY3DPEHPK3PXP').provisioning_uri("alice at google.com", issuer_name="Secure App")
+
+    >>> 'otpauth://totp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure%20App'
+
+    pyotp.hotp.HOTP('JBSWY3DPEHPK3PXP').provisioning_uri("alice at google.com", initial_count=0, issuer_name="Secure App")
 
-    totp.provisioning_uri("alice at google.com") # => 'otpauth://totp/alice@google.com?secret=JBSWY3DPEHPK3PXP'
-    hotp.provisioning_uri("alice at google.com", 0) # => 'otpauth://hotp/alice@google.com?secret=JBSWY3DPEHPK3PXP&counter=0'
+    >>> 'otpauth://hotp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure%20App&counter=0'
 
-This can then be rendered as a QR Code which can then be scanned and added to the users list of OTP credentials.
+This URL can then be rendered as a QR Code (for example, using https://github.com/neocotic/qrious) which can then be scanned
+and added to the users list of OTP credentials.
 
 Working example
 ~~~~~~~~~~~~~~~
 
-Scan the following barcode with your phone, using Google Authenticator
+Scan the following barcode with your phone's OTP app (e.g. Google Authenticator):
 
 .. image:: http://chart.apis.google.com/chart?cht=qr&chs=250x250&chl=otpauth%3A%2F%2Ftotp%2Falice%40google.com%3Fsecret%3DJBSWY3DPEHPK3PXP
 
@@ -88,13 +96,15 @@ Links
 ~~~~~
 
 * `Project home page (GitHub) <https://github.com/pyotp/pyotp>`_
-* `Documentation (Read the Docs) <https://pyotp.readthedocs.org/en/latest/>`_
+* `Documentation (Read the Docs) <https://pyotp.readthedocs.io/en/latest/>`_
 * `Package distribution (PyPI) <https://pypi.python.org/pypi/pyotp>`_
 * `Change log <https://github.com/pyotp/pyotp/blob/master/Changes.rst>`_
 * `RFC 4226: HOTP: An HMAC-Based One-Time Password <https://tools.ietf.org/html/rfc4226>`_
 * `RFC 6238: TOTP: Time-Based One-Time Password Algorithm <https://tools.ietf.org/html/rfc6238>`_
 * `ROTP <https://github.com/mdp/rotp>`_ - Original Ruby OTP library by `Mark Percival <https://github.com/mdp>`_
 * `OTPHP <https://github.com/lelag/otphp>`_ - PHP port of ROTP by `Le Lag <https://github.com/lelag>`_
+* `OWASP Authentication Cheat Sheet <https://www.owasp.org/index.php/Authentication_Cheat_Sheet>`_
+* `NIST SP 800-63-3: Digital Authentication Guideline <https://pages.nist.gov/800-63-3/>`_
 
 .. image:: https://img.shields.io/travis/pyotp/pyotp.svg
         :target: https://travis-ci.org/pyotp/pyotp
@@ -105,4 +115,4 @@ Links
 .. 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
-        :target: https://pyotp.readthedocs.org/
+        :target: https://pyotp.readthedocs.io/
diff --git a/setup.cfg b/setup.cfg
index 4644c25..0069dd9 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -3,10 +3,9 @@ universal = 1
 
 [flake8]
 max-line-length = 120
-ignore = E301, E302, E401, E226, F841
+ignore = E301, E302, E305, E401, E226, F841
 
 [egg_info]
 tag_build = 
 tag_date = 0
-tag_svn_revision = 0
 
diff --git a/setup.py b/setup.py
index 27cf6a3..6451169 100755
--- a/setup.py
+++ b/setup.py
@@ -6,29 +6,29 @@ 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.2.1',
-    url='https://github.com/pyotp/pyotp',
-    license='BSD License',
-    author='PyOTP contributors',
-    author_email='kislyuk at gmail.com',
-    description='Python One Time Password Library',
-    long_description=open('README.rst').read(),
+    name="pyotp",
+    version="2.2.6",
+    url="https://github.com/pyotp/pyotp",
+    license="BSD License",
+    author="PyOTP contributors",
+    author_email="kislyuk at gmail.com",
+    description="Python One Time Password Library",
+    long_description=open("README.rst").read(),
     install_requires=install_requires,
-    packages=['pyotp'],
-    package_dir={'': 'src'},
-    platforms=['MacOS X', 'Posix'],
+    packages=["pyotp"],
+    package_dir={"": "src"},
+    platforms=["MacOS X", "Posix"],
     zip_safe=False,
     test_suite="test",
     classifiers=[
-        'Intended Audience :: Developers',
-        'License :: OSI Approved :: BSD License',
-        'Operating System :: MacOS :: MacOS X',
-        'Operating System :: POSIX',
-        'Programming Language :: Python',
-        'Programming Language :: Python :: 2.7',
-        'Programming Language :: Python :: 3.3',
-        'Programming Language :: Python :: 3.4',
-        'Topic :: Software Development :: Libraries :: Python Modules'
+        "Intended Audience :: Developers",
+        "License :: OSI Approved :: BSD License",
+        "Operating System :: MacOS :: MacOS X",
+        "Operating System :: POSIX",
+        "Programming Language :: Python",
+        "Programming Language :: Python :: 2.7",
+        "Programming Language :: Python :: 3.3",
+        "Programming Language :: Python :: 3.4",
+        "Topic :: Software Development :: Libraries :: Python Modules"
     ]
 )
diff --git a/src/pyotp.egg-info/PKG-INFO b/src/pyotp.egg-info/PKG-INFO
index 1a62fa1..7a8312d 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.2.1
+Version: 2.2.6
 Summary: Python One Time Password Library
 Home-page: https://github.com/pyotp/pyotp
 Author: PyOTP contributors
@@ -20,13 +20,15 @@ Description: PyOTP - The Python One-Time Password Library
         compatible app. Users can set up auth tokens in their apps easily by using their phone camera to scan `otpauth://
         <https://github.com/google/google-authenticator/wiki/Key-Uri-Format>`_ QR codes provided by PyOTP.
         
+        We recommend that implementers read the `OWASP Authentication Cheat Sheet <https://www.owasp.org/index.php/Authentication_Cheat_Sheet>`_ and `NIST SP 800-63-3: Digital Authentication Guideline <https://pages.nist.gov/800-63-3/>`_ for a high level overview of authentication best practices.
+        
         Quick overview of using One Time Passwords on your phone
         --------------------------------------------------------
         
         * OTPs involve a shared secret, stored both on the phone and the server
         * OTPs can be generated on a phone without internet connectivity
-        * OTPs should always be used as a second factor of authentication(if your phone is lost, you account is still secured with a password)
-        * Google Authenticator allows you to store multiple OTP secrets and provision those using a QR Code(no more typing in the secret)
+        * OTPs should always be used as a second factor of authentication (if your phone is lost, you account is still secured with a password)
+        * Google Authenticator and other OTP client apps allow you to store multiple OTP secrets and provision those using a QR Code
         
         Installation
         ------------
@@ -37,7 +39,7 @@ Description: PyOTP - The Python One-Time Password Library
         Usage
         -----
         
-        Time based OTPs
+        Time-based OTPs
         ~~~~~~~~~~~~~~~
         ::
         
@@ -66,23 +68,29 @@ Description: PyOTP - The Python One-Time Password Library
         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         ::
         
-            pyotp.random_base32() # returns a 16 character base32 secret. Compatible with Google Authenticator
+            pyotp.random_base32() # returns a 16 character base32 secret. Compatible with Google Authenticator and other OTP apps
         
         Google Authenticator Compatible
         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         
-        The library works with the Google Authenticator iPhone and Android app, and also includes the ability to generate
-        provisioning URI's for use with the QR Code scanner built into the app::
+        PyOTP works with the Google Authenticator iPhone and Android app, as well as other OTP apps like Authy. PyOTP includes the
+        ability to generate provisioning URIs for use with the QR Code scanner built into these MFA client apps::
+        
+            pyotp.totp.TOTP('JBSWY3DPEHPK3PXP').provisioning_uri("alice at google.com", issuer_name="Secure App")
+        
+            >>> 'otpauth://totp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure%20App'
+        
+            pyotp.hotp.HOTP('JBSWY3DPEHPK3PXP').provisioning_uri("alice at google.com", initial_count=0, issuer_name="Secure App")
         
-            totp.provisioning_uri("alice at google.com") # => 'otpauth://totp/alice@google.com?secret=JBSWY3DPEHPK3PXP'
-            hotp.provisioning_uri("alice at google.com", 0) # => 'otpauth://hotp/alice@google.com?secret=JBSWY3DPEHPK3PXP&counter=0'
+            >>> 'otpauth://hotp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure%20App&counter=0'
         
-        This can then be rendered as a QR Code which can then be scanned and added to the users list of OTP credentials.
+        This URL can then be rendered as a QR Code (for example, using https://github.com/neocotic/qrious) which can then be scanned
+        and added to the users list of OTP credentials.
         
         Working example
         ~~~~~~~~~~~~~~~
         
-        Scan the following barcode with your phone, using Google Authenticator
+        Scan the following barcode with your phone's OTP app (e.g. Google Authenticator):
         
         .. image:: http://chart.apis.google.com/chart?cht=qr&chs=250x250&chl=otpauth%3A%2F%2Ftotp%2Falice%40google.com%3Fsecret%3DJBSWY3DPEHPK3PXP
         
@@ -96,13 +104,15 @@ Description: PyOTP - The Python One-Time Password Library
         ~~~~~
         
         * `Project home page (GitHub) <https://github.com/pyotp/pyotp>`_
-        * `Documentation (Read the Docs) <https://pyotp.readthedocs.org/en/latest/>`_
+        * `Documentation (Read the Docs) <https://pyotp.readthedocs.io/en/latest/>`_
         * `Package distribution (PyPI) <https://pypi.python.org/pypi/pyotp>`_
         * `Change log <https://github.com/pyotp/pyotp/blob/master/Changes.rst>`_
         * `RFC 4226: HOTP: An HMAC-Based One-Time Password <https://tools.ietf.org/html/rfc4226>`_
         * `RFC 6238: TOTP: Time-Based One-Time Password Algorithm <https://tools.ietf.org/html/rfc6238>`_
         * `ROTP <https://github.com/mdp/rotp>`_ - Original Ruby OTP library by `Mark Percival <https://github.com/mdp>`_
         * `OTPHP <https://github.com/lelag/otphp>`_ - PHP port of ROTP by `Le Lag <https://github.com/lelag>`_
+        * `OWASP Authentication Cheat Sheet <https://www.owasp.org/index.php/Authentication_Cheat_Sheet>`_
+        * `NIST SP 800-63-3: Digital Authentication Guideline <https://pages.nist.gov/800-63-3/>`_
         
         .. image:: https://img.shields.io/travis/pyotp/pyotp.svg
                 :target: https://travis-ci.org/pyotp/pyotp
@@ -113,7 +123,7 @@ Description: PyOTP - The Python One-Time Password Library
         .. 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
-                :target: https://pyotp.readthedocs.org/
+                :target: https://pyotp.readthedocs.io/
         
 Platform: MacOS X
 Platform: Posix
diff --git a/src/pyotp/hotp.py b/src/pyotp/hotp.py
index 40ddcfa..d65199b 100644
--- a/src/pyotp/hotp.py
+++ b/src/pyotp/hotp.py
@@ -5,32 +5,48 @@ from .otp import OTP
 from .compat import str
 
 class HOTP(OTP):
+    """
+    Handler for HMAC-based OTP counters.
+    """
     def at(self, count):
         """
-        Generates the OTP for the given count
-        @param [Integer] count counter
-        @returns [Integer] OTP
+        Generates the OTP for the given count.
+
+        :param count: the OTP HMAC counter
+        :type count: int
+        :returns: OTP
+        :rtype: str
         """
         return self.generate_otp(count)
 
     def verify(self, otp, counter):
         """
-        Verifies the OTP passed in against the current time OTP
-        @param [String/Integer] otp the OTP to check against
-        @param [Integer] counter the counter of the OTP
+        Verifies the OTP passed in against the current time OTP.
+
+        :param otp: the OTP to check against
+        :type otp: str
+        :param count: the OTP HMAC counter
+        :type count: int
         """
         return utils.strings_equal(str(otp), str(self.at(counter)))
 
     def provisioning_uri(self, name, initial_count=0, issuer_name=None):
         """
-        Returns the provisioning URI for the OTP
-        This can then be encoded in a QR Code and used
-        to provision the Google Authenticator app
-        @param [String] name of the account
-        @param [Integer] initial_count starting counter value, defaults to 0
-        @param [String] the name of the OTP issuer; this will be the
+        Returns the provisioning URI for the OTP.  This can then be
+        encoded in a QR Code and used to provision an OTP app like
+        Google Authenticator.
+
+        See also:
+            https://github.com/google/google-authenticator/wiki/Key-Uri-Format
+
+        :param name: name of the user account
+        :type name: str
+        :param initial_count: starting HMAC counter value, defaults to 0
+        :type initial_count: int
+        :param issuer_name: the name of the OTP issuer; this will be the
             organization title of the OTP entry in Authenticator
-        @return [String] provisioning uri
+        :returns: provisioning URI
+        :rtype: str
         """
         return utils.build_uri(
             self.secret,
diff --git a/src/pyotp/otp.py b/src/pyotp/otp.py
index f0fe49f..45afcf3 100644
--- a/src/pyotp/otp.py
+++ b/src/pyotp/otp.py
@@ -6,16 +6,17 @@ import hmac
 from .compat import str
 
 class OTP(object):
+    """
+    Base class for OTP handlers.
+    """
     def __init__(self, s, digits=6, digest=hashlib.sha1):
         """
-        @param [String] secret in the form of base32
-        @option options digits [Integer] (6)
-            Number of integers in the OTP
-            Google Authenticate only supports 6 currently
-        @option options digest [Callable] (hashlib.sha1)
-            Digest used in the HMAC
-            Google Authenticate only supports 'sha1' currently
-        @returns [OTP] OTP instantiation
+        :param s: secret in base32 format
+        :type s: str
+        :param digits: number of integers in the OTP. Some apps expect this to be 6 digits, others support more.
+        :type digits: int
+        :param digest: digest function to use in the HMAC (expected to be sha1)
+        :type digest: callable
         """
         self.digits = digits
         self.digest = digest
@@ -23,10 +24,12 @@ class OTP(object):
 
     def generate_otp(self, input):
         """
-        @param [Integer] input the number used seed the HMAC
-        Usually either the counter, or the computed integer
-        based on the Unix timestamp
+        :param input: the HMAC counter value to use as the OTP input.
+            Usually either the counter, or the computed integer based on the Unix timestamp
+        :type input: int
         """
+        if input < 0:
+            raise ValueError('input must be positive integer')
         hasher = hmac.new(self.byte_secret(), self.int_to_bytestring(input), self.digest)
         hmac_hash = bytearray(hasher.digest())
         offset = hmac_hash[-1] & 0xf
diff --git a/src/pyotp/totp.py b/src/pyotp/totp.py
index 27f81b8..50faf8b 100644
--- a/src/pyotp/totp.py
+++ b/src/pyotp/totp.py
@@ -8,20 +8,27 @@ from .otp import OTP
 from .compat import str
 
 class TOTP(OTP):
+    """
+    Handler for time-based OTP counters.
+    """
     def __init__(self, *args, **kwargs):
         """
-        @option options [Integer] interval (30) the time interval in seconds
-            for OTP This defaults to 30 which is standard.
+        :param interval: the time interval in seconds
+            for OTP. This defaults to 30.
+        :type interval: int
         """
         self.interval = kwargs.pop('interval', 30)
         super(TOTP, self).__init__(*args, **kwargs)
 
     def at(self, for_time, counter_offset=0):
         """
-        Accepts either a Unix timestamp integer or a Time object.
-        @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
+        Accepts either a Unix timestamp integer or a datetime object.
+
+        :param for_time: the time to generate an OTP for
+        :type for_time: int or datetime
+        :param counter_offset: the amount of ticks to add to the time counter
+        :returns: OTP value
+        :rtype: str
         """
         if not isinstance(for_time, datetime.datetime):
             for_time = datetime.datetime.fromtimestamp(int(for_time))
@@ -30,16 +37,24 @@ class TOTP(OTP):
     def now(self):
         """
         Generate the current time OTP
-        @return [Integer] the OTP as an integer
+
+        :returns: OTP value
+        :rtype: str
         """
         return self.generate_otp(self.timecode(datetime.datetime.now()))
 
     def verify(self, otp, for_time=None, valid_window=0):
         """
-        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
+        Verifies the OTP passed in against the current time OTP.
+
+        :param otp: the OTP to check against
+        :type otp: str
+        :param for_time: Time to check OTP at (defaults to now)
+        :type for_time: int or datetime
+        :param valid_window: extends the validity to this many counter ticks before and after the current one
+        :type valid_window: int
+        :returns: True if verification succeeded, False otherwise
+        :rtype: bool
         """
         if for_time is None:
             for_time = datetime.datetime.now()
@@ -54,11 +69,19 @@ class TOTP(OTP):
 
     def provisioning_uri(self, name, issuer_name=None):
         """
-        Returns the provisioning URI for the OTP
-        This can then be encoded in a QR Code and used
-        to provision the Google Authenticator app
-        @param [String] name of the account
-        @return [String] provisioning uri
+        Returns the provisioning URI for the OTP.  This can then be
+        encoded in a QR Code and used to provision an OTP app like
+        Google Authenticator.
+
+        See also:
+            https://github.com/google/google-authenticator/wiki/Key-Uri-Format
+
+        :param name: name of the user account
+        :type name: str
+        :param issuer_name: the name of the OTP issuer; this will be the
+            organization title of the OTP entry in Authenticator
+        :returns: provisioning URI
+        :rtype: str
         """
         return utils.build_uri(self.secret, name, issuer_name=issuer_name,
                                algorithm=self.digest().name,
diff --git a/src/pyotp/utils.py b/src/pyotp/utils.py
index 16a13d5..1e52064 100644
--- a/src/pyotp/utils.py
+++ b/src/pyotp/utils.py
@@ -25,17 +25,25 @@ def build_uri(secret, name, initial_count=None, issuer_name=None,
     See also:
         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
-    @param [Integer] initial_count starting counter value, defaults to None.
+    :param secret: the hotp/totp secret used to generate the URI
+    :type secret: str
+    :param name: name of the account
+    :type name: str
+    :param initial_count: starting counter value, defaults to None.
         If none, the OTP type will be assumed as TOTP.
-    @param [String] the name of the OTP issuer; this will be the
+    :type initial_count: int
+    :param issuer_name: 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
+    :type issuer_name: str
+    :param algorithm: the algorithm used in the OTP generation.
+    :type algorithm: str
+    :param digits: the length of the OTP generated code.
+    :type digits: int
+    :param period: the number of seconds the OTP generator is set to
         expire every code.
-    @return [String] provisioning uri
+    :type period: int
+    :returns: provisioning uri
+    :rtype: str
     """
     # initial_count may be 0 as a valid param
     is_initial_count_present = (initial_count is not None)
@@ -46,7 +54,7 @@ def build_uri(secret, name, initial_count=None, issuer_name=None,
     is_period_set = (period is not None and period != 30)
 
     otp_type = 'hotp' if is_initial_count_present else 'totp'
-    base_uri = 'otpauth://{}/{}?{}'
+    base_uri = 'otpauth://{0}/{1}?{2}'
 
     url_args = {'secret': secret}
 
@@ -64,7 +72,7 @@ def build_uri(secret, name, initial_count=None, issuer_name=None,
     if is_period_set:
         url_args['period'] = period
 
-    uri = base_uri.format(otp_type, label, urlencode(url_args))
+    uri = base_uri.format(otp_type, label, urlencode(url_args).replace("+", "%20"))
     return uri
 
 
diff --git a/test.py b/test.py
index 395d475..d3cbc07 100755
--- a/test.py
+++ b/test.py
@@ -3,12 +3,9 @@
 
 from __future__ import absolute_import, division, print_function, unicode_literals
 
-import base64
-import datetime
-import hashlib
-import os
-import sys
-import unittest
+import base64, datetime, hashlib, os, sys, unittest
+from warnings import warn
+
 try:
     from urllib.parse import urlparse, parse_qsl
 except ImportError:
@@ -33,6 +30,11 @@ class HOTPExampleValuesFromTheRFC(unittest.TestCase):
         self.assertEqual(hotp.at(8), '399871')
         self.assertEqual(hotp.at(9), '520489')
 
+    def test_invalid_input(self):
+        hotp = pyotp.HOTP('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ')
+        with self.assertRaises(ValueError):
+            hotp.at(-1)
+
     def test_verify_otp_reuse(self):
         hotp = pyotp.HOTP('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ')
         self.assertTrue(hotp.verify('520489', 9))
@@ -81,14 +83,14 @@ class HOTPExampleValuesFromTheRFC(unittest.TestCase):
 
         hotp = pyotp.HOTP(key, digits=8)
         url = urlparse(
-            hotp.provisioning_uri('baco at peperina', issuer_name='FooCorp',
+            hotp.provisioning_uri('baco at peperina', issuer_name='Foo Corp',
                                   initial_count=10))
         self.assertEqual(url.scheme, 'otpauth')
         self.assertEqual(url.netloc, 'hotp')
-        self.assertEqual(url.path, '/FooCorp:baco%40peperina')
+        self.assertEqual(url.path, '/Foo%20Corp:baco%40peperina')
         self.assertEqual(dict(parse_qsl(url.query)),
                          {'secret': 'c7uxuqhgflpw7oruedmglbrk7u6242vb',
-                          'counter': '10', 'issuer': 'FooCorp',
+                          'counter': '10', 'issuer': 'Foo Corp',
                           'digits': '8'})
 
     def test_other_secret(self):
@@ -137,9 +139,9 @@ 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:
+                    warn("32-bit platforms use native functions to handle timestamps, so they fail this test" +
+                         " (and will fail after 19 January 2038)")
                     continue
                 value = totp.at(utime)
                 msg = "%s != %s (%s, time=%d)"
@@ -165,6 +167,14 @@ class TOTPExampleValuesFromTheRFC(unittest.TestCase):
         with Timecop(1297553958 + 30):
             self.assertFalse(totp.verify('102705'))
 
+    def test_input_before_epoch(self):
+        totp = pyotp.TOTP('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ')
+        # -1 and -29.5 round down to 0 (epoch)
+        self.assertEqual(totp.at(-1), '755224')
+        self.assertEqual(totp.at(-29.5), '755224')
+        with self.assertRaises(ValueError):
+            totp.at(-30)
+
     def test_validate_totp_with_digit_length(self):
         totp = pyotp.TOTP('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ')
         with Timecop(1111111111):

-- 
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