Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix!: update code and tests for pyjwt>=2.0.0 #560

Merged
merged 19 commits into from Sep 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 1 addition & 4 deletions .deepsource.toml
Expand Up @@ -2,14 +2,11 @@ version = 1

exclude_patterns = [
'examples/**',

# auto-generated files
'twilio/rest/**',
'twilio/twiml/**',
'tests/integration/**',

# compat files
'twilio/compat.py',
]

test_patterns = [
Expand Down
11 changes: 4 additions & 7 deletions .travis.yml
@@ -1,13 +1,10 @@
dist: xenial
language: python
python:
- '2.7'
- '3.4'
- '3.5'
- '3.6'
- '3.7'
- '3.8'
- '3.9'
- "3.6"
- "3.7"
- "3.8"
- "3.9"
services:
- docker
jobs:
Expand Down
2 changes: 1 addition & 1 deletion Makefile
@@ -1,7 +1,7 @@
.PHONY: clean install analysis test test-install develop docs docs-install

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

install: venv
Expand Down
3 changes: 0 additions & 3 deletions README.md
Expand Up @@ -26,9 +26,6 @@ Please consult the [official migration guide](https://www.twilio.com/docs/librar

This library supports the following Python implementations:

* Python 2.7
* Python 3.4
* Python 3.5
* Python 3.6
* Python 3.7
* Python 3.8
Expand Down
3 changes: 1 addition & 2 deletions requirements.txt
@@ -1,7 +1,6 @@
flake8
mock
nose
six
requests>=2.0.0
PyJWT==1.7.1
PyJWT>=2.0.0, <3.0.0
twine
17 changes: 3 additions & 14 deletions setup.py
@@ -1,4 +1,3 @@
from __future__ import with_statement
from setuptools import setup, find_packages

with open('README.md') as f:
Expand All @@ -20,19 +19,12 @@
author_email="help@twilio.com",
url="https://github.com/twilio/twilio-python/",
keywords=["twilio", "twiml"],
python_requires='>=3.6.0',
install_requires=[
"six",
"pytz",
"PyJWT == 1.7.1",
"requests >= 2.0.0",
"PyJWT >= 2.0.0, < 3.0.0",
],
extras_require={
':python_version<"3.0"': [
"requests[security] >= 2.0.0",
],
':python_version>="3.0"': [
"requests >= 2.0.0"
],
},
packages=find_packages(exclude=['tests', 'tests.*']),
include_package_data=True,
classifiers=[
Expand All @@ -41,9 +33,6 @@
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
Expand Down
4 changes: 1 addition & 3 deletions tests/unit/jwt/test_client_validation.py
Expand Up @@ -266,7 +266,7 @@ def test_jwt_signing(self):
private_key = private_key.private_bytes(Encoding.PEM, PrivateFormat.PKCS8, NoEncryption())

jwt = ClientValidationJwt('AC123', 'SK123', 'CR123', private_key, vp)
decoded = Jwt.from_jwt(jwt.to_jwt(), public_key)
decoded = ClientValidationJwt.from_jwt(jwt.to_jwt(), public_key)

self.assertDictContainsSubset({
'hrh': 'authorization;host',
Expand All @@ -282,5 +282,3 @@ def test_jwt_signing(self):
'cty': 'twilio-pkrv;v=1',
'kid': 'CR123'
}, decoded.headers)


53 changes: 15 additions & 38 deletions tests/unit/jwt/test_jwt.py
Expand Up @@ -10,13 +10,17 @@

class DummyJwt(Jwt):
"""Jwt implementation that allows setting arbitrary payload and headers for testing."""
def __init__(self, secret_key, issuer, subject=None, algorithm='HS256', nbf=Jwt.GENERATE,
ttl=3600, valid_until=None, headers=None, payload=None):

ALGORITHM = 'HS256'

def __init__(self, secret_key, issuer, subject=None, algorithm=None,
nbf=Jwt.GENERATE, ttl=3600, valid_until=None, headers=None,
payload=None):
super(DummyJwt, self).__init__(
secret_key=secret_key,
issuer=issuer,
subject=subject,
algorithm=algorithm,
algorithm=algorithm or self.ALGORITHM,
nbf=nbf,
ttl=ttl,
valid_until=valid_until
Expand All @@ -43,7 +47,7 @@ def assertJwtsEqual(self, jwt, key, expected_payload=None, expected_headers=None
expected_headers = expected_headers or {}
expected_payload = expected_payload or {}

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

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

@patch('time.time')
def test_encode_custom_algorithm(self, time_mock):
time_mock.return_value = 0.0

jwt = DummyJwt('secret_key', 'issuer', algorithm='HS512', headers={}, payload={})

self.assertJwtsEqual(
jwt.to_jwt(), 'secret_key',
expected_headers={'typ': 'JWT', 'alg': 'HS512'},
expected_payload={'iss': 'issuer', 'exp': 3600, 'nbf': 0},
)

@patch('time.time')
def test_encode_override_algorithm(self, time_mock):
time_mock.return_value = 0.0

jwt = DummyJwt('secret_key', 'issuer', algorithm='HS256', headers={}, payload={})

self.assertJwtsEqual(
jwt.to_jwt(algorithm='HS512'),
'secret_key',
expected_headers={'typ': 'JWT', 'alg': 'HS512'},
expected_payload={'iss': 'issuer', 'exp': 3600, 'nbf': 0},
)

@patch('time.time')
def test_encode_with_headers(self, time_mock):
time_mock.return_value = 0.0

jwt = DummyJwt('secret_key', 'issuer', algorithm='HS256', headers={'sooper': 'secret'},
payload={})
jwt = DummyJwt('secret_key', 'issuer', headers={'sooper': 'secret'}, payload={})

self.assertJwtsEqual(
jwt.to_jwt(), 'secret_key',
Expand All @@ -188,7 +166,7 @@ def test_encode_with_headers(self, time_mock):
def test_encode_with_payload(self, time_mock):
time_mock.return_value = 0.0

jwt = DummyJwt('secret_key', 'issuer', algorithm='HS256', payload={'root': 'true'})
jwt = DummyJwt('secret_key', 'issuer', payload={'root': 'true'})

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

def test_encode_invalid_crypto_alg_fails(self):
jwt = DummyJwt('secret_key', 'issuer', algorithm='PlzDontTouchAlgorithm')
self.assertRaises(NotImplementedError, jwt.to_jwt)

def test_encode_no_key_fails(self):
jwt = DummyJwt(None, 'issuer')
self.assertRaises(ValueError, jwt.to_jwt)
Expand All @@ -236,15 +210,18 @@ def test_encode_decode(self):
'sick': 'sick',
}, decoded_jwt.payload)

def test_encode_decode_mismatched_algorithms(self):
jwt = DummyJwt('secret_key', 'issuer', algorithm='HS512', subject='hey', payload={'sick': 'sick'})
self.assertRaises(JwtDecodeError, Jwt.from_jwt, jwt.to_jwt())

def test_decode_bad_secret(self):
jwt = DummyJwt('secret_key', 'issuer')
self.assertRaises(JwtDecodeError, Jwt.from_jwt, jwt.to_jwt(), 'letmeinplz')

def test_decode_modified_jwt_fails(self):
jwt = DummyJwt('secret_key', 'issuer')
example_jwt = jwt.to_jwt().decode('utf-8')
example_jwt = jwt.to_jwt()
example_jwt = 'ABC' + example_jwt[3:]
example_jwt = example_jwt.encode('utf-8')

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

Expand Down
14 changes: 2 additions & 12 deletions tests/unit/test_request_validator.py
Expand Up @@ -2,7 +2,6 @@
import unittest

from nose.tools import assert_equal, assert_true
from six import b, u

from twilio.request_validator import RequestValidator

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

def test_compute_signature_bytecode(self):
expected = b(self.expected)
signature = self.validator.compute_signature(self.uri,
self.params,
utf=False)
assert_equal(signature, expected)

def test_compute_signature(self):
expected = (self.expected)
signature = self.validator.compute_signature(self.uri,
self.params,
utf=True)
signature = self.validator.compute_signature(self.uri, self.params)
assert_equal(signature, expected)

def test_compute_hash_unicode(self):
expected = u(self.bodyHash)
expected = self.bodyHash
body_hash = self.validator.compute_hash(self.body)

assert_equal(expected, body_hash)
Expand Down
3 changes: 1 addition & 2 deletions tests/unit/twiml/__init__.py
@@ -1,7 +1,6 @@
import unittest

from nose.tools import raises
from six import text_type

from twilio.twiml import (
format_language,
Expand All @@ -13,7 +12,7 @@

class TwilioTest(unittest.TestCase):
def strip(self, xml):
return text_type(xml)
return str(xml)

@raises(TwiMLException)
def test_append_fail(self):
Expand Down
3 changes: 1 addition & 2 deletions tests/unit/twiml/test_voice_response.py
@@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
from nose.tools import assert_equal
from six import u
from tests.unit.twiml import TwilioTest
from twilio.twiml.voice_response import VoiceResponse, Dial, Enqueue, Gather

Expand Down Expand Up @@ -82,7 +81,7 @@ def test_say_hello_world(self):
def test_say_french(self):
""" should say hello monkey """
r = VoiceResponse()
r.say(u('n\xe9cessaire et d\'autres'))
r.say('n\xe9cessaire et d\'autres')

assert_equal(
self.strip(r),
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
@@ -1,5 +1,5 @@
[tox]
envlist = py27, py3{4,5,6,7,8,9}, pypy
envlist = py3{6,7,8,9}, pypy
skip_missing_interpreters = true

[testenv]
Expand Down
10 changes: 4 additions & 6 deletions twilio/base/exceptions.py
@@ -1,8 +1,6 @@
# -*- coding: utf-8 -*-
import sys

from six import u


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

def red(words):
return u("\033[31m\033[49m%s\033[0m") % words
return "\033[31m\033[49m%s\033[0m" % words

def white(words):
return u("\033[37m\033[49m%s\033[0m") % words
return "\033[37m\033[49m%s\033[0m" % words

def blue(words):
return u("\033[34m\033[49m%s\033[0m") % words
return "\033[34m\033[49m%s\033[0m" % words

def teal(words):
return u("\033[36m\033[49m%s\033[0m") % words
return "\033[36m\033[49m%s\033[0m" % words

def get_uri(code):
return "https://www.twilio.com/docs/errors/{0}".format(code)
Expand Down
3 changes: 1 addition & 2 deletions twilio/base/values.py
@@ -1,4 +1,3 @@
from six import iteritems
unset = object()


Expand All @@ -9,4 +8,4 @@ def of(d):
:param dict d: A dict to strip.
:return dict: A dict with unset values removed.
"""
return {k: v for k, v in iteritems(d) if v != unset}
return {k: v for k, v in d.items() if v != unset}
17 changes: 0 additions & 17 deletions twilio/compat.py

This file was deleted.

2 changes: 1 addition & 1 deletion twilio/http/http_client.py
Expand Up @@ -2,7 +2,7 @@

from requests import Request, Session, hooks
from requests.adapters import HTTPAdapter
from twilio.compat import urlencode
from urllib.parse import urlencode
from twilio.http import HttpClient
from twilio.http.request import Request as TwilioRequest
from twilio.http.response import Response
Expand Down
2 changes: 1 addition & 1 deletion twilio/http/request.py
@@ -1,4 +1,4 @@
from twilio.compat import urlencode
from urllib.parse import urlencode


class Request(object):
Expand Down
2 changes: 1 addition & 1 deletion twilio/http/validation_client.py
Expand Up @@ -3,7 +3,7 @@
from requests import Request, Session

from twilio.base.exceptions import TwilioRestException
from twilio.compat import urlparse
from urllib.parse import urlparse
from twilio.http import HttpClient
from twilio.http.response import Response
from twilio.jwt.validation import ClientValidationJwt
Expand Down