Skip to content

Commit e2df36a

Browse files
karlsthinkingseriouseshanholtzshwetha-manvinkurkeJenniferMah
authored
fix!: update code and tests for pyjwt>=2.0.0 (#560)
Co-authored-by: Elmer Thomas <[email protected]> Co-authored-by: Elise Shanholtz <[email protected]> Co-authored-by: Shwetha Radhakrishna <[email protected]> Co-authored-by: Jennifer Mah <[email protected]> Co-authored-by: Jennifer Mah <[email protected]>
1 parent 4147180 commit e2df36a

26 files changed

+72
-190
lines changed

.deepsource.toml

+1-4
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,11 @@ version = 1
22

33
exclude_patterns = [
44
'examples/**',
5-
5+
66
# auto-generated files
77
'twilio/rest/**',
88
'twilio/twiml/**',
99
'tests/integration/**',
10-
11-
# compat files
12-
'twilio/compat.py',
1310
]
1411

1512
test_patterns = [

.travis.yml

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
dist: xenial
22
language: python
33
python:
4-
- '2.7'
5-
- '3.4'
6-
- '3.5'
7-
- '3.6'
8-
- '3.7'
9-
- '3.8'
10-
- '3.9'
4+
- "3.6"
5+
- "3.7"
6+
- "3.8"
7+
- "3.9"
118
services:
129
- docker
1310
jobs:

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
.PHONY: clean install analysis test test-install develop docs docs-install
22

33
venv:
4-
@python --version || (echo "Python is not installed, please install Python 2 or Python 3"; exit 1);
4+
@python --version || (echo "Python is not installed, Python 3.6+"; exit 1);
55
virtualenv --python=python venv
66

77
install: venv

README.md

-3
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,6 @@ Please consult the [official migration guide](https://www.twilio.com/docs/librar
2626

2727
This library supports the following Python implementations:
2828

29-
* Python 2.7
30-
* Python 3.4
31-
* Python 3.5
3229
* Python 3.6
3330
* Python 3.7
3431
* Python 3.8

requirements.txt

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
flake8
22
mock
33
nose
4-
six
54
requests>=2.0.0
6-
PyJWT==1.7.1
5+
PyJWT>=2.0.0, <3.0.0
76
twine

setup.py

+3-14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from __future__ import with_statement
21
from setuptools import setup, find_packages
32

43
with open('README.md') as f:
@@ -20,19 +19,12 @@
2019
author_email="[email protected]",
2120
url="https://github.com/twilio/twilio-python/",
2221
keywords=["twilio", "twiml"],
22+
python_requires='>=3.6.0',
2323
install_requires=[
24-
"six",
2524
"pytz",
26-
"PyJWT == 1.7.1",
25+
"requests >= 2.0.0",
26+
"PyJWT >= 2.0.0, < 3.0.0",
2727
],
28-
extras_require={
29-
':python_version<"3.0"': [
30-
"requests[security] >= 2.0.0",
31-
],
32-
':python_version>="3.0"': [
33-
"requests >= 2.0.0"
34-
],
35-
},
3628
packages=find_packages(exclude=['tests', 'tests.*']),
3729
include_package_data=True,
3830
classifiers=[
@@ -41,9 +33,6 @@
4133
"License :: OSI Approved :: MIT License",
4234
"Operating System :: OS Independent",
4335
"Programming Language :: Python",
44-
"Programming Language :: Python :: 2.7",
45-
"Programming Language :: Python :: 3.4",
46-
"Programming Language :: Python :: 3.5",
4736
"Programming Language :: Python :: 3.6",
4837
"Programming Language :: Python :: 3.7",
4938
"Programming Language :: Python :: 3.8",

tests/unit/jwt/test_client_validation.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ def test_jwt_signing(self):
266266
private_key = private_key.private_bytes(Encoding.PEM, PrivateFormat.PKCS8, NoEncryption())
267267

268268
jwt = ClientValidationJwt('AC123', 'SK123', 'CR123', private_key, vp)
269-
decoded = Jwt.from_jwt(jwt.to_jwt(), public_key)
269+
decoded = ClientValidationJwt.from_jwt(jwt.to_jwt(), public_key)
270270

271271
self.assertDictContainsSubset({
272272
'hrh': 'authorization;host',
@@ -282,5 +282,3 @@ def test_jwt_signing(self):
282282
'cty': 'twilio-pkrv;v=1',
283283
'kid': 'CR123'
284284
}, decoded.headers)
285-
286-

tests/unit/jwt/test_jwt.py

+15-38
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,17 @@
1010

1111
class DummyJwt(Jwt):
1212
"""Jwt implementation that allows setting arbitrary payload and headers for testing."""
13-
def __init__(self, secret_key, issuer, subject=None, algorithm='HS256', nbf=Jwt.GENERATE,
14-
ttl=3600, valid_until=None, headers=None, payload=None):
13+
14+
ALGORITHM = 'HS256'
15+
16+
def __init__(self, secret_key, issuer, subject=None, algorithm=None,
17+
nbf=Jwt.GENERATE, ttl=3600, valid_until=None, headers=None,
18+
payload=None):
1519
super(DummyJwt, self).__init__(
1620
secret_key=secret_key,
1721
issuer=issuer,
1822
subject=subject,
19-
algorithm=algorithm,
23+
algorithm=algorithm or self.ALGORITHM,
2024
nbf=nbf,
2125
ttl=ttl,
2226
valid_until=valid_until
@@ -43,7 +47,7 @@ def assertJwtsEqual(self, jwt, key, expected_payload=None, expected_headers=None
4347
expected_headers = expected_headers or {}
4448
expected_payload = expected_payload or {}
4549

46-
decoded_payload = jwt_lib.decode(jwt, key, verify=False)
50+
decoded_payload = jwt_lib.decode(jwt, key, algorithms=["HS256"], options={"verify_signature": False})
4751
decoded_headers = jwt_lib.get_unverified_header(jwt)
4852

4953
self.assertEqual(expected_headers, decoded_headers)
@@ -146,37 +150,11 @@ def test_encode_custom_nbf(self, time_mock):
146150
expected_payload={'iss': 'issuer', 'exp': 10, 'nbf': 5},
147151
)
148152

149-
@patch('time.time')
150-
def test_encode_custom_algorithm(self, time_mock):
151-
time_mock.return_value = 0.0
152-
153-
jwt = DummyJwt('secret_key', 'issuer', algorithm='HS512', headers={}, payload={})
154-
155-
self.assertJwtsEqual(
156-
jwt.to_jwt(), 'secret_key',
157-
expected_headers={'typ': 'JWT', 'alg': 'HS512'},
158-
expected_payload={'iss': 'issuer', 'exp': 3600, 'nbf': 0},
159-
)
160-
161-
@patch('time.time')
162-
def test_encode_override_algorithm(self, time_mock):
163-
time_mock.return_value = 0.0
164-
165-
jwt = DummyJwt('secret_key', 'issuer', algorithm='HS256', headers={}, payload={})
166-
167-
self.assertJwtsEqual(
168-
jwt.to_jwt(algorithm='HS512'),
169-
'secret_key',
170-
expected_headers={'typ': 'JWT', 'alg': 'HS512'},
171-
expected_payload={'iss': 'issuer', 'exp': 3600, 'nbf': 0},
172-
)
173-
174153
@patch('time.time')
175154
def test_encode_with_headers(self, time_mock):
176155
time_mock.return_value = 0.0
177156

178-
jwt = DummyJwt('secret_key', 'issuer', algorithm='HS256', headers={'sooper': 'secret'},
179-
payload={})
157+
jwt = DummyJwt('secret_key', 'issuer', headers={'sooper': 'secret'}, payload={})
180158

181159
self.assertJwtsEqual(
182160
jwt.to_jwt(), 'secret_key',
@@ -188,7 +166,7 @@ def test_encode_with_headers(self, time_mock):
188166
def test_encode_with_payload(self, time_mock):
189167
time_mock.return_value = 0.0
190168

191-
jwt = DummyJwt('secret_key', 'issuer', algorithm='HS256', payload={'root': 'true'})
169+
jwt = DummyJwt('secret_key', 'issuer', payload={'root': 'true'})
192170

193171
self.assertJwtsEqual(
194172
jwt.to_jwt(), 'secret_key',
@@ -208,10 +186,6 @@ def test_encode_with_payload_and_headers(self, time_mock):
208186
expected_payload={'iss': 'issuer', 'exp': 3600, 'nbf': 0, 'pay': 'me'},
209187
)
210188

211-
def test_encode_invalid_crypto_alg_fails(self):
212-
jwt = DummyJwt('secret_key', 'issuer', algorithm='PlzDontTouchAlgorithm')
213-
self.assertRaises(NotImplementedError, jwt.to_jwt)
214-
215189
def test_encode_no_key_fails(self):
216190
jwt = DummyJwt(None, 'issuer')
217191
self.assertRaises(ValueError, jwt.to_jwt)
@@ -236,15 +210,18 @@ def test_encode_decode(self):
236210
'sick': 'sick',
237211
}, decoded_jwt.payload)
238212

213+
def test_encode_decode_mismatched_algorithms(self):
214+
jwt = DummyJwt('secret_key', 'issuer', algorithm='HS512', subject='hey', payload={'sick': 'sick'})
215+
self.assertRaises(JwtDecodeError, Jwt.from_jwt, jwt.to_jwt())
216+
239217
def test_decode_bad_secret(self):
240218
jwt = DummyJwt('secret_key', 'issuer')
241219
self.assertRaises(JwtDecodeError, Jwt.from_jwt, jwt.to_jwt(), 'letmeinplz')
242220

243221
def test_decode_modified_jwt_fails(self):
244222
jwt = DummyJwt('secret_key', 'issuer')
245-
example_jwt = jwt.to_jwt().decode('utf-8')
223+
example_jwt = jwt.to_jwt()
246224
example_jwt = 'ABC' + example_jwt[3:]
247-
example_jwt = example_jwt.encode('utf-8')
248225

249226
self.assertRaises(JwtDecodeError, Jwt.from_jwt, example_jwt, 'secret_key')
250227

tests/unit/test_request_validator.py

+2-12
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import unittest
33

44
from nose.tools import assert_equal, assert_true
5-
from six import b, u
65

76
from twilio.request_validator import RequestValidator
87

@@ -26,22 +25,13 @@ def setUp(self):
2625
self.bodyHash = "0a1ff7634d9ab3b95db5c9a2dfe9416e41502b283a80c7cf19632632f96e6620"
2726
self.uriWithBody = self.uri + "&bodySHA256=" + self.bodyHash
2827

29-
def test_compute_signature_bytecode(self):
30-
expected = b(self.expected)
31-
signature = self.validator.compute_signature(self.uri,
32-
self.params,
33-
utf=False)
34-
assert_equal(signature, expected)
35-
3628
def test_compute_signature(self):
3729
expected = (self.expected)
38-
signature = self.validator.compute_signature(self.uri,
39-
self.params,
40-
utf=True)
30+
signature = self.validator.compute_signature(self.uri, self.params)
4131
assert_equal(signature, expected)
4232

4333
def test_compute_hash_unicode(self):
44-
expected = u(self.bodyHash)
34+
expected = self.bodyHash
4535
body_hash = self.validator.compute_hash(self.body)
4636

4737
assert_equal(expected, body_hash)

tests/unit/twiml/__init__.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import unittest
22

33
from nose.tools import raises
4-
from six import text_type
54

65
from twilio.twiml import (
76
format_language,
@@ -13,7 +12,7 @@
1312

1413
class TwilioTest(unittest.TestCase):
1514
def strip(self, xml):
16-
return text_type(xml)
15+
return str(xml)
1716

1817
@raises(TwiMLException)
1918
def test_append_fail(self):

tests/unit/twiml/test_voice_response.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# -*- coding: utf-8 -*-
22
from nose.tools import assert_equal
3-
from six import u
43
from tests.unit.twiml import TwilioTest
54
from twilio.twiml.voice_response import VoiceResponse, Dial, Enqueue, Gather
65

@@ -82,7 +81,7 @@ def test_say_hello_world(self):
8281
def test_say_french(self):
8382
""" should say hello monkey """
8483
r = VoiceResponse()
85-
r.say(u('n\xe9cessaire et d\'autres'))
84+
r.say('n\xe9cessaire et d\'autres')
8685

8786
assert_equal(
8887
self.strip(r),

tox.ini

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[tox]
2-
envlist = py27, py3{4,5,6,7,8,9}, pypy
2+
envlist = py3{6,7,8,9}, pypy
33
skip_missing_interpreters = true
44

55
[testenv]

twilio/base/exceptions.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
# -*- coding: utf-8 -*-
22
import sys
33

4-
from six import u
5-
64

75
class TwilioException(Exception):
86
pass
@@ -32,16 +30,16 @@ def __str__(self):
3230
""" Try to pretty-print the exception, if this is going on screen. """
3331

3432
def red(words):
35-
return u("\033[31m\033[49m%s\033[0m") % words
33+
return "\033[31m\033[49m%s\033[0m" % words
3634

3735
def white(words):
38-
return u("\033[37m\033[49m%s\033[0m") % words
36+
return "\033[37m\033[49m%s\033[0m" % words
3937

4038
def blue(words):
41-
return u("\033[34m\033[49m%s\033[0m") % words
39+
return "\033[34m\033[49m%s\033[0m" % words
4240

4341
def teal(words):
44-
return u("\033[36m\033[49m%s\033[0m") % words
42+
return "\033[36m\033[49m%s\033[0m" % words
4543

4644
def get_uri(code):
4745
return "https://www.twilio.com/docs/errors/{0}".format(code)

twilio/base/values.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from six import iteritems
21
unset = object()
32

43

@@ -9,4 +8,4 @@ def of(d):
98
:param dict d: A dict to strip.
109
:return dict: A dict with unset values removed.
1110
"""
12-
return {k: v for k, v in iteritems(d) if v != unset}
11+
return {k: v for k, v in d.items() if v != unset}

twilio/compat.py

-17
This file was deleted.

twilio/http/http_client.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from requests import Request, Session, hooks
44
from requests.adapters import HTTPAdapter
5-
from twilio.compat import urlencode
5+
from urllib.parse import urlencode
66
from twilio.http import HttpClient
77
from twilio.http.request import Request as TwilioRequest
88
from twilio.http.response import Response

twilio/http/request.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from twilio.compat import urlencode
1+
from urllib.parse import urlencode
22

33

44
class Request(object):

twilio/http/validation_client.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from requests import Request, Session
44

55
from twilio.base.exceptions import TwilioRestException
6-
from twilio.compat import urlparse
6+
from urllib.parse import urlparse
77
from twilio.http import HttpClient
88
from twilio.http.response import Response
99
from twilio.jwt.validation import ClientValidationJwt

0 commit comments

Comments
 (0)