|
1 | 1 | import unittest
|
2 | 2 | import jwt as jwt_lib
|
3 |
| -import time |
| 3 | +import time as real_time |
4 | 4 |
|
5 | 5 | from nose.tools import assert_true
|
6 | 6 | from mock import patch
|
@@ -37,153 +37,164 @@ def assertIn(self, foo, bar, msg=None):
|
37 | 37 | return assert_true(foo in bar, msg=(msg or "%s not found in %s" % (foo, bar)))
|
38 | 38 |
|
39 | 39 | def now(self):
|
40 |
| - return int(time.time()) |
| 40 | + return int(real_time.time()) |
| 41 | + |
| 42 | + def assertJwtsEqual(self, jwt, key, expected_payload=None, expected_headers=None): |
| 43 | + expected_headers = expected_headers or {} |
| 44 | + expected_payload = expected_payload or {} |
| 45 | + |
| 46 | + decoded_payload = jwt_lib.decode(jwt, key, verify=False) |
| 47 | + decoded_headers = jwt_lib.get_unverified_header(jwt) |
| 48 | + |
| 49 | + self.assertEqual(expected_headers, decoded_headers) |
| 50 | + self.assertEqual(expected_payload, decoded_payload) |
41 | 51 |
|
42 | 52 | @patch('time.time')
|
43 | 53 | def test_basic_encode(self, time_mock):
|
44 | 54 | time_mock.return_value = 0.0
|
45 | 55 |
|
46 | 56 | jwt = DummyJwt('secret_key', 'issuer', headers={}, payload={})
|
47 |
| - expected_jwt = jwt_lib.encode( |
48 |
| - {'iss': 'issuer', 'exp': 3600, 'nbf': 0}, |
49 |
| - 'secret_key', |
50 |
| - algorithm='HS256' |
51 |
| - ) |
52 | 57 |
|
53 |
| - self.assertEqual(expected_jwt, jwt.to_jwt()) |
| 58 | + self.assertJwtsEqual( |
| 59 | + jwt.to_jwt(), 'secret_key', |
| 60 | + expected_headers={'typ': 'JWT', 'alg': 'HS256'}, |
| 61 | + expected_payload={'iss': 'issuer', 'exp': 3600, 'nbf': 0}, |
| 62 | + ) |
54 | 63 |
|
55 | 64 | @patch('time.time')
|
56 | 65 | def test_encode_with_subject(self, time_mock):
|
57 | 66 | time_mock.return_value = 0.0
|
58 | 67 |
|
59 | 68 | jwt = DummyJwt('secret_key', 'issuer', subject='subject', headers={}, payload={})
|
60 |
| - expected_jwt = jwt_lib.encode( |
61 |
| - {'iss': 'issuer', 'exp': 3600, 'nbf': 0, 'sub': 'subject'}, |
62 |
| - 'secret_key', |
63 |
F438
td> | - algorithm='HS256' |
64 |
| - ) |
65 | 69 |
|
66 |
| - self.assertEqual(expected_jwt, jwt.to_jwt()) |
| 70 | + self.assertJwtsEqual( |
| 71 | + jwt.to_jwt(), 'secret_key', |
| 72 | + expected_headers={'typ': 'JWT', 'alg': 'HS256'}, |
| 73 | + expected_payload={'iss': 'issuer', 'exp': 3600, 'nbf': 0, 'sub': 'subject'}, |
| 74 | + ) |
67 | 75 |
|
68 | 76 | @patch('time.time')
|
69 | 77 | def test_encode_custom_ttl(self, time_mock):
|
70 | 78 | time_mock.return_value = 0.0
|
71 | 79 |
|
72 | 80 | jwt = DummyJwt('secret_key', 'issuer', ttl=10, headers={}, payload={})
|
73 |
| - expected_jwt = jwt_lib.encode( |
74 |
| - {'iss': 'issuer', 'exp': 10, 'nbf': 0}, |
75 |
| - 'secret_key', |
76 |
| - algorithm='HS256' |
77 |
| - ) |
78 | 81 |
|
79 |
| - self.assertEqual(expected_jwt, jwt.to_jwt()) |
| 82 | + self.assertJwtsEqual( |
| 83 | + jwt.to_jwt(), 'secret_key', |
| 84 | + expected_headers={'typ': 'JWT', 'alg': 'HS256'}, |
| 85 | + expected_payload={'iss': 'issuer', 'exp': 10, 'nbf': 0}, |
| 86 | + ) |
80 | 87 |
|
81 | 88 | @patch('time.time')
|
82 | 89 | def test_encode_ttl_added_to_current_time(self, time_mock):
|
83 | 90 | time_mock.return_value = 50.0
|
84 | 91 |
|
85 | 92 | jwt = DummyJwt('secret_key', 'issuer', ttl=10, headers={}, payload={})
|
86 |
| - expected_jwt = jwt_lib.encode( |
87 |
| - {'iss': 'issuer', 'exp': 60, 'nbf': 50}, |
88 |
| - 'secret_key', |
89 |
| - algorithm='HS256' |
90 |
| - ) |
91 | 93 |
|
92 |
| - self.assertEqual(expected_jwt, jwt.to_jwt()) |
| 94 | + self.assertJwtsEqual( |
| 95 | + jwt.to_jwt(), 'secret_key', |
| 96 | + expected_headers={'typ': 'JWT', 'alg': 'HS256'}, |
| 97 | + expected_payload={'iss': 'issuer', 'exp': 60, 'nbf': 50}, |
| 98 | + ) |
93 | 99 |
|
94 | 100 | @patch('time.time')
|
95 | 101 | def test_encode_override_ttl(self, time_mock):
|
96 | 102 | time_mock.return_value = 0.0
|
97 | 103 |
|
98 | 104 | jwt = DummyJwt('secret_key', 'issuer', ttl=10, headers={}, payload={})
|
99 |
| - expected_jwt = jwt_lib.encode( |
100 |
| - {'iss': 'issuer', 'exp': 20, 'nbf': 0}, |
| 105 | + |
| 106 | + self.assertJwtsEqual( |
| 107 | + jwt.to_jwt(ttl=20), |
101 | 108 | 'secret_key',
|
102 |
| - algorithm='HS256' |
| 109 | + expected_headers={'typ': 'JWT', 'alg': 'HS256'}, |
| 110 | + expected_payload={'iss': 'issuer', 'exp': 20, 'nbf': 0}, |
103 | 111 | )
|
104 | 112 |
|
105 |
| - self.assertEqual(expected_jwt, jwt.to_jwt(ttl=20)) |
106 |
| - |
107 | 113 | @patch('time.time')
|
108 | 114 | def test_encode_valid_until_overrides_ttl(self, time_mock):
|
109 | 115 | time_mock.return_value = 0.0
|
110 | 116 |
|
111 | 117 | jwt = DummyJwt('secret_key', 'issuer', ttl=10, valid_until=70, headers={}, payload={})
|
112 |
| - expected_jwt = jwt_lib.encode( |
113 |
| - {'iss': 'issuer', 'exp': 70, 'nbf': 0}, |
114 |
| - 'secret_key', |
115 |
| - algorithm='HS256' |
116 |
| - ) |
117 | 118 |
|
118 |
| - self.assertEqual(expected_jwt, jwt.to_jwt()) |
| 119 | + self.assertJwtsEqual( |
| 120 | + jwt.to_jwt(), 'secret_key', |
| 121 | + expected_headers={'typ': 'JWT', 'alg': 'HS256'}, |
| 122 | + expected_payload={'iss': 'issuer', 'exp': 70, 'nbf': 0}, |
| 123 | + ) |
119 | 124 |
|
120 | 125 | @patch('time.time')
|
121 | 126 | def test_encode_custom_nbf(self, time_mock):
|
122 | 127 | time_mock.return_value = 0.0
|
123 | 128 |
|
124 | 129 | jwt = DummyJwt('secret_key', 'issuer', ttl=10, nbf=5, headers={}, payload={})
|
125 |
| - expected_jwt = jwt_lib.encode( |
126 |
| - {'iss': 'issuer', 'exp': 10, 'nbf': 5}, |
127 |
| - 'secret_key', |
128 |
| - algorithm='HS256' |
129 |
| - ) |
130 | 130 |
|
131 |
| - self.assertEqual(expected_jwt, jwt.to_jwt()) |
| 131 | + self.assertJwtsEqual( |
| 132 | + jwt.to_jwt(), 'secret_key', |
| 133 | + expected_headers={'typ': 'JWT', 'alg': 'HS256'}, |
| 134 | + expected_payload={'iss': 'issuer', 'exp': 10, 'nbf': 5}, |
| 135 | + ) |
132 | 136 |
|
133 | 137 | @patch('time.time')
|
134 | 138 | def test_encode_custom_algorithm(self, time_mock):
|
135 | 139 | time_mock.return_value = 0.0
|
136 | 140 |
|
137 | 141 | jwt = DummyJwt('secret_key', 'issuer', algorithm='HS512', headers={}, payload={})
|
138 |
| - expected_jwt = jwt_lib.encode( |
139 |
| - {'iss': 'issuer', 'exp': 3600, 'nbf': 0}, |
140 |
| - 'secret_key', |
141 |
| - algorithm='HS512' |
142 |
| - ) |
143 | 142 |
|
144 |
| - self.assertEqual(expected_jwt, jwt.to_jwt()) |
| 143 | + self.assertJwtsEqual( |
| 144 | + jwt.to_jwt(), 'secret_key', |
| 145 | + expected_headers={'typ': 'JWT', 'alg': 'HS512'}, |
| 146 | + expected_payload={'iss': 'issuer', 'exp': 3600, 'nbf': 0}, |
| 147 | + ) |
145 | 148 |
|
146 | 149 | @patch('time.time')
|
147 |
| - def test_encode_with_headers(self, time_mock): |
| 150 | + def test_encode_override_algorithm(self, time_mock): |
148 | 151 | time_mock.return_value = 0.0
|
149 |
| - headers = {'sooper': 'secret'} |
150 | 152 |
|
151 |
| - jwt = DummyJwt('secret_key', 'issuer', algorithm='HS512', headers=headers, payload={}) |
152 |
| - expected_jwt = jwt_lib.encode( |
153 |
| - {'iss': 'issuer', 'exp': 3600, 'nbf': 0}, |
| 153 | + jwt = DummyJwt('secret_key', 'issuer', algorithm='HS256', headers={}, payload={}) |
| 154 | + |
| 155 | + self.assertJwtsEqual( |
| 156 | + jwt.to_jwt(algorithm='HS512'), |
154 | 157 | 'secret_key',
|
155 |
| - algorithm='HS512', |
156 |
| - headers=headers |
| 158 | + expected_headers={'typ': 'JWT', 'alg': 'HS512'}, |
| 159 | + expected_payload={'iss': 'issuer', 'exp': 3600, 'nbf': 0}, |
157 | 160 | )
|
158 | 161 |
|
159 |
| - self.assertEqual(expected_jwt, jwt.to_jwt()) |
| 162 | + @patch('time.time') |
| 163 | + def test_encode_with_headers(self, time_mock): |
| 164 | + time_mock.return_value = 0.0 |
| 165 | + |
| 166 | + jwt = DummyJwt('secret_key', 'issuer', algorithm='HS256', headers={'sooper': 'secret'}, |
| 167 | + payload={}) |
| 168 | + |
| 169 | + self.assertJwtsEqual( |
| 170 | + jwt.to_jwt(), 'secret_key', |
| 171 | + expected_headers={'typ': 'JWT', 'alg': 'HS256', 'sooper': 'secret'}, |
| 172 | + expected_payload={'iss': 'issuer', 'exp': 3600, 'nbf': 0}, |
| 173 | + ) |
160 | 174 |
|
161 | 175 | @patch('time.time')
|
162 | 176 | def test_encode_with_payload(self, time_mock):
|
163 | 177 | time_mock.return_value = 0.0
|
164 | 178 |
|
165 |
| - jwt = DummyJwt('secret_key', 'issuer', algorithm='HS512', payload={'root': 'true'}) |
166 |
| - expected_jwt = jwt_lib.encode( |
167 |
| - {'iss': 'issuer', 'exp': 3600, 'nbf': 0, 'root': 'true'}, |
168 |
| - 'secret_key', |
169 |
| - algorithm='HS512' |
170 |
| - ) |
| 179 | + jwt = DummyJwt('secret_key', 'issuer', algorithm='HS256', payload={'root': 'true'}) |
171 | 180 |
|
172 |
| - self.assertEqual(expected_jwt, jwt.to_jwt()) |
| 181 | + self.assertJwtsEqual( |
| 182 | + jwt.to_jwt(), 'secret_key', |
| 183 | + expected_headers={'typ': 'JWT', 'alg': 'HS256'}, |
| 184 | + expected_payload={'iss': 'issuer', 'exp': 3600, 'nbf': 0, 'root': 'true'}, |
| 185 | + ) |
173 | 186 |
|
174 | 187 | @patch('time.time')
|
175 | 188 | def test_encode_with_payload_and_headers(self, time_mock):
|
176 | 189 | time_mock.return_value = 0.0
|
177 | 190 |
|
178 | 191 | jwt = DummyJwt('secret_key', 'issuer', headers={'yes': 'oui'}, payload={'pay': 'me'})
|
179 |
| - expected_jwt = jwt_lib.encode( |
180 |
| - {'iss': 'issuer', 'exp': 3600, 'nbf': 0, 'pay': 'me'}, |
181 |
| - 'secret_key', |
182 |
| - algorithm='HS256', |
183 |
| - headers={'yes': 'oui'} |
184 |
| - ) |
185 | 192 |
|
186 |
| - self.assertEqual(expected_jwt, jwt.to_jwt()) |
| 193 | + self.assertJwtsEqual( |
| 194 | + jwt.to_jwt(), 'secret_key', |
| 195 | + expected_headers={'typ': 'JWT', 'alg': 'HS256', 'yes': 'oui'}, |
| 196 | + expected_payload={'iss': 'issuer', 'exp': 3600, 'nbf': 0, 'pay': 'me'}, |
| 197 | + ) |
187 | 198 |
|
188 | 199 | def test_encode_invalid_crypto_alg_fails(self):
|
189 | 200 | jwt = DummyJwt('secret_key', 'issuer', algorithm='PlzDontTouchAlgorithm')
|
@@ -217,6 +228,23 @@ def test_decode_bad_secret(self):
|
217 | 228 | jwt = DummyJwt('secret_key', 'issuer')
|
218 | 229 | self.assertRaises(JwtDecodeError, Jwt.from_jwt, jwt.to_jwt(), 'letmeinplz')
|
219 | 230 |
|
| 231 | + def test_decode_modified_jwt_fails(self): |
| 232 | + jwt = DummyJwt('secret_key', 'issuer') |
| 233 | + example_jwt = jwt.to_jwt().decode('utf-8') |
| 234 | + example_jwt = 'ABC' + example_jwt[3:] |
| 235 | + example_jwt = example_jwt.encode('utf-8') |
| 236 | + |
| 237 | + self.assertRaises(JwtDecodeError, Jwt.from_jwt, example_jwt, 'secret_key') |
| 238 | + |
| 239 | + def test_decode_validates_expiration(self): |
| 240 | + expired_jwt = DummyJwt('secret_key', 'issuer', valid_until=self.now()) |
| 241 | + real_time.sleep(1) |
| 242 | + self.assertRaises(JwtDecodeError, Jwt.from_jwt, expired_jwt.to_jwt(), 'secret_key') |
| 243 | + |
| 244 | + def test_decode_validates_nbf(self): |
| 245 | + expired_jwt = DummyJwt('secret_key', 'issuer', nbf=self.now() + 3600) # valid 1hr from now |
| 246 | + self.assertRaises(JwtDecodeError, Jwt.from_jwt, expired_jwt.to_jwt(), 'secret_key') |
| 247 | + |
220 | 248 | def test_decodes_valid_jwt(self):
|
221 | 249 | expiry_time = self.now() + 1000
|
222 | 250 | example_jwt = jwt_lib.encode(
|
|
0 commit comments