Commit 8f82192a by Clinton Blackburn

Merge pull request #12162 from edx/clintonb/jwt-updates

JWT authentication updates
parents 67c055e2 5a7bc019
...@@ -2118,14 +2118,16 @@ SOCIAL_MEDIA_FOOTER_NAMES = [ ...@@ -2118,14 +2118,16 @@ SOCIAL_MEDIA_FOOTER_NAMES = [
# JWT Settings # JWT Settings
JWT_AUTH = { JWT_AUTH = {
'JWT_SECRET_KEY': None, # TODO Set JWT_SECRET_KEY to a secure value. By default, SECRET_KEY will be used.
# 'JWT_SECRET_KEY': '',
'JWT_ALGORITHM': 'HS256', 'JWT_ALGORITHM': 'HS256',
'JWT_VERIFY_EXPIRATION': True, 'JWT_VERIFY_EXPIRATION': True,
'JWT_ISSUER': None, # TODO Set JWT_ISSUER and JWT_AUDIENCE to values specific to your service/organization.
'JWT_PAYLOAD_GET_USERNAME_HANDLER': lambda d: d.get('username'), 'JWT_ISSUER': 'change-me',
'JWT_AUDIENCE': None, 'JWT_AUDIENCE': None,
'JWT_PAYLOAD_GET_USERNAME_HANDLER': lambda d: d.get('username'),
'JWT_LEEWAY': 1, 'JWT_LEEWAY': 1,
'JWT_DECODE_HANDLER': 'openedx.core.lib.api.jwt_decode_handler.decode', 'JWT_DECODE_HANDLER': 'edx_rest_framework_extensions.utils.jwt_decode_handler',
} }
# The footer URLs dictionary maps social footer names # The footer URLs dictionary maps social footer names
...@@ -2225,14 +2227,6 @@ if FEATURES.get('CLASS_DASHBOARD'): ...@@ -2225,14 +2227,6 @@ if FEATURES.get('CLASS_DASHBOARD'):
ENABLE_CREDIT_ELIGIBILITY = True ENABLE_CREDIT_ELIGIBILITY = True
FEATURES['ENABLE_CREDIT_ELIGIBILITY'] = ENABLE_CREDIT_ELIGIBILITY FEATURES['ENABLE_CREDIT_ELIGIBILITY'] = ENABLE_CREDIT_ELIGIBILITY
################ Enable JWT auth ####################
# When this feature flag is set to False, API endpoints using
# JSONWebTokenAuthentication will reject requests using JWT to authenticate,
# even if those tokens are valid. Set this to True only if you need those
# endpoints, and have configured settings 'JWT_AUTH' to override its default
# values with secure values.
FEATURES['ENABLE_JWT_AUTH'] = False
######################## CAS authentication ########################### ######################## CAS authentication ###########################
if FEATURES.get('AUTH_USE_CAS'): if FEATURES.get('AUTH_USE_CAS'):
......
"""
Custom JWT decoding function for django_rest_framework jwt package.
Adds logging to facilitate debugging of InvalidTokenErrors. Also
requires "exp" and "iat" claims to be present - the base package
doesn't expose settings to enforce this.
"""
import logging
from django.conf import settings
import jwt
from rest_framework import exceptions
from rest_framework_jwt.settings import api_settings
log = logging.getLogger(__name__)
def decode(token):
"""
Ensure InvalidTokenErrors are logged for diagnostic purposes, before
failing authentication.
"""
if not settings.FEATURES.get('ENABLE_JWT_AUTH', False):
msg = 'JWT auth not supported.'
log.error(msg)
raise exceptions.AuthenticationFailed(msg)
options = {
'verify_exp': api_settings.JWT_VERIFY_EXPIRATION,
'require_exp': True,
'require_iat': True,
}
try:
return jwt.decode(
token,
api_settings.JWT_SECRET_KEY,
api_settings.JWT_VERIFY,
options=options,
leeway=api_settings.JWT_LEEWAY,
audience=api_settings.JWT_AUDIENCE,
issuer=api_settings.JWT_ISSUER,
algorithms=[api_settings.JWT_ALGORITHM]
)
except jwt.InvalidTokenError as exc:
exc_type = u'{}.{}'.format(exc.__class__.__module__, exc.__class__.__name__)
log.exception("raised_invalid_token: exc_type=%r, exc_detail=%r", exc_type, exc.message)
raise
...@@ -4,40 +4,33 @@ Tests for OAuth2. This module is copied from django-rest-framework-oauth ...@@ -4,40 +4,33 @@ Tests for OAuth2. This module is copied from django-rest-framework-oauth
""" """
from __future__ import unicode_literals from __future__ import unicode_literals
from collections import namedtuple
from datetime import datetime, timedelta
import itertools import itertools
import json import json
from collections import namedtuple
import ddt import ddt
from django.conf import settings from datetime import datetime, timedelta
from django.conf.urls import patterns, url, include from django.conf.urls import patterns, url, include
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.http import HttpResponse from django.http import HttpResponse
from django.test import TestCase from django.test import TestCase
from django.utils import unittest from django.utils import unittest
from django.utils.http import urlencode from django.utils.http import urlencode
from mock import patch
from nose.plugins.attrib import attr from nose.plugins.attrib import attr
from oauth2_provider import models as dot_models from oauth2_provider import models as dot_models
from rest_framework import exceptions from provider import constants, scope
from rest_framework import status from rest_framework import status
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework_oauth import permissions
from rest_framework_oauth.compat import oauth2_provider, oauth2_provider_scope
from rest_framework.test import APIRequestFactory, APIClient from rest_framework.test import APIRequestFactory, APIClient
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework_jwt.settings import api_settings from rest_framework_oauth import permissions
from rest_framework_oauth.compat import oauth2_provider, oauth2_provider_scope
from lms.djangoapps.oauth_dispatch import adapters from lms.djangoapps.oauth_dispatch import adapters
from openedx.core.lib.api import authentication from openedx.core.lib.api import authentication
from openedx.core.lib.api.tests.mixins import JwtMixin
from provider import constants, scope
from student.tests.factories import UserFactory
factory = APIRequestFactory() # pylint: disable=invalid-name factory = APIRequestFactory() # pylint: disable=invalid-name
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER # pylint: disable=invalid-name
class MockView(APIView): # pylint: disable=missing-docstring class MockView(APIView): # pylint: disable=missing-docstring
...@@ -311,32 +304,3 @@ class OAuth2Tests(TestCase): ...@@ -311,32 +304,3 @@ class OAuth2Tests(TestCase):
self.assertEqual(response.status_code, scope_statuses.read_status) self.assertEqual(response.status_code, scope_statuses.read_status)
response = self.post_with_bearer_token('/oauth2-with-scope-test/', token=self.access_token.token) response = self.post_with_bearer_token('/oauth2-with-scope-test/', token=self.access_token.token)
self.assertEqual(response.status_code, scope_statuses.write_status) self.assertEqual(response.status_code, scope_statuses.write_status)
@attr('shard_2')
@ddt.ddt
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
class TestJWTAuthToggle(JwtMixin, TestCase):
""" Test JWT authentication toggling with feature flag 'ENABLE_JWT_AUTH'."""
USERNAME = 'test-username'
def setUp(self):
self.user = UserFactory.create(username=self.USERNAME)
self.jwt_token = self.generate_id_token(user=self.user)
super(TestJWTAuthToggle, self).setUp()
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_JWT_AUTH': True})
def test_enabled_jwt_auth(self):
""" Ensure that the JWT auth works fine when its feature flag
'ENABLE_JWT_AUTH' is set.
"""
jwt_decode_handler(self.jwt_token)
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_JWT_AUTH': False})
def test_disabled_jwt_auth(self):
""" Ensure that the JWT auth raises exception when its feature flag
'ENABLE_JWT_AUTH' is not set.
"""
with self.assertRaises(exceptions.AuthenticationFailed):
jwt_decode_handler(self.jwt_token)
...@@ -34,9 +34,10 @@ django-method-override==0.1.0 ...@@ -34,9 +34,10 @@ django-method-override==0.1.0
#djangorestframework>=3.1,<3.2 #djangorestframework>=3.1,<3.2
git+https://github.com/edx/django-rest-framework.git@3c72cb5ee5baebc4328947371195eae2077197b0#egg=djangorestframework==3.2.3 git+https://github.com/edx/django-rest-framework.git@3c72cb5ee5baebc4328947371195eae2077197b0#egg=djangorestframework==3.2.3
django==1.8.12 django==1.8.12
djangorestframework-jwt==1.7.2 djangorestframework-jwt==1.8.0
djangorestframework-oauth==1.1.0 djangorestframework-oauth==1.1.0
edx-ccx-keys==0.1.2 edx-ccx-keys==0.1.2
edx-drf-extensions==0.5.0
edx-lint==0.4.3 edx-lint==0.4.3
edx-management-commands==0.1.1 edx-management-commands==0.1.1
edx-django-oauth2-provider==1.0.3 edx-django-oauth2-provider==1.0.3
......
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