From 9e014671ce07af6747eb4632aa7ead713c0b47fe Mon Sep 17 00:00:00 2001 From: Awais Qureshi Date: Mon, 9 Aug 2021 11:16:55 +0500 Subject: [PATCH] feat!: Major upgrade to pyjwt 2.1.0 (#167) BREAKING CHANGES (edx-drf-extensions): - ``generate_jwt_token``: Now returns string (instead of bytes), and no longer requires decoding. This was to keep consistent with change to ``jwt.encode`` in `pyjwt` upgrade (see below). BREAKING CHANGES (pyjwt): - ``jwt.decode``: dropped deprecated verify param. - ``jwt.decode``: requires explicit algorithms argument. - ``jwt.decode``: Returns string (in place of bytes), and no longer requires decoding. - For more details, visit this: https://pyjwt.readthedocs.io/en/stable/changelog.html#v2-1-0 Co-authored-by: edX requirements bot <49161187+edx-requirements-bot@users.noreply.github.com> --- CHANGELOG.rst | 9 ++++++ edx_rest_framework_extensions/__init__.py | 2 +- .../auth/jwt/decoder.py | 1 - .../auth/jwt/tests/test_decoder.py | 30 ++++++++++++++++--- .../auth/jwt/tests/utils.py | 2 +- requirements/base.in | 1 + requirements/base.txt | 4 +-- requirements/common_constraints.txt | 3 ++ requirements/constraints.txt | 3 -- requirements/dev.txt | 3 +- requirements/test.txt | 3 +- 11 files changed, 45 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2dbe46f3..ae971f34 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,15 @@ Change Log Unreleased ---------- +[7.0.0] - 2021-08-03 +-------------------- + +Changed +~~~~~~~ + +* **BREAKING CHANGE:** ``generate_jwt_token``: Now returns string (instead of bytes), and no longer requires decoding. This was to keep consistent with change to ``jwt.encode`` in `pyjwt` upgrade (see below). +* **BREAKING CHANGE:** Upgraded dependency ``pyjwt[crypto]`` to 2.1.0, which introduces its own breaking changes that may affect consumers of this library. Pay careful attention to the 2.0.0 breaking changes documented in https://pyjwt.readthedocs.io/en/stable/changelog.html#v2-0-0. + [6.6.0] - 2021-07-13 -------------------- diff --git a/edx_rest_framework_extensions/__init__.py b/edx_rest_framework_extensions/__init__.py index 732a3d7e..75ccf1c1 100644 --- a/edx_rest_framework_extensions/__init__.py +++ b/edx_rest_framework_extensions/__init__.py @@ -1,3 +1,3 @@ """ edx Django REST Framework extensions. """ -__version__ = '6.6.0' # pragma: no cover +__version__ = '7.0.0' # pragma: no cover diff --git a/edx_rest_framework_extensions/auth/jwt/decoder.py b/edx_rest_framework_extensions/auth/jwt/decoder.py index d676ee36..6d9a9bd3 100644 --- a/edx_rest_framework_extensions/auth/jwt/decoder.py +++ b/edx_rest_framework_extensions/auth/jwt/decoder.py @@ -174,7 +174,6 @@ def _decode_and_verify_token(token, jwt_issuer): decoded_token = jwt.decode( token, jwt_issuer['SECRET_KEY'], - api_settings.JWT_VERIFY, options=options, leeway=api_settings.JWT_LEEWAY, audience=jwt_issuer['AUDIENCE'], diff --git a/edx_rest_framework_extensions/auth/jwt/tests/test_decoder.py b/edx_rest_framework_extensions/auth/jwt/tests/test_decoder.py index 9f653729..0edc8613 100644 --- a/edx_rest_framework_extensions/auth/jwt/tests/test_decoder.py +++ b/edx_rest_framework_extensions/auth/jwt/tests/test_decoder.py @@ -68,7 +68,8 @@ def test_valid_token_multiple_valid_issuers(self, jwt_issuer): def test_failure_invalid_issuer(self): """ - Verifies the function logs decode failures, and raises an InvalidTokenError if the token cannot be decoded + Verifies the function logs decode failures with invalid issuer, + and raises an InvalidTokenError if the token cannot be decoded """ # Create tokens using each invalid issuer and attempt to decode them against @@ -76,14 +77,16 @@ def test_failure_invalid_issuer(self): with mock.patch('edx_rest_framework_extensions.auth.jwt.decoder.logger') as patched_log: with self.assertRaises(jwt.InvalidTokenError): self.payload['iss'] = 'invalid-issuer' - signing_key = 'invalid-secret-key' + # signing key of None will use the default valid signing key + valid_signing_key = None # Generate a token using the invalid issuer data - token = generate_jwt_token(self.payload, signing_key) + token = generate_jwt_token(self.payload, valid_signing_key) # Attempt to decode the token against the entries in the valid issuers list, # which will fail with an InvalidTokenError jwt_decode_handler(token) - patched_log.exception.assert_any_call("Token verification failed.") + msg = "Token decode failed due to mismatched issuer [%s]" + patched_log.info.assert_any_call(msg, 'invalid-issuer') def test_failure_invalid_token(self): """ @@ -143,6 +146,25 @@ def test_upgrade(self): upgraded_payload['iat'], upgraded_payload['exp'] = jwt_payload['iat'], jwt_payload['exp'] self.assertDictEqual(jwt_decode_handler(token), upgraded_payload) + def test_failure_invalid_signature(self): + """ + Verifies the function logs decode failures with invalid signature, + and raises an InvalidTokenError if the token cannot be decoded + """ + # Create tokens using each invalid signature and attempt to decode them against + # the valid signature. + with mock.patch('edx_rest_framework_extensions.auth.jwt.decoder.logger') as patched_log: + with self.assertRaises(jwt.InvalidTokenError): + invalid_signing_key = 'invalid-secret-key' + + # Generate a token using the invalid signing key data + token = generate_jwt_token(self.payload, invalid_signing_key) + # Attempt to decode the token against invalid signature, + # which will fail with an InvalidTokenError + jwt_decode_handler(token) + + patched_log.exception.assert_any_call("Token verification failed.") + def _jwt_decode_handler_with_defaults(token): # pylint: disable=unused-argument """ diff --git a/edx_rest_framework_extensions/auth/jwt/tests/utils.py b/edx_rest_framework_extensions/auth/jwt/tests/utils.py index 22fe9a4f..724ccd3c 100644 --- a/edx_rest_framework_extensions/auth/jwt/tests/utils.py +++ b/edx_rest_framework_extensions/auth/jwt/tests/utils.py @@ -23,7 +23,7 @@ def generate_jwt_token(payload, signing_key=None): Generate a valid JWT token for authenticated requests. """ signing_key = signing_key or settings.JWT_AUTH['JWT_ISSUERS'][0]['SECRET_KEY'] - return jwt.encode(payload, signing_key).decode('utf-8') + return jwt.encode(payload, signing_key) def generate_latest_version_payload(user, scopes=None, filters=None, version=None, diff --git a/requirements/base.in b/requirements/base.in index d8ac7428..2683a993 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -7,6 +7,7 @@ django-waffle edx-django-utils>=3.8.0 # using new set_custom_attribute method edx-opaque-keys pyjwkest +pyjwt[crypto]>=2.1.0 # depends on newer jwt.decode and jwt.encode python-dateutil>=2.0 requests>=2.7.0 rest-condition>=1.0.3 diff --git a/requirements/base.txt b/requirements/base.txt index 7abb9229..7c46ebe8 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -56,9 +56,9 @@ pycryptodomex==3.10.1 # via pyjwkest pyjwkest==1.4.2 # via -r requirements/base.in -pyjwt[crypto]==1.7.1 +pyjwt[crypto]==2.1.0 # via - # -c requirements/constraints.txt + # -r requirements/base.in # drf-jwt pymongo==3.12.0 # via edx-opaque-keys diff --git a/requirements/common_constraints.txt b/requirements/common_constraints.txt index 8f399bf0..b8bd2d74 100644 --- a/requirements/common_constraints.txt +++ b/requirements/common_constraints.txt @@ -23,3 +23,6 @@ drf-jwt<1.19.1 # latest version requires PyJWT>=2.0.0 but drf-jwt requires PyJWT[crypto]<2.0.0,>=1.5.2 social-auth-core<4.0.3 + +# 5.0.0+ of social-auth-app-django requires social-auth-core>=4.1.0 +social-auth-app-django<5.0.0 diff --git a/requirements/constraints.txt b/requirements/constraints.txt index a210d805..3be8341d 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -10,6 +10,3 @@ # This file contains all common constraints for edx-repos -c common_constraints.txt - -# greater versions causing tests failures. -pyjwt[crypto]==1.7.1 diff --git a/requirements/dev.txt b/requirements/dev.txt index 5e509298..226f3067 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -229,9 +229,8 @@ pyjwkest==1.4.2 # via # -r requirements/base.txt # -r requirements/test.txt -pyjwt[crypto]==1.7.1 +pyjwt[crypto]==2.1.0 # via - # -c requirements/constraints.txt # -r requirements/base.txt # -r requirements/test.txt # drf-jwt diff --git a/requirements/test.txt b/requirements/test.txt index 257937db..2f7521c1 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -146,9 +146,8 @@ pycryptodomex==3.10.1 # pyjwkest pyjwkest==1.4.2 # via -r requirements/base.txt -pyjwt[crypto]==1.7.1 +pyjwt[crypto]==2.1.0 # via - # -c requirements/constraints.txt # -r requirements/base.txt # drf-jwt pylint==2.9.6