Skip to content

Commit

Permalink
feat!: Major upgrade to pyjwt 2.1.0 (#167)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
awais786 and edx-requirements-bot authored Aug 9, 2021
1 parent ff1f909 commit 9e01467
Show file tree
Hide file tree
Showing 11 changed files with 45 additions and 16 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
--------------------

Expand Down
2 changes: 1 addition & 1 deletion edx_rest_framework_extensions/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
""" edx Django REST Framework extensions. """

__version__ = '6.6.0' # pragma: no cover
__version__ = '7.0.0' # pragma: no cover
1 change: 0 additions & 1 deletion edx_rest_framework_extensions/auth/jwt/decoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand Down
30 changes: 26 additions & 4 deletions edx_rest_framework_extensions/auth/jwt/tests/test_decoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,22 +68,25 @@ 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
# the valid issuers list, which won't work
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):
"""
Expand Down Expand Up @@ -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
"""
Expand Down
2 changes: 1 addition & 1 deletion edx_rest_framework_extensions/auth/jwt/tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions requirements/base.in
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions requirements/common_constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 0 additions & 3 deletions requirements/constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 1 addition & 2 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions requirements/test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 9e01467

Please sign in to comment.