PyOTP 2.0.1__tar.gz → 2.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.1
2
2
  Name: pyotp
3
- Version: 2.0.1
3
+ Version: 2.1.0
4
4
  Summary: Python One Time Password Library
5
5
  Home-page: https://github.com/pyotp/pyotp
6
6
  Author: PyOTP contributors
@@ -0,0 +1 @@
1
+ future==0.15.2
@@ -7,7 +7,7 @@ install_requires = [line.rstrip() for line in open(os.path.join(os.path.dirname(
7
7
 
8
8
  setup(
9
9
  name='pyotp',
10
- version='2.0.1',
10
+ version='2.1.0',
11
11
  url='https://github.com/pyotp/pyotp',
12
12
  license='BSD License',
13
13
  author='PyOTP contributors',
@@ -7,9 +7,6 @@ from pyotp.otp import OTP
7
7
  from pyotp.totp import TOTP
8
8
  from . import utils
9
9
 
10
- VERSION = '1.4.2'
11
-
12
-
13
10
  def random_base32(length=16, random=_random.SystemRandom(),
14
11
  chars=list('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567')):
15
12
  return ''.join(
@@ -2,7 +2,7 @@ from __future__ import print_function, unicode_literals, division, absolute_impo
2
2
 
3
3
  from pyotp.otp import OTP
4
4
  from pyotp import utils
5
-
5
+ from future.builtins import str
6
6
 
7
7
  class HOTP(OTP):
8
8
  def at(self, count):
@@ -3,7 +3,7 @@ from __future__ import print_function, unicode_literals, division, absolute_impo
3
3
  import base64
4
4
  import hashlib
5
5
  import hmac
6
-
6
+ from future.builtins import str
7
7
 
8
8
  class OTP(object):
9
9
  def __init__(self, s, digits=6, digest=hashlib.sha1):
@@ -46,6 +46,9 @@ class OTP(object):
46
46
  return str_code
47
47
 
48
48
  def byte_secret(self):
49
+ missing_padding = len(self.secret) % 8
50
+ if missing_padding != 0:
51
+ self.secret += '=' * (8 - missing_padding)
49
52
  return base64.b32decode(self.secret, casefold=True)
50
53
 
51
54
  @staticmethod
@@ -5,7 +5,7 @@ import time
5
5
 
6
6
  from pyotp import utils
7
7
  from pyotp.otp import OTP
8
-
8
+ from future.builtins import str
9
9
 
10
10
  class TOTP(OTP):
11
11
  def __init__(self, *args, **kwargs):
@@ -16,15 +16,16 @@ class TOTP(OTP):
16
16
  self.interval = kwargs.pop('interval', 30)
17
17
  super(TOTP, self).__init__(*args, **kwargs)
18
18
 
19
- def at(self, for_time):
19
+ def at(self, for_time, counter_offset=0):
20
20
  """
21
21
  Accepts either a Unix timestamp integer or a Time object.
22
22
  Time objects will be adjusted to UTC automatically
23
23
  @param [Time/Integer] time the time to generate an OTP for
24
+ @param [Integer] counter_offset an amount of ticks to add to the time counter
24
25
  """
25
26
  if not isinstance(for_time, datetime.datetime):
26
27
  for_time = datetime.datetime.fromtimestamp(int(for_time))
27
- return self.generate_otp(self.timecode(for_time))
28
+ return self.generate_otp(self.timecode(for_time) + counter_offset)
28
29
 
29
30
  def now(self):
30
31
  """
@@ -33,13 +34,20 @@ class TOTP(OTP):
33
34
  """
34
35
  return self.generate_otp(self.timecode(datetime.datetime.now()))
35
36
 
36
- def verify(self, otp, for_time=None):
37
+ def verify(self, otp, for_time=None, valid_window=0):
37
38
  """
38
39
  Verifies the OTP passed in against the current time OTP
39
40
  @param [String/Integer] otp the OTP to check against
41
+ @param [Integer] valid_window extends the validity to this many counter ticks before and after the current one
40
42
  """
41
43
  if for_time is None:
42
44
  for_time = datetime.datetime.now()
45
+
46
+ if valid_window:
47
+ for i in range(-valid_window, valid_window + 1):
48
+ if utils.strings_equal(str(otp), str(self.at(for_time, i))):
49
+ return True
50
+ return False
43
51
 
44
52
  return utils.strings_equal(str(otp), str(self.at(for_time)))
45
53
 
@@ -1,11 +1,12 @@
1
1
  from __future__ import print_function, unicode_literals, division, absolute_import
2
2
 
3
+ import unicodedata
4
+
3
5
  try:
4
6
  from urllib.parse import quote
5
7
  except ImportError:
6
8
  from urllib import quote
7
9
 
8
-
9
10
  def build_uri(secret, name, initial_count=None, issuer_name=None):
10
11
  """
11
12
  Returns the provisioning URI for the OTP; works for either TOTP or HOTP.
@@ -60,6 +61,8 @@ def strings_equal(s1, s2):
60
61
  still reveal to a timing attack whether the strings are the same
61
62
  length.
62
63
  """
64
+ s1 = unicodedata.normalize('NFKC', s1)
65
+ s2 = unicodedata.normalize('NFKC', s2)
63
66
  try:
64
67
  # Python 3.3+ and 2.7.7+ include a timing-attack-resistant
65
68
  # comparison function, which is probably more reliable than ours.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.1
2
2
  Name: pyotp
3
- Version: 2.0.1
3
+ Version: 2.1.0
4
4
  Summary: Python One Time Password Library
5
5
  Home-page: https://github.com/pyotp/pyotp
6
6
  Author: PyOTP contributors
@@ -14,4 +14,5 @@ src/pyotp.egg-info/PKG-INFO
14
14
  src/pyotp.egg-info/SOURCES.txt
15
15
  src/pyotp.egg-info/dependency_links.txt
16
16
  src/pyotp.egg-info/not-zip-safe
17
+ src/pyotp.egg-info/requires.txt
17
18
  src/pyotp.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ future==0.15.2
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env python
2
+ # coding: utf-8
2
3
 
3
4
  from __future__ import print_function, unicode_literals, division, absolute_import
4
5
 
@@ -12,9 +13,8 @@ import unittest
12
13
  sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
13
14
  import pyotp
14
15
 
15
-
16
16
  class HOTPExampleValuesFromTheRFC(unittest.TestCase):
17
- def testMatchTheRFC(self):
17
+ def test_match_rfc(self):
18
18
  # 12345678901234567890 in Bas32
19
19
  # GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ
20
20
  hotp = pyotp.HOTP('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ')
@@ -29,13 +29,13 @@ class HOTPExampleValuesFromTheRFC(unittest.TestCase):
29
29
  self.assertEqual(hotp.at(8), '399871')
30
30
  self.assertEqual(hotp.at(9), '520489')
31
31
 
32
- def testVerifyAnOTPAndNowAllowReuse(self):
32
+ def test_verify_otp_reuse(self):
33
33
  hotp = pyotp.HOTP('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ')
34
34
  self.assertTrue(hotp.verify('520489', 9))
35
35
  self.assertFalse(hotp.verify('520489', 10))
36
36
  self.assertFalse(hotp.verify('520489', 10))
37
37
 
38
- def testProvisioningURI(self):
38
+ def test_provisioning_uri(self):
39
39
  hotp = pyotp.HOTP('wrn3pqx5uqxqvnqr')
40
40
 
41
41
  self.assertEqual(
@@ -50,6 +50,13 @@ class HOTPExampleValuesFromTheRFC(unittest.TestCase):
50
50
  hotp.provisioning_uri('mark@percival', issuer_name='FooCorp!'),
51
51
  'otpauth://hotp/FooCorp%21:mark@percival?secret=wrn3pqx5uqxqvnqr&counter=0&issuer=FooCorp%21')
52
52
 
53
+ def test_other_secret(self):
54
+ hotp = pyotp.HOTP('N3OVNIBRERIO5OHGVCMDGS4V4RJ3AUZOUN34J6FRM4P6JIFCG3ZA')
55
+ self.assertEqual(hotp.at(0), '737863')
56
+ self.assertEqual(hotp.at(1), '390601')
57
+ self.assertEqual(hotp.at(2), '363354')
58
+ self.assertEqual(hotp.at(3), '936780')
59
+ self.assertEqual(hotp.at(4), '654019')
53
60
 
54
61
  class TOTPExampleValuesFromTheRFC(unittest.TestCase):
55
62
  RFC_VALUES = {
@@ -81,7 +88,7 @@ class TOTPExampleValuesFromTheRFC(unittest.TestCase):
81
88
  ),
82
89
  }
83
90
 
84
- def testMatchTheRFC(self):
91
+ def test_match_rfc(self):
85
92
  for digest, secret in self.RFC_VALUES:
86
93
  totp = pyotp.TOTP(base64.b32encode(secret), 8, digest)
87
94
  for utime, code in self.RFC_VALUES[(digest, secret)]:
@@ -90,18 +97,18 @@ class TOTPExampleValuesFromTheRFC(unittest.TestCase):
90
97
  msg %= (value, code, digest().name, utime)
91
98
  self.assertEqual(value, str(code), msg)
92
99
 
93
- def testMatchTheRFCDigitLength(self):
100
+ def test_match_rfc_digit_length(self):
94
101
  totp = pyotp.TOTP('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ')
95
102
  self.assertEqual(totp.at(1111111111), '050471')
96
103
  self.assertEqual(totp.at(1234567890), '005924')
97
104
  self.assertEqual(totp.at(2000000000), '279037')
98
105
 
99
- def testMatchTheGoogleAuthenticatorOutput(self):
106
+ def test_match_google_authenticator_output(self):
100
107
  totp = pyotp.TOTP('wrn3pqx5uqxqvnqr')
101
108
  with Timecop(1297553958):
102
109
  self.assertEqual(totp.now(), '102705')
103
110
 
104
- def testValidateATimeBasedOTP(self):
111
+ def test_validate_totp(self):
105
112
  totp = pyotp.TOTP('wrn3pqx5uqxqvnqr')
106
113
  with Timecop(1297553958):
107
114
  self.assertTrue(totp.verify('102705'))
@@ -109,14 +116,14 @@ class TOTPExampleValuesFromTheRFC(unittest.TestCase):
109
116
  with Timecop(1297553958 + 30):
110
117
  self.assertFalse(totp.verify('102705'))
111
118
 
112
- def testValidateATimeBasedOTPWithDigitLength(self):
119
+ def test_validate_totp_with_digit_length(self):
113
120
  totp = pyotp.TOTP('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ')
114
121
  with Timecop(1111111111):
115
122
  self.assertTrue(totp.verify('050471'))
116
123
  with Timecop(1297553958 + 30):
117
124
  self.assertFalse(totp.verify('050471'))
118
125
 
119
- def testProvisioningURI(self):
126
+ def test_provisioning_uri(self):
120
127
  totp = pyotp.TOTP('wrn3pqx5uqxqvnqr')
121
128
  self.assertEqual(
122
129
  totp.provisioning_uri('mark@percival'),
@@ -126,13 +133,12 @@ class TOTPExampleValuesFromTheRFC(unittest.TestCase):
126
133
  totp.provisioning_uri('mark@percival', issuer_name='FooCorp!'),
127
134
  'otpauth://totp/FooCorp%21:mark@percival?secret=wrn3pqx5uqxqvnqr&issuer=FooCorp%21')
128
135
 
129
- def testRandomKeyGeneration(self):
136
+ def test_random_key_generation(self):
130
137
  self.assertEqual(len(pyotp.random_base32()), 16)
131
138
  self.assertEqual(len(pyotp.random_base32(length=20)), 20)
132
139
 
133
-
134
140
  class StringComparisonTest(unittest.TestCase):
135
- def testComparisons(self):
141
+ def test_comparisons(self):
136
142
  self.assertTrue(pyotp.utils.strings_equal("", ""))
137
143
  self.assertTrue(pyotp.utils.strings_equal("a", "a"))
138
144
  self.assertTrue(pyotp.utils.strings_equal("a" * 1000, "a" * 1000))
@@ -141,6 +147,22 @@ class StringComparisonTest(unittest.TestCase):
141
147
  self.assertFalse(pyotp.utils.strings_equal("a", ""))
142
148
  self.assertFalse(pyotp.utils.strings_equal("a" * 999 + "b", "a" * 1000))
143
149
 
150
+ def test_fullwidth_input(self):
151
+ self.assertTrue(pyotp.utils.strings_equal("xs12345", "xs12345"))
152
+
153
+ class CounterOffsetTest(unittest.TestCase):
154
+ def test_counter_offset(self):
155
+ totp = pyotp.TOTP("ABCDEFGH")
156
+ self.assertEqual(totp.at(200), "028307")
157
+ self.assertTrue(totp.at(200, 1), "681610")
158
+
159
+ class ValidWindowTest(unittest.TestCase):
160
+ def test_valid_window(self):
161
+ totp = pyotp.TOTP("ABCDEFGH")
162
+ self.assertTrue(totp.verify("451564", 200, 1))
163
+ self.assertTrue(totp.verify("028307", 200, 1))
164
+ self.assertTrue(totp.verify("681610", 200, 1))
165
+ self.assertFalse(totp.verify("195979", 200, 1))
144
166
 
145
167
  class Timecop(object):
146
168
  """
@@ -166,6 +188,5 @@ class Timecop(object):
166
188
  timecop = self
167
189
  return FrozenDateTime
168
190
 
169
-
170
191
  if __name__ == '__main__':
171
192
  unittest.main()
File without changes
File without changes
File without changes
File without changes
File without changes