Commit e7aba82b by Clinton Blackburn

Merge pull request #740 from edx/clintonb/multiple-signers

Updated JWT decode handler to support multiple signing keys
parents 5f0f8f07 b3a613b9
...@@ -29,22 +29,30 @@ def jwt_decode_handler(token): ...@@ -29,22 +29,30 @@ def jwt_decode_handler(token):
# JWT_ISSUERS is not one of DRF-JWT's default settings, and cannot be accessed # JWT_ISSUERS is not one of DRF-JWT's default settings, and cannot be accessed
# using the `api_settings` object without overriding DRF-JWT's defaults. # using the `api_settings` object without overriding DRF-JWT's defaults.
issuers = settings.JWT_AUTH['JWT_ISSUERS'] issuers = settings.JWT_AUTH['JWT_ISSUERS']
for issuer in issuers: secret_keys = settings.JWT_AUTH['JWT_SECRET_KEYS'] or (api_settings.JWT_SECRET_KEY,)
try:
return jwt.decode( # TODO (CCB): The usage of multiple issuers complicates matters. We should only have one issuer.
token, # Update ecommerce-worker to properly use client credentials, and remove the internal loop. (ECOM-4477)
api_settings.JWT_SECRET_KEY, for secret_key in secret_keys:
api_settings.JWT_VERIFY, for issuer in issuers:
options=options, try:
leeway=api_settings.JWT_LEEWAY, return jwt.decode(
audience=api_settings.JWT_AUDIENCE, token,
issuer=issuer, secret_key,
algorithms=[api_settings.JWT_ALGORITHM] api_settings.JWT_VERIFY,
) options=options,
except jwt.InvalidIssuerError: leeway=api_settings.JWT_LEEWAY,
pass audience=api_settings.JWT_AUDIENCE,
except jwt.InvalidTokenError: issuer=issuer,
logger.exception('JWT decode failed!') algorithms=[api_settings.JWT_ALGORITHM]
raise )
except jwt.InvalidIssuerError:
raise jwt.InvalidIssuerError # Ignore these errors since we have multiple issuers
pass
except jwt.DecodeError:
# Ignore these errors since we have multiple signing keys
pass
except jwt.InvalidTokenError:
logger.exception('JWT decode failed!')
raise jwt.InvalidTokenError('All combinations of JWT issuers and secret keys failed to validate the token.')
""" Tests for handler functions. """
from time import time
from django.conf import settings
from django.test import TestCase
import jwt
import mock
from oscar.test.factories import UserFactory
from ecommerce.extensions.api.handlers import jwt_decode_handler
ISSUERS = ('test-issuer', 'another-issuer',)
SIGNING_KEYS = ('insecure-secret-key', 'secret', 'another-secret',)
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_SECRET_KEY']
return jwt.encode(payload, signing_key).decode('utf-8')
def generate_jwt_payload(user):
"""Generate a valid JWT payload given a user."""
now = int(time())
ttl = 5
return {
'iss': settings.JWT_AUTH['JWT_ISSUERS'][0],
'username': user.username,
'email': user.email,
'iat': now,
'exp': now + ttl
}
class JWTDecodeHandlerTests(TestCase):
""" Tests for the `jwt_decode_handler` utility function. """
def setUp(self):
super(JWTDecodeHandlerTests, self).setUp()
self.user = UserFactory()
self.payload = generate_jwt_payload(self.user)
self.jwt = generate_jwt_token(self.payload)
def test_decode_success(self):
self.assertEqual(jwt_decode_handler(self.jwt), self.payload)
def test_decode_success_with_multiple_issuers(self):
settings.JWT_AUTH['JWT_ISSUERS'] = ISSUERS
for issuer in ISSUERS:
self.payload['iss'] = issuer
token = generate_jwt_token(self.payload)
self.assertEqual(jwt_decode_handler(token), self.payload)
def test_decode_success_with_multiple_signing_keys(self):
settings.JWT_AUTH['JWT_SECRET_KEYS'] = SIGNING_KEYS
for signing_key in SIGNING_KEYS:
token = generate_jwt_token(self.payload, signing_key)
self.assertEqual(jwt_decode_handler(token), self.payload)
def test_decode_error(self):
# Update the payload to ensure a validation error
self.payload['exp'] = 0
token = generate_jwt_token(self.payload)
with mock.patch('ecommerce.extensions.api.handlers.logger') as patched_log:
with self.assertRaises(jwt.InvalidTokenError):
jwt_decode_handler(token)
patched_log.exception.assert_called_once_with('JWT decode failed!')
...@@ -326,13 +326,13 @@ JWT_AUTH = { ...@@ -326,13 +326,13 @@ JWT_AUTH = {
'JWT_SECRET_KEY': None, 'JWT_SECRET_KEY': None,
'JWT_ALGORITHM': 'HS256', 'JWT_ALGORITHM': 'HS256',
'JWT_VERIFY_EXPIRATION': True, 'JWT_VERIFY_EXPIRATION': True,
# NOTE (CCB): This is temporarily set to False until we decide what values
# are acceptable.
'JWT_VERIFY_AUDIENCE': False,
'JWT_LEEWAY': 1, 'JWT_LEEWAY': 1,
'JWT_DECODE_HANDLER': 'ecommerce.extensions.api.handlers.jwt_decode_handler', 'JWT_DECODE_HANDLER': 'ecommerce.extensions.api.handlers.jwt_decode_handler',
# This setting is not one of DRF-JWT's defaults. # These settings are NOT part of DRF-JWT's defaults.
'JWT_ISSUERS': (), 'JWT_ISSUERS': (),
# NOTE (CCB): This is temporarily set to False until we decide what values are acceptable.
'JWT_VERIFY_AUDIENCE': False,
'JWT_SECRET_KEYS': (),
} }
# Service user for worker processes. # Service user for worker processes.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment