Commit 040578b3 by Zubair Afzal

Merge pull request #11599 from edx/zub/feature-flag-jwt-auth

enable jwt auth with feature flag
parents 41febaa8 18282626
...@@ -2143,6 +2143,14 @@ if FEATURES.get('CLASS_DASHBOARD'): ...@@ -2143,6 +2143,14 @@ 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'):
......
...@@ -7,7 +7,9 @@ doesn't expose settings to enforce this. ...@@ -7,7 +7,9 @@ doesn't expose settings to enforce this.
""" """
import logging import logging
from django.conf import settings
import jwt import jwt
from rest_framework import exceptions
from rest_framework_jwt.settings import api_settings from rest_framework_jwt.settings import api_settings
...@@ -19,6 +21,10 @@ def decode(token): ...@@ -19,6 +21,10 @@ def decode(token):
Ensure InvalidTokenErrors are logged for diagnostic purposes, before Ensure InvalidTokenErrors are logged for diagnostic purposes, before
failing authentication. failing authentication.
""" """
if not settings.FEATURES.get('ENABLE_JWT_AUTH', False):
msg = 'JWT auth not supported.'
log.error(msg)
raise exceptions.AuthenticationFailed(msg)
options = { options = {
'verify_exp': api_settings.JWT_VERIFY_EXPIRATION, 'verify_exp': api_settings.JWT_VERIFY_EXPIRATION,
......
"""
Mixins for JWT auth tests.
"""
from time import time
from django.conf import settings
import jwt
JWT_AUTH = 'JWT_AUTH'
class JwtMixin(object):
""" Mixin with JWT-related helper functions. """
JWT_SECRET_KEY = getattr(settings, JWT_AUTH)['JWT_SECRET_KEY'] if hasattr(settings, JWT_AUTH) else ''
JWT_ISSUER = getattr(settings, JWT_AUTH)['JWT_ISSUER'] if hasattr(settings, JWT_AUTH) else ''
JWT_AUDIENCE = getattr(settings, JWT_AUTH)['JWT_AUDIENCE'] if hasattr(settings, JWT_AUTH) else ''
def generate_token(self, payload, secret=None):
""" Generate a JWT token with the provided payload."""
secret = secret or self.JWT_SECRET_KEY
token = jwt.encode(payload, secret)
return token
def generate_id_token(self, user, ttl=1, **overrides):
""" Generate a JWT id_token that looks like the ones currently
returned by the edx oidc provider.
"""
payload = self.default_payload(user=user, ttl=ttl)
payload.update(overrides)
return self.generate_token(payload)
def default_payload(self, user, ttl=1):
""" Generate a bare payload, in case tests need to manipulate
it directly before encoding.
"""
now = int(time())
return {
"iss": self.JWT_ISSUER,
"aud": self.JWT_AUDIENCE,
"nonce": "dummy-nonce",
"exp": now + ttl,
"iat": now,
"username": user.username,
"email": user.email,
}
...@@ -10,25 +10,31 @@ import itertools ...@@ -10,25 +10,31 @@ import itertools
import json import json
import ddt import ddt
from django.conf import settings
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 rest_framework import exceptions
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 import permissions
from rest_framework_oauth.compat import oauth2_provider, oauth2_provider_scope 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 provider import scope, constants
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
...@@ -284,3 +290,31 @@ class OAuth2Tests(TestCase): ...@@ -284,3 +290,31 @@ 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)
@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)
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