PyOTP 1.3.1__tar.gz → 1.4.1__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.0
2
2
  Name: pyotp
3
- Version: 1.3.1
3
+ Version: 1.4.1
4
4
  Summary: https://github.com/nathforge/pyotp
5
5
  Home-page: https://github.com/nathforge/pyotp
6
6
  Author: Mark Percival (ported by Nathan Reynolds to Python)
@@ -3,7 +3,7 @@
3
3
  from distutils.core import setup
4
4
 
5
5
  setup(
6
- version='1.3.1',
6
+ version='1.4.1',
7
7
  url='https://github.com/nathforge/pyotp',
8
8
  name='pyotp',
9
9
  description='https://github.com/nathforge/pyotp',
@@ -1,6 +1,8 @@
1
1
  from pyotp.otp import OTP
2
2
  from pyotp.hotp import HOTP
3
3
  from pyotp.totp import TOTP
4
+ import utils
5
+
4
6
  import base64
5
7
  import random
6
8
 
@@ -1,5 +1,5 @@
1
1
  from pyotp.otp import OTP
2
- import urllib
2
+ from pyotp import utils
3
3
 
4
4
 
5
5
  class HOTP(OTP):
@@ -10,26 +10,30 @@ class HOTP(OTP):
10
10
  @returns [Integer] OTP
11
11
  """
12
12
  return self.generate_otp(count)
13
-
13
+
14
14
  def verify(self, otp, counter):
15
15
  """
16
16
  Verifies the OTP passed in against the current time OTP
17
17
  @param [String/Integer] otp the OTP to check against
18
18
  @param [Integer] counter the counter of the OTP
19
19
  """
20
- return otp == self.at(counter)
21
-
22
- def provisioning_uri(self, name, initial_count=0):
20
+ return unicode(otp) == unicode(self.at(counter))
21
+
22
+ def provisioning_uri(self, name, initial_count=0, issuer_name=None):
23
23
  """
24
24
  Returns the provisioning URI for the OTP
25
25
  This can then be encoded in a QR Code and used
26
26
  to provision the Google Authenticator app
27
27
  @param [String] name of the account
28
28
  @param [Integer] initial_count starting counter value, defaults to 0
29
+ @param [String] the name of the OTP issuer; this will be the
30
+ organization title of the OTP entry in Authenticator
29
31
  @return [String] provisioning uri
30
32
  """
31
- return 'otpauth://hotp/%(name)s?secret=%(secret)s&counter=%(initial_count)s' % {
32
- 'name': urllib.quote(name, safe='@'),
33
- 'secret': self.secret,
34
- 'initial_count': initial_count,
35
- }
33
+ return utils.build_uri(
34
+ self.secret,
35
+ name,
36
+ initial_count=initial_count,
37
+ issuer_name=issuer_name,
38
+ )
39
+
@@ -1,18 +1,18 @@
1
1
  from pyotp.otp import OTP
2
+ from pyotp import utils
2
3
  import datetime
3
4
  import time
4
- import urllib
5
5
 
6
6
 
7
7
  class TOTP(OTP):
8
8
  def __init__(self, *args, **kwargs):
9
9
  """
10
- @option options [Integer] interval (30) the time interval in seconds for OTP
11
- This defaults to 30 which is standard.
10
+ @option options [Integer] interval (30) the time interval in seconds
11
+ for OTP This defaults to 30 which is standard.
12
12
  """
13
13
  self.interval = kwargs.pop('interval', 30)
14
14
  super(TOTP, self).__init__(*args, **kwargs)
15
-
15
+
16
16
  def at(self, for_time):
17
17
  """
18
18
  Accepts either a Unix timestamp integer or a Time object.
@@ -22,14 +22,14 @@ class TOTP(OTP):
22
22
  if not isinstance(for_time, datetime.datetime):
23
23
  for_time = datetime.datetime.fromtimestamp(int(for_time))
24
24
  return self.generate_otp(self.timecode(for_time))
25
-
25
+
26
26
  def now(self):
27
27
  """
28
28
  Generate the current time OTP
29
29
  @return [Integer] the OTP as an integer
30
30
  """
31
31
  return self.generate_otp(self.timecode(datetime.datetime.now()))
32
-
32
+
33
33
  def verify(self, otp, for_time=None):
34
34
  """
35
35
  Verifies the OTP passed in against the current time OTP
@@ -37,9 +37,10 @@ class TOTP(OTP):
37
37
  """
38
38
  if for_time is None:
39
39
  for_time = datetime.datetime.now()
40
- return otp == self.at(for_time)
41
-
42
- def provisioning_uri(self, name):
40
+
41
+ return unicode(otp) == unicode(self.at(for_time))
42
+
43
+ def provisioning_uri(self, name, issuer_name=None):
43
44
  """
44
45
  Returns the provisioning URI for the OTP
45
46
  This can then be encoded in a QR Code and used
@@ -47,11 +48,8 @@ class TOTP(OTP):
47
48
  @param [String] name of the account
48
49
  @return [String] provisioning uri
49
50
  """
50
- return 'otpauth://totp/%(name)s?secret=%(secret)s' % {
51
- 'name': urllib.quote(name, safe='@'),
52
- 'secret': self.secret,
53
- }
54
-
51
+ return utils.build_uri(self.secret, name, issuer_name=issuer_name)
52
+
55
53
  def timecode(self, for_time):
56
54
  i = time.mktime(for_time.timetuple())
57
55
  return int(i / self.interval)
@@ -0,0 +1,46 @@
1
+ import urllib
2
+
3
+
4
+ def build_uri(secret, name, initial_count=None, issuer_name=None):
5
+ """
6
+ Returns the provisioning URI for the OTP; works for either TOTP or HOTP.
7
+
8
+ This can then be encoded in a QR Code and used to provision the Google
9
+ Authenticator app.
10
+
11
+ For module-internal use.
12
+
13
+ See also:
14
+ http://code.google.com/p/google-authenticator/wiki/KeyUriFormat
15
+
16
+ @param [String] the hotp/totp secret used to generate the URI
17
+ @param [String] name of the account
18
+ @param [Integer] initial_count starting counter value, defaults to None.
19
+ If none, the OTP type will be assumed as TOTP.
20
+ @param [String] the name of the OTP issuer; this will be the
21
+ organization title of the OTP entry in Authenticator
22
+ @return [String] provisioning uri
23
+ """
24
+ # initial_count may be 0 as a valid param
25
+ is_initial_count_present = (initial_count != None)
26
+
27
+ otp_type = 'hotp' if is_initial_count_present else 'totp'
28
+ base = 'otpauth://%s/' % otp_type
29
+
30
+ if issuer_name:
31
+ issuer_name = urllib.quote(issuer_name)
32
+ base += '%s:' % issuer_name
33
+
34
+ uri = '%(base)s%(name)s?secret=%(secret)s' % {
35
+ 'name': urllib.quote(name, safe='@'),
36
+ 'secret': secret,
37
+ 'base': base,
38
+ }
39
+
40
+ if is_initial_count_present:
41
+ uri += '&counter=%s' % initial_count
42
+
43
+ if issuer_name:
44
+ uri += '&issuer=%s' % issuer_name
45
+
46
+ return uri
@@ -23,15 +23,27 @@ class HOTPExampleValuesFromTheRFC(unittest.TestCase):
23
23
  self.assertEqual(hotp.at(7), 162583)
24
24
  self.assertEqual(hotp.at(8), 399871)
25
25
  self.assertEqual(hotp.at(9), 520489)
26
-
26
+
27
27
  def testVerifyAnOTPAndNowAllowReuse(self):
28
28
  hotp = pyotp.HOTP('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ')
29
29
  self.assertTrue(hotp.verify(520489, 9))
30
30
  self.assertFalse(hotp.verify(520489, 10))
31
-
31
+ self.assertFalse(hotp.verify("520489", 10))
32
+
32
33
  def testProvisioningURI(self):
33
34
  hotp = pyotp.HOTP('wrn3pqx5uqxqvnqr')
34
- self.assertEqual(hotp.provisioning_uri('mark@percival'), 'otpauth://hotp/mark@percival?secret=wrn3pqx5uqxqvnqr&counter=0')
35
+
36
+ self.assertEqual(
37
+ hotp.provisioning_uri('mark@percival'),
38
+ 'otpauth://hotp/mark@percival?secret=wrn3pqx5uqxqvnqr&counter=0')
39
+
40
+ self.assertEqual(
41
+ hotp.provisioning_uri('mark@percival', initial_count=12),
42
+ 'otpauth://hotp/mark@percival?secret=wrn3pqx5uqxqvnqr&counter=12')
43
+
44
+ self.assertEqual(
45
+ hotp.provisioning_uri('mark@percival', issuer_name='FooCorp!'),
46
+ 'otpauth://hotp/FooCorp%21:mark@percival?secret=wrn3pqx5uqxqvnqr&counter=0&issuer=FooCorp%21')
35
47
 
36
48
 
37
49
  class TOTPExampleValuesFromTheRFC(unittest.TestCase):
@@ -40,45 +52,52 @@ class TOTPExampleValuesFromTheRFC(unittest.TestCase):
40
52
  self.assertEqual(totp.at(1111111111), 50471)
41
53
  self.assertEqual(totp.at(1234567890), 5924)
42
54
  self.assertEqual(totp.at(2000000000), 279037)
43
-
55
+
44
56
  def testMatchTheGoogleAuthenticatorOutput(self):
45
57
  totp = pyotp.TOTP('wrn3pqx5uqxqvnqr')
46
58
  with Timecop(1297553958):
47
59
  self.assertEqual(totp.now(), 102705)
48
-
60
+
49
61
  def testValidateATimeBasedOTP(self):
50
62
  totp = pyotp.TOTP('wrn3pqx5uqxqvnqr')
51
63
  with Timecop(1297553958):
52
64
  self.assertTrue(totp.verify(102705))
65
+ self.assertTrue(totp.verify("102705"))
53
66
  with Timecop(1297553958 + 30):
54
67
  self.assertFalse(totp.verify(102705))
55
-
68
+
56
69
  def testProvisioningURI(self):
57
70
  totp = pyotp.TOTP('wrn3pqx5uqxqvnqr')
58
- self.assertEqual(totp.provisioning_uri('mark@percival'), 'otpauth://totp/mark@percival?secret=wrn3pqx5uqxqvnqr')
71
+ self.assertEqual(
72
+ totp.provisioning_uri('mark@percival'),
73
+ 'otpauth://totp/mark@percival?secret=wrn3pqx5uqxqvnqr')
74
+
75
+ self.assertEqual(
76
+ totp.provisioning_uri('mark@percival', issuer_name='FooCorp!'),
77
+ 'otpauth://totp/FooCorp%21:mark@percival?secret=wrn3pqx5uqxqvnqr&issuer=FooCorp%21')
59
78
 
60
79
 
61
80
  class Timecop(object):
62
81
  """
63
82
  Half-assed clone of timecop.rb, just enough to pass our tests.
64
83
  """
65
-
84
+
66
85
  def __init__(self, freeze_timestamp):
67
86
  self.freeze_timestamp = freeze_timestamp
68
-
87
+
69
88
  def __enter__(self):
70
89
  self.real_datetime = datetime.datetime
71
90
  datetime.datetime = self.frozen_datetime()
72
-
91
+
73
92
  def __exit__(self, type, value, traceback):
74
93
  datetime.datetime = self.real_datetime
75
-
94
+
76
95
  def frozen_datetime(self):
77
96
  class FrozenDateTime(datetime.datetime):
78
97
  @classmethod
79
98
  def now(cls):
80
99
  return cls.fromtimestamp(timecop.freeze_timestamp)
81
-
100
+
82
101
  timecop = self
83
102
  return FrozenDateTime
84
103
 
File without changes
File without changes
File without changes