diff --git a/.deepsource.toml b/.deepsource.toml index 1fc2a332af..a352c02e4c 100644 --- a/.deepsource.toml +++ b/.deepsource.toml @@ -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 = [ diff --git a/.travis.yml b/.travis.yml index 55662c2acc..aa81fe51e4 100644 --- a/.travis.yml +++ b/.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: diff --git a/Makefile b/Makefile index 3356682d49..901acc11ce 100644 --- a/Makefile +++ b/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 diff --git a/README.md b/README.md index 30b54712ef..12f5d9c2da 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/requirements.txt b/requirements.txt index f488f49a27..29174d1c0c 100644 --- a/requirements.txt +++ b/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 diff --git a/setup.py b/setup.py index 01601dc213..ea06ff5b2a 100755 --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ -from __future__ import with_statement from setuptools import setup, find_packages with open('README.md') as f: @@ -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=[ @@ -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", diff --git a/tests/unit/jwt/test_client_validation.py b/tests/unit/jwt/test_client_validation.py index 5e0ddb8995..60c183fc8f 100644 --- a/tests/unit/jwt/test_client_validation.py +++ b/tests/unit/jwt/test_client_validation.py @@ -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', @@ -282,5 +282,3 @@ def test_jwt_signing(self): 'cty': 'twilio-pkrv;v=1', 'kid': 'CR123' }, decoded.headers) - - diff --git a/tests/unit/jwt/test_jwt.py b/tests/unit/jwt/test_jwt.py index 053bb610bf..e9ac4f0c1b 100644 --- a/tests/unit/jwt/test_jwt.py +++ b/tests/unit/jwt/test_jwt.py @@ -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 @@ -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) @@ -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', @@ -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', @@ -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) @@ -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') diff --git a/tests/unit/test_request_validator.py b/tests/unit/test_request_validator.py index 245c5ae43a..86478d6746 100644 --- a/tests/unit/test_request_validator.py +++ b/tests/unit/test_request_validator.py @@ -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 @@ -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) diff --git a/tests/unit/twiml/__init__.py b/tests/unit/twiml/__init__.py index b589cf460e..70b95d781e 100644 --- a/tests/unit/twiml/__init__.py +++ b/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, @@ -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): diff --git a/tests/unit/twiml/test_voice_response.py b/tests/unit/twiml/test_voice_response.py index 7f7e478556..6a90112585 100644 --- a/tests/unit/twiml/test_voice_response.py +++ b/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 @@ -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), diff --git a/tox.ini b/tox.ini index 73258c971a..57665fcb19 100644 --- a/tox.ini +++ b/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] diff --git a/twilio/base/exceptions.py b/twilio/base/exceptions.py index fdf9903d8d..ec14747f02 100644 --- a/twilio/base/exceptions.py +++ b/twilio/base/exceptions.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- import sys -from six import u - class TwilioException(Exception): pass @@ -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) diff --git a/twilio/base/values.py b/twilio/base/values.py index f2421c3a7f..b05f3390b5 100644 --- a/twilio/base/values.py +++ b/twilio/base/values.py @@ -1,4 +1,3 @@ -from six import iteritems unset = object() @@ -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} diff --git a/twilio/compat.py b/twilio/compat.py deleted file mode 100644 index de40b70089..0000000000 --- a/twilio/compat.py +++ /dev/null @@ -1,17 +0,0 @@ -# Those are not supported by the six library and needs to be done manually -try: - # python 3 - from urllib.parse import urlencode, urlparse, urljoin, urlunparse, parse_qs -except ImportError: - # python 2 backward compatibility - # noinspection PyUnresolvedReferences - from urllib import urlencode - # noinspection PyUnresolvedReferences - from urlparse import urlparse, urljoin, urlunparse, parse_qs - -try: - # python 2 - from itertools import izip -except ImportError: - # python 3 - izip = zip diff --git a/twilio/http/http_client.py b/twilio/http/http_client.py index a83493dd16..a9ce024a2a 100644 --- a/twilio/http/http_client.py +++ b/twilio/http/http_client.py @@ -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 diff --git a/twilio/http/request.py b/twilio/http/request.py index e96528dcff..49ec6e8faa 100644 --- a/twilio/http/request.py +++ b/twilio/http/request.py @@ -1,4 +1,4 @@ -from twilio.compat import urlencode +from urllib.parse import urlencode class Request(object): diff --git a/twilio/http/validation_client.py b/twilio/http/validation_client.py index 78abe1cbbc..e14ca7325a 100644 --- a/twilio/http/validation_client.py +++ b/twilio/http/validation_client.py @@ -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 diff --git a/twilio/jwt/__init__.py b/twilio/jwt/__init__.py index dd2ceed45f..e8793865d2 100644 --- a/twilio/jwt/__init__.py +++ b/twilio/jwt/__init__.py @@ -1,19 +1,4 @@ -import hmac -import sys - -from twilio.jwt import compat - -if sys.version_info[0] == 3 and sys.version_info[1] == 2: - # PyJWT expects hmac.compare_digest to exist even under python 3.2 - hmac.compare_digest = compat.compare_digest - import jwt as jwt_lib - -try: - import json -except ImportError: - import simplejson as json - import time @@ -27,8 +12,9 @@ class JwtDecodeError(Exception): class Jwt(object): """Base class for building a Json Web Token""" GENERATE = object() + ALGORITHM = 'HS256' - def __init__(self, secret_key, issuer, subject=None, algorithm='HS256', nbf=GENERATE, + def __init__(self, secret_key, issuer, subject=None, algorithm=None, nbf=GENERATE, ttl=3600, valid_until=None): self.secret_key = secret_key """:type str: The secret used to encode the JWT""" @@ -36,7 +22,7 @@ def __init__(self, secret_key, issuer, subject=None, algorithm='HS256', nbf=GENE """:type str: The issuer of this JWT""" self.subject = subject """:type str: The subject of this JWT, omitted from payload by default""" - self.algorithm = algorithm + self.algorithm = algorithm or self.ALGORITHM """:type str: The algorithm used to encode the JWT, defaults to 'HS256'""" self.nbf = nbf """:type int: Time in secs since epoch before which this JWT is invalid. Defaults to now.""" @@ -105,10 +91,9 @@ def headers(self): headers['alg'] = self.algorithm return headers - def to_jwt(self, algorithm=None, ttl=None): + def to_jwt(self, ttl=None): """ Encode this JWT object into a JWT string - :param str algorithm: override the algorithm used to encode the JWT :param int ttl: override the ttl configured in the constructor :rtype: str The JWT string """ @@ -117,15 +102,12 @@ def to_jwt(self, algorithm=None, ttl=None): raise ValueError('JWT does not have a signing key configured.') headers = self.headers.copy() - if algorithm: - headers['alg'] = algorithm - algorithm = algorithm or self.algorithm payload = self.payload.copy() if ttl: payload['exp'] = int(time.time()) + ttl - return jwt_lib.encode(payload, self.secret_key, algorithm=algorithm, headers=headers) + return jwt_lib.encode(payload, self.secret_key, algorithm=self.algorithm, headers=headers) @classmethod def from_jwt(cls, jwt, key=''): @@ -140,12 +122,18 @@ def from_jwt(cls, jwt, key=''): verify = True if key else False try: - payload = jwt_lib.decode(bytes(jwt), key, options={ + headers = jwt_lib.get_unverified_header(jwt) + + alg = headers.get('alg') + if alg != cls.ALGORITHM: + raise ValueError(f"Incorrect decoding algorithm {alg}, " + f"expecting {cls.ALGORITHM}.") + + payload = jwt_lib.decode(jwt, key, algorithms=[cls.ALGORITHM], options={ 'verify_signature': verify, 'verify_exp': True, 'verify_nbf': True, }) - headers = jwt_lib.get_unverified_header(jwt) except Exception as e: raise JwtDecodeError(getattr(e, 'message', str(e))) diff --git a/twilio/jwt/access_token/__init__.py b/twilio/jwt/access_token/__init__.py index f59f5cac0f..ce544e6684 100644 --- a/twilio/jwt/access_token/__init__.py +++ b/twilio/jwt/access_token/__init__.py @@ -20,6 +20,9 @@ def __str__(self): class AccessToken(Jwt): """Access Token containing one or more AccessTokenGrants used to access Twilio Resources""" + + ALGORITHM = 'HS256' + def __init__(self, account_sid, signing_key_sid, secret, grants=None, identity=None, nbf=Jwt.GENERATE, ttl=3600, valid_until=None, region=None): grants = grants or [] @@ -33,7 +36,7 @@ def __init__(self, account_sid, signing_key_sid, secret, grants=None, self.grants = grants super(AccessToken, self).__init__( secret_key=secret, - algorithm='HS256', + algorithm=self.ALGORITHM, issuer=signing_key_sid, subject=self.account_sid, nbf=nbf, diff --git a/twilio/jwt/client/__init__.py b/twilio/jwt/client/__init__.py index be0ede910b..13520e21e9 100644 --- a/twilio/jwt/client/__init__.py +++ b/twilio/jwt/client/__init__.py @@ -1,12 +1,13 @@ from twilio.jwt import Jwt -from six import iteritems -from twilio.compat import urlencode +from urllib.parse import urlencode class ClientCapabilityToken(Jwt): """A token to control permissions with Twilio Client""" + ALGORITHM = 'HS256' + def __init__(self, account_sid, auth_token, nbf=Jwt.GENERATE, ttl=3600, valid_until=None, **kwargs): """ @@ -21,7 +22,7 @@ def __init__(self, account_sid, auth_token, nbf=Jwt.GENERATE, ttl=3600, valid_un :returns: A new CapabilityToken with zero permissions """ super(ClientCapabilityToken, self).__init__( - algorithm='HS256', + algorithm=self.ALGORITHM, secret_key=auth_token, issuer=account_sid, nbf=nbf, @@ -94,7 +95,7 @@ def add_param(self, key, value): def to_payload(self): if self.params: - sorted_params = sorted([(k, v) for k, v in iteritems(self.params)]) + sorted_params = sorted([(k, v) for k, v in self.params.items()]) encoded_params = urlencode(sorted_params) param_string = '?{}'.format(encoded_params) else: diff --git a/twilio/jwt/compat.py b/twilio/jwt/compat.py deleted file mode 100644 index f0237c2f72..0000000000 --- a/twilio/jwt/compat.py +++ /dev/null @@ -1,25 +0,0 @@ -def compare_digest(a, b): - """ - PyJWT expects hmac.compare_digest to exist for all Python 3.x, however it was added in Python > 3.3 - It has a fallback for Python 2.x but not for Pythons between 2.x and 3.3 - Copied from: https://github.com/python/cpython/commit/6cea65555caf2716b4633827715004ab0291a282#diff-c49659257ec1b129707ce47a98adc96eL16 - - Returns the equivalent of 'a == b', but avoids content based short - circuiting to reduce the vulnerability to timing attacks. - """ - # Consistent timing matters more here than data type flexibility - if not (isinstance(a, bytes) and isinstance(b, bytes)): - raise TypeError("inputs must be bytes instances") - - # We assume the length of the expected digest is public knowledge, - # thus this early return isn't leaking anything an attacker wouldn't - # already know - if len(a) != len(b): - return False - - # We assume that integers in the bytes range are all cached, - # thus timing shouldn't vary much due to integer object creation - result = 0 - for x, y in zip(a, b): - result |= x ^ y - return result == 0 diff --git a/twilio/jwt/taskrouter/__init__.py b/twilio/jwt/taskrouter/__init__.py index 3095ae73a7..bace17e437 100644 --- a/twilio/jwt/taskrouter/__init__.py +++ b/twilio/jwt/taskrouter/__init__.py @@ -5,6 +5,7 @@ class TaskRouterCapabilityToken(Jwt): VERSION = 'v1' DOMAIN = 'https://taskrouter.twilio.com' EVENTS_BASE_URL = 'https://event-bridge.twilio.com/v1/wschannels' + ALGORITHM = 'HS256' def __init__(self, account_sid, auth_token, workspace_sid, channel_id, **kwargs): """ @@ -28,7 +29,7 @@ def __init__(self, account_sid, auth_token, workspace_sid, channel_id, **kwargs) super(TaskRouterCapabilityToken, self).__init__( secret_key=auth_token, issuer=account_sid, - algorithm='HS256', + algorithm=self.ALGORITHM, nbf=kwargs.get('nbf', Jwt.GENERATE), ttl=kwargs.get('ttl', 3600), valid_until=kwargs.get('valid_until', None), @@ -131,4 +132,3 @@ def _validate_inputs(self, account_sid, workspace_sid, channel_id): def __str__(self): return ''.format(self.to_jwt()) - diff --git a/twilio/jwt/validation/__init__.py b/twilio/jwt/validation/__init__.py index 63b9292bcf..a5536ebd49 100644 --- a/twilio/jwt/validation/__init__.py +++ b/twilio/jwt/validation/__init__.py @@ -1,5 +1,4 @@ from hashlib import sha256 -from six import string_types from twilio.jwt import Jwt @@ -7,6 +6,7 @@ class ClientValidationJwt(Jwt): """A JWT included on requests so that Twilio can verify request authenticity""" __CTY = 'twilio-pkrv;v=1' + ALGORITHM = 'RS256' def __init__(self, account_sid, api_key_sid, credential_sid, private_key, validation_payload): """ @@ -22,7 +22,7 @@ def __init__(self, account_sid, api_key_sid, credential_sid, private_key, valida secret_key=private_key, issuer=api_key_sid, subject=account_sid, - algorithm='RS256', + algorithm=self.ALGORITHM, ttl=300 # 5 minute ttl ) self.credential_sid = credential_sid @@ -73,7 +73,7 @@ def _generate_payload(self): @classmethod def _sort_and_join(cls, values, joiner): - if isinstance(values, string_types): + if isinstance(values, str): return values return joiner.join(sorted(values)) diff --git a/twilio/request_validator.py b/twilio/request_validator.py index 7e57923f5a..9f11925d98 100644 --- a/twilio/request_validator.py +++ b/twilio/request_validator.py @@ -2,9 +2,7 @@ import hmac from hashlib import sha1, sha256 -from six import PY3, string_types - -from twilio.compat import izip, urlparse, parse_qs +from urllib.parse import urlparse, parse_qs def compare(string1, string2): @@ -19,7 +17,7 @@ def compare(string1, string2): if len(string1) != len(string2): return False result = True - for c1, c2 in izip(string1, string2): + for c1, c2 in zip(string1, string2): result &= c1 == c2 return result @@ -65,12 +63,11 @@ class RequestValidator(object): def __init__(self, token): self.token = token.encode("utf-8") - def compute_signature(self, uri, params, utf=PY3): + def compute_signature(self, uri, params): """Compute the signature for a given request :param uri: full URI that Twilio requested on your server :param params: post vars that Twilio sent with the request - :param utf: whether return should be bytestring or unicode (python3) :returns: The computed signature """ @@ -82,8 +79,7 @@ def compute_signature(self, uri, params, utf=PY3): # compute signature and compare signatures mac = hmac.new(self.token, s.encode("utf-8"), sha1) computed = base64.b64encode(mac.digest()) - if utf: - computed = computed.decode('utf-8') + computed = computed.decode('utf-8') return computed.strip() @@ -113,7 +109,7 @@ def validate(self, uri, params, signature): valid_body_hash = True # May not receive body hash, so default succeed query = parse_qs(parsed_uri.query) - if "bodySHA256" in query and isinstance(params, string_types): + if "bodySHA256" in query and isinstance(params, str): valid_body_hash = compare(self.compute_hash(params), query["bodySHA256"][0]) params = {} diff --git a/twilio/rest/__init__.py b/twilio/rest/__init__.py index d263207c93..29e7994582 100644 --- a/twilio/rest/__init__.py +++ b/twilio/rest/__init__.py @@ -8,13 +8,10 @@ import os import platform +from urllib.parse import urlparse,urlunparse from twilio import __version__ from twilio.base.exceptions import TwilioException from twilio.base.obsolete import obsolete_client -from twilio.compat import ( - urlparse, - urlunparse, -) from twilio.http.http_client import TwilioHttpClient