Skip to content

Commit 9872a81

Browse files
committed
Basic Python 3 compatibility
1 parent 2b5dd54 commit 9872a81

File tree

3 files changed

+88
-39
lines changed

3 files changed

+88
-39
lines changed

jwt/__init__.py

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
Minimum implementation based on this spec:
44
http://self-issued.info/docs/draft-jones-json-web-token-01.html
55
"""
6+
from __future__ import unicode_literals
67
import base64
8+
import binascii
79
import hashlib
810
import hmac
11+
import sys
912

1013
from datetime import datetime
1114
from calendar import timegm
@@ -19,6 +22,10 @@
1922
__all__ = ['encode', 'decode', 'DecodeError']
2023

2124

25+
if sys.version_info >= (3, 0, 0):
26+
unicode = str
27+
28+
2229
class DecodeError(Exception):
2330
pass
2431

@@ -43,26 +50,31 @@ def constant_time_compare(val1, val2):
4350
if len(val1) != len(val2):
4451
return False
4552
result = 0
46-
for x, y in zip(val1, val2):
47-
result |= ord(x) ^ ord(y)
53+
if sys.version_info >= (3, 0, 0): # bytes are numbers
54+
for x, y in zip(val1, val2):
55+
result |= x ^ y
56+
else:
57+
for x, y in zip(val1, val2):
58+
result |= ord(x) ^ ord(y)
4859
return result == 0
4960

5061

5162
def base64url_decode(input):
5263
rem = len(input) % 4
5364
if rem > 0:
54-
input += '=' * (4 - rem)
65+
input += b'=' * (4 - rem)
5566
return base64.urlsafe_b64decode(input)
5667

5768

5869
def base64url_encode(input):
59-
return base64.urlsafe_b64encode(input).replace('=', '')
70+
return base64.urlsafe_b64encode(input).replace(b'=', b'')
6071

6172

6273
def header(jwt):
63-
header_segment = jwt.split('.', 1)[0]
74+
header_segment = jwt.split(b'.', 1)[0]
6475
try:
65-
return json.loads(base64url_decode(header_segment))
76+
header_data = base64url_decode(header_segment).decode('utf-8')
77+
return json.loads(header_data)
6678
except (ValueError, TypeError):
6779
raise DecodeError("Invalid header encoding")
6880

@@ -77,49 +89,57 @@ def encode(payload, key, algorithm='HS256'):
7789

7890
# Header
7991
header = {"typ": "JWT", "alg": algorithm}
80-
segments.append(base64url_encode(json.dumps(header)))
92+
json_header = json.dumps(header).encode('utf-8')
93+
segments.append(base64url_encode(json_header))
8194

8295
# Payload
8396
if isinstance(payload.get('exp'), datetime):
8497
payload['exp'] = timegm(payload['exp'].utctimetuple())
85-
segments.append(base64url_encode(json.dumps(payload)))
98+
json_payload = json.dumps(payload).encode('utf-8')
99+
segments.append(base64url_encode(json_payload))
86100

87101
# Segments
88-
signing_input = '.'.join(segments)
102+
signing_input = b'.'.join(segments)
89103
try:
90104
if isinstance(key, unicode):
91105
key = key.encode('utf-8')
92106
signature = signing_methods[algorithm](signing_input, key)
93107
except KeyError:
94108
raise NotImplementedError("Algorithm not supported")
95109
segments.append(base64url_encode(signature))
96-
return '.'.join(segments)
110+
return b'.'.join(segments)
97111

98112

99113
def decode(jwt, key='', verify=True, verify_expiration=True, leeway=0):
114+
if isinstance(jwt, unicode):
115+
jwt = jwt.encode('utf-8')
100116
try:
101-
signing_input, crypto_segment = str(jwt).rsplit('.', 1)
102-
header_segment, payload_segment = signing_input.split('.', 1)
117+
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
118+
header_segment, payload_segment = signing_input.split(b'.', 1)
103119
except ValueError:
104120
raise DecodeError("Not enough segments")
105121

106122
try:
107-
header = json.loads(base64url_decode(header_segment))
108-
except TypeError:
123+
header_data = base64url_decode(header_segment)
124+
except (TypeError, binascii.Error):
109125
raise DecodeError("Invalid header padding")
126+
try:
127+
header = json.loads(header_data.decode('utf-8'))
110128
except ValueError as e:
111129
raise DecodeError("Invalid header string: %s" % e)
112130

113131
try:
114-
payload = json.loads(base64url_decode(payload_segment))
115-
except TypeError:
132+
payload_data = base64url_decode(payload_segment)
133+
except (TypeError, binascii.Error):
116134
raise DecodeError("Invalid payload padding")
135+
try:
136+
payload = json.loads(payload_data.decode('utf-8'))
117137
except ValueError as e:
118138
raise DecodeError("Invalid payload string: %s" % e)
119139

120140
try:
121141
signature = base64url_decode(crypto_segment)
122-
except TypeError:
142+
except (TypeError, binascii.Error):
123143
raise DecodeError("Invalid crypto padding")
124144

125145
if verify:

setup.py

100644100755
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#!/usr/bin/env python
12
import os
23
from setuptools import setup
34

@@ -21,4 +22,5 @@ def read(fname):
2122
"Topic :: Utilities",
2223
"License :: OSI Approved :: MIT License",
2324
],
25+
test_suite='tests.test_jwt'
2426
)

tests/test_jwt.py

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
import unittest
1+
from __future__ import unicode_literals
2+
from calendar import timegm
3+
from datetime import datetime
4+
import sys
25
import time
6+
import unittest
37

48
import jwt
59

6-
from datetime import datetime
7-
from calendar import timegm
10+
if sys.version_info >= (3, 0, 0):
11+
unicode = str
812

913

1014
def utc_timestamp():
@@ -14,7 +18,8 @@ def utc_timestamp():
1418
class TestJWT(unittest.TestCase):
1519

1620
def setUp(self):
17-
self.payload = {"iss": "jeff", "exp": utc_timestamp() + 1, "claim": "insanity"}
21+
self.payload = {"iss": "jeff", "exp": utc_timestamp() + 1,
22+
"claim": "insanity"}
1823

1924
def test_encode_decode(self):
2025
secret = 'secret'
@@ -36,7 +41,8 @@ def test_encode_expiration_datetime(self):
3641
payload = {"exp": current_datetime}
3742
jwt_message = jwt.encode(payload, secret)
3843
decoded_payload = jwt.decode(jwt_message, secret, leeway=1)
39-
self.assertEqual(decoded_payload['exp'],
44+
self.assertEqual(
45+
decoded_payload['exp'],
4046
timegm(current_datetime.utctimetuple()))
4147

4248
def test_bad_secret(self):
@@ -49,27 +55,29 @@ def test_bad_secret(self):
4955
def test_decodes_valid_jwt(self):
5056
example_payload = {"hello": "world"}
5157
example_secret = "secret"
52-
example_jwt = "eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8"
58+
example_jwt = (
59+
b"eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9"
60+
b".eyJoZWxsbyI6ICJ3b3JsZCJ9"
61+
b".tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8")
5362
decoded_payload = jwt.decode(example_jwt, example_secret)
5463
self.assertEqual(decoded_payload, example_payload)
5564

5665
def test_allow_skip_verification(self):
5766
right_secret = 'foo'
58-
bad_secret = 'bar'
5967
jwt_message = jwt.encode(self.payload, right_secret)
6068
decoded_payload = jwt.decode(jwt_message, verify=False)
6169
self.assertEqual(decoded_payload, self.payload)
6270

6371
def test_no_secret(self):
6472
right_secret = 'foo'
65-
bad_secret = 'bar'
6673
jwt_message = jwt.encode(self.payload, right_secret)
6774

6875
with self.assertRaises(jwt.DecodeError):
6976
jwt.decode(jwt_message)
7077

7178
def test_invalid_crypto_alg(self):
72-
self.assertRaises(NotImplementedError, jwt.encode, self.payload, "secret", "HS1024")
79+
self.assertRaises(NotImplementedError, jwt.encode, self.payload,
80+
"secret", "HS1024")
7381

7482
def test_unicode_secret(self):
7583
secret = u'\xc2'
@@ -78,47 +86,66 @@ def test_unicode_secret(self):
7886
self.assertEqual(decoded_payload, self.payload)
7987

8088
def test_nonascii_secret(self):
81-
secret = '\xc2' # char value that ascii codec cannot decode
89+
secret = '\xc2' # char value that ascii codec cannot decode
8290
jwt_message = jwt.encode(self.payload, secret)
8391
decoded_payload = jwt.decode(jwt_message, secret)
8492
self.assertEqual(decoded_payload, self.payload)
8593

8694
def test_decode_unicode_value(self):
8795
example_payload = {"hello": "world"}
8896
example_secret = "secret"
89-
example_jwt = unicode("eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8")
97+
example_jwt = (
98+
"eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9"
99+
".eyJoZWxsbyI6ICJ3b3JsZCJ9"
100+
".tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8")
90101
decoded_payload = jwt.decode(example_jwt, example_secret)
91102
self.assertEqual(decoded_payload, example_payload)
92103

93104
def test_decode_invalid_header_padding(self):
94-
example_jwt = unicode("aeyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8")
105+
example_jwt = (
106+
"aeyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9"
107+
".eyJoZWxsbyI6ICJ3b3JsZCJ9"
108+
".tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8")
95109
example_secret = "secret"
96110
with self.assertRaises(jwt.DecodeError):
97-
jwt_message = jwt.decode(example_jwt, example_secret)
111+
jwt.decode(example_jwt, example_secret)
98112

99113
def test_decode_invalid_header_string(self):
100-
example_jwt = unicode("eyJhbGciOiAiSFMyNTbpIiwgInR5cCI6ICJKV1QifQ==.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8")
114+
example_jwt = (
115+
"eyJhbGciOiAiSFMyNTbpIiwgInR5cCI6ICJKV1QifQ=="
116+
".eyJoZWxsbyI6ICJ3b3JsZCJ9"
117+
".tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8")
101118
example_secret = "secret"
102119
with self.assertRaisesRegexp(jwt.DecodeError, "Invalid header string"):
103-
jwt_message = jwt.decode(example_jwt, example_secret)
120+
jwt.decode(example_jwt, example_secret)
104121

105122
def test_decode_invalid_payload_padding(self):
106-
example_jwt = unicode("eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.aeyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8")
123+
example_jwt = (
124+
"eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9"
125+
".aeyJoZWxsbyI6ICJ3b3JsZCJ9"
126+
".tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8")
107127
example_secret = "secret"
108128
with self.assertRaises(jwt.DecodeError):
109-
jwt_message = jwt.decode(example_jwt, example_secret)
129+
jwt.decode(example_jwt, example_secret)
110130

111131
def test_decode_invalid_payload_string(self):
112-
example_jwt = unicode("eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsb-kiOiAid29ybGQifQ==.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8")
132+
example_jwt = (
133+
"eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9"
134+
".eyJoZWxsb-kiOiAid29ybGQifQ=="
135+
".tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8")
113136
example_secret = "secret"
114-
with self.assertRaisesRegexp(jwt.DecodeError, "Invalid payload string"):
115-
jwt_message = jwt.decode(example_jwt, example_secret)
137+
with self.assertRaisesRegexp(jwt.DecodeError,
138+
"Invalid payload string"):
139+
jwt.decode(example_jwt, example_secret)
116140

117141
def test_decode_invalid_crypto_padding(self):
118-
example_jwt = unicode("eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.aatvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8")
142+
example_jwt = (
143+
"eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9"
144+
".eyJoZWxsbyI6ICJ3b3JsZCJ9"
145+
".aatvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8")
119146
example_secret = "secret"
120147
with self.assertRaises(jwt.DecodeError):
121-
jwt_message = jwt.decode(example_jwt, example_secret)
148+
jwt.decode(example_jwt, example_secret)
122149

123150
def test_decode_with_expiration(self):
124151
self.payload['exp'] = utc_timestamp() - 1

0 commit comments

Comments
 (0)