def process_request(self, request): """ Reconstitute the full JWT and add a new cookie on the request object. """ use_jwt_cookie_requested = request.META.get(USE_JWT_COOKIE_HEADER) header_payload_cookie = request.COOKIES.get(jwt_cookie_header_payload_name()) signature_cookie = request.COOKIES.get(jwt_cookie_signature_name()) if not use_jwt_cookie_requested: metric_value = 'not-requested' elif header_payload_cookie and signature_cookie: # Reconstitute JWT auth cookie if split cookies are available and jwt cookie # authentication was requested by the client. request.COOKIES[jwt_cookie_name()] = '{}{}{}'.format( header_payload_cookie, JWT_DELIMITER, signature_cookie, ) metric_value = 'success' elif header_payload_cookie or signature_cookie: # Log unexpected case of only finding one cookie. if not header_payload_cookie: log_message, metric_value = self._get_missing_cookie_message_and_metric( jwt_cookie_header_payload_name() ) if not signature_cookie: log_message, metric_value = self._get_missing_cookie_message_and_metric( jwt_cookie_signature_name() ) log.warning(log_message) else: metric_value = 'missing-both' monitoring.set_custom_metric('request_jwt_cookie', metric_value)
def process_request(self, request): """ Reconstitute the full JWT and add a new cookie on the request object. """ use_jwt_cookie_requested = request.META.get(USE_JWT_COOKIE_HEADER) header_payload_cookie = request.COOKIES.get(jwt_cookie_header_payload_name()) signature_cookie = request.COOKIES.get(jwt_cookie_signature_name()) if not use_jwt_cookie_requested: metric_value = 'not-requested' elif header_payload_cookie and signature_cookie: # Reconstitute JWT auth cookie if split cookies are available and jwt cookie # authentication was requested by the client. request.COOKIES[jwt_cookie_name()] = '{}{}{}'.format( header_payload_cookie, JWT_DELIMITER, signature_cookie, ) metric_value = 'success' elif header_payload_cookie or signature_cookie: # Log unexpected case of only finding one cookie. if not header_payload_cookie: log_message, metric_value = self._get_missing_cookie_message_and_metric( jwt_cookie_header_payload_name() ) if not signature_cookie: log_message, metric_value = self._get_missing_cookie_message_and_metric( jwt_cookie_signature_name() ) log.warning(log_message) else: metric_value = 'missing-both' monitoring.set_custom_metric('request_jwt_cookie', metric_value)
class TestJwtAuthCookieMiddleware(TestCase): def setUp(self): super(TestJwtAuthCookieMiddleware, self).setUp() self.request = RequestFactory().get('/') self.middleware = JwtAuthCookieMiddleware() @patch('edx_django_utils.monitoring.set_custom_metric') def test_do_not_use_jwt_cookies(self, mock_set_custom_metric): self.middleware.process_request(self.request) self.assertIsNone(self.request.COOKIES.get(jwt_cookie_name())) mock_set_custom_metric.assert_called_once_with('request_jwt_cookie', 'not-requested') @ddt.data( (jwt_cookie_header_payload_name(), jwt_cookie_signature_name()), (jwt_cookie_signature_name(), jwt_cookie_header_payload_name()), ) @ddt.unpack @patch('edx_rest_framework_extensions.auth.jwt.middleware.log') @patch('edx_django_utils.monitoring.set_custom_metric') def test_missing_cookies(self, set_cookie_name, missing_cookie_name, mock_set_custom_metric, mock_log): self.request.META[USE_JWT_COOKIE_HEADER] = 'true' self.request.COOKIES[set_cookie_name] = 'test' self.middleware.process_request(self.request) self.assertIsNone(self.request.COOKIES.get(jwt_cookie_name())) mock_log.warning.assert_called_once_with( '%s cookie is missing. JWT auth cookies will not be reconstituted.' % missing_cookie_name) mock_set_custom_metric.assert_called_once_with( 'request_jwt_cookie', 'missing-{}'.format(missing_cookie_name)) @patch('edx_django_utils.monitoring.set_custom_metric') def test_no_cookies(self, mock_set_custom_metric): self.request.META[USE_JWT_COOKIE_HEADER] = 'true' self.middleware.process_request(self.request) self.assertIsNone(self.request.COOKIES.get(jwt_cookie_name())) mock_set_custom_metric.assert_called_once_with('request_jwt_cookie', 'missing-both') @patch('edx_django_utils.monitoring.set_custom_metric') def test_success(self, mock_set_custom_metric): self.request.META[USE_JWT_COOKIE_HEADER] = 'true' self.request.COOKIES[ jwt_cookie_header_payload_name()] = 'header.payload' self.request.COOKIES[jwt_cookie_signature_name()] = 'signature' self.middleware.process_request(self.request) self.assertEqual(self.request.COOKIES[jwt_cookie_name()], 'header.payload.signature') mock_set_custom_metric.assert_called_once_with('request_jwt_cookie', 'success')
def process_view(self, request, view_func, view_args, view_kwargs): # pylint: disable=unused-argument """ Reconstitute the full JWT and add a new cookie on the request object. """ assert hasattr( request, 'session' ), "The Django authentication middleware requires session middleware to be installed. Edit your MIDDLEWARE setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'." # noqa E501 line too long use_jwt_cookie_requested = request.META.get(USE_JWT_COOKIE_HEADER) header_payload_cookie = request.COOKIES.get( jwt_cookie_header_payload_name()) signature_cookie = request.COOKIES.get(jwt_cookie_signature_name()) is_set_request_user_for_jwt_cookie_enabled = get_setting( ENABLE_SET_REQUEST_USER_FOR_JWT_COOKIE) if use_jwt_cookie_requested and is_set_request_user_for_jwt_cookie_enabled: # DRF does not set request.user until process_response. This makes it available in process_view. # For more info, see https://github.com/jpadilla/django-rest-framework-jwt/issues/45#issuecomment-74996698 request.user = SimpleLazyObject( lambda: _get_user_from_jwt(request, view_func)) if not use_jwt_cookie_requested: metric_value = 'not-requested' elif header_payload_cookie and signature_cookie: # Reconstitute JWT auth cookie if split cookies are available and jwt cookie # authentication was requested by the client. request.COOKIES[jwt_cookie_name()] = '{}{}{}'.format( header_payload_cookie, JWT_DELIMITER, signature_cookie, ) metric_value = 'success' elif header_payload_cookie or signature_cookie: # Log unexpected case of only finding one cookie. if not header_payload_cookie: log_message, metric_value = self._get_missing_cookie_message_and_metric( jwt_cookie_header_payload_name()) if not signature_cookie: log_message, metric_value = self._get_missing_cookie_message_and_metric( jwt_cookie_signature_name()) log.warning(log_message) else: metric_value = 'missing-both' log.warning( 'Both JWT auth cookies missing. JWT auth cookies will not be reconstituted.' ) monitoring.set_custom_metric('request_jwt_cookie', metric_value)
def test_success(self, mock_set_custom_metric): self.request.META[USE_JWT_COOKIE_HEADER] = 'true' self.request.COOKIES[jwt_cookie_header_payload_name()] = 'header.payload' self.request.COOKIES[jwt_cookie_signature_name()] = 'signature' self.middleware.process_request(self.request) self.assertEqual(self.request.COOKIES[jwt_cookie_name()], 'header.payload.signature') mock_set_custom_metric.assert_called_once_with('request_jwt_cookie', 'success')
def test_success(self, mock_set_custom_attribute): self.request.META[USE_JWT_COOKIE_HEADER] = 'true' self.request.COOKIES[jwt_cookie_header_payload_name()] = 'header.payload' self.request.COOKIES[jwt_cookie_signature_name()] = 'signature' self.middleware.process_view(self.request, None, None, None) self.assertEqual(self.request.COOKIES[jwt_cookie_name()], 'header.payload.signature') mock_set_custom_attribute.assert_called_once_with('request_jwt_cookie', 'success')
def _set_jwt_cookies(response, cookie_settings, jwt_header_and_payload, jwt_signature): """ Sets the given jwt_header_and_payload, jwt_signature, and refresh token in 3 different cookies. The latter 2 cookies are set as httponly. """ cookie_settings['httponly'] = None response.set_cookie(jwt_cookies.jwt_cookie_header_payload_name(), jwt_header_and_payload, **cookie_settings) cookie_settings['httponly'] = True response.set_cookie(jwt_cookies.jwt_cookie_signature_name(), jwt_signature, **cookie_settings)
def _set_jwt_cookies(response, cookie_settings, jwt_header_and_payload, jwt_signature): """ Sets the given jwt_header_and_payload, jwt_signature, and refresh token in 3 different cookies. The latter 2 cookies are set as httponly. """ cookie_settings['httponly'] = None response.set_cookie( jwt_cookies.jwt_cookie_header_payload_name(), jwt_header_and_payload, **cookie_settings ) cookie_settings['httponly'] = True response.set_cookie( jwt_cookies.jwt_cookie_signature_name(), jwt_signature, **cookie_settings )
from oauth2_provider.models import Application from openedx.core.djangoapps.oauth_dispatch.adapters import DOTAdapter from openedx.core.djangoapps.oauth_dispatch.api import create_dot_access_token from openedx.core.djangoapps.oauth_dispatch.jwt import create_jwt_from_token from openedx.core.djangoapps.user_api.accounts.utils import retrieve_last_sitewide_block_completed from openedx.core.djangoapps.user_authn.exceptions import AuthFailedError from student.models import CourseEnrollment log = logging.getLogger(__name__) CREATE_LOGON_COOKIE = Signal(providing_args=['user', 'response']) JWT_COOKIE_NAMES = ( # Header and payload sections of a JSON Web Token containing user # information and used as an access token. jwt_cookies.jwt_cookie_header_payload_name(), # Signature section of a JSON Web Token. jwt_cookies.jwt_cookie_signature_name(), ) # TODO (ARCH-245): Remove the following deprecated cookies. DEPRECATED_LOGGED_IN_COOKIE_NAMES = ( # Set to 'true' if the user is logged in. settings.EDXMKTG_LOGGED_IN_COOKIE_NAME, # JSON-encoded dictionary with user information. settings.EDXMKTG_USER_INFO_COOKIE_NAME, ) ALL_LOGGED_IN_COOKIE_NAMES = JWT_COOKIE_NAMES + DEPRECATED_LOGGED_IN_COOKIE_NAMES
from openedx.core.djangoapps.oauth_dispatch.jwt import create_jwt_from_token from openedx.core.djangoapps.user_api.accounts.utils import retrieve_last_sitewide_block_completed from openedx.core.djangoapps.user_authn.exceptions import AuthFailedError from student.models import CourseEnrollment log = logging.getLogger(__name__) CREATE_LOGON_COOKIE = Signal(providing_args=['user', 'response']) JWT_COOKIE_NAMES = ( # Header and payload sections of a JSON Web Token containing user # information and used as an access token. jwt_cookies.jwt_cookie_header_payload_name(), # Signature section of a JSON Web Token. jwt_cookies.jwt_cookie_signature_name(), # Refresh token, which can be used to get a new JSON Web Token. jwt_cookies.jwt_refresh_cookie_name(), ) # TODO (ARCH-245): Remove the following deprecated cookies. DEPRECATED_LOGGED_IN_COOKIE_NAMES = ( # Set to 'true' if the user is logged in. settings.EDXMKTG_LOGGED_IN_COOKIE_NAME, # JSON-encoded dictionary with user information. settings.EDXMKTG_USER_INFO_COOKIE_NAME,
def _get_test_cookie(is_cookie_valid=True): header_payload_value = 'header.payload' if is_cookie_valid else 'header.payload.invalid' return SimpleCookie({ jwt_cookie_header_payload_name(): header_payload_value, jwt_cookie_signature_name(): 'signature', })
class TestJwtAuthCookieMiddleware(TestCase): def setUp(self): super(TestJwtAuthCookieMiddleware, self).setUp() self.request = RequestFactory().get('/') self.request.session = 'mock session' self.middleware = JwtAuthCookieMiddleware() @patch('edx_django_utils.monitoring.set_custom_metric') def test_do_not_use_jwt_cookies(self, mock_set_custom_metric): self.middleware.process_view(self.request, None, None, None) self.assertIsNone(self.request.COOKIES.get(jwt_cookie_name())) mock_set_custom_metric.assert_called_once_with('request_jwt_cookie', 'not-requested') @ddt.data( (jwt_cookie_header_payload_name(), jwt_cookie_signature_name()), (jwt_cookie_signature_name(), jwt_cookie_header_payload_name()), ) @ddt.unpack @patch('edx_rest_framework_extensions.auth.jwt.middleware.log') @patch('edx_django_utils.monitoring.set_custom_metric') def test_missing_cookies(self, set_cookie_name, missing_cookie_name, mock_set_custom_metric, mock_log): self.request.META[USE_JWT_COOKIE_HEADER] = 'true' self.request.COOKIES[set_cookie_name] = 'test' self.middleware.process_view(self.request, None, None, None) self.assertIsNone(self.request.COOKIES.get(jwt_cookie_name())) mock_log.warning.assert_called_once_with( '%s cookie is missing. JWT auth cookies will not be reconstituted.' % missing_cookie_name) mock_set_custom_metric.assert_called_once_with( 'request_jwt_cookie', 'missing-{}'.format(missing_cookie_name)) @patch('edx_django_utils.monitoring.set_custom_metric') def test_no_cookies(self, mock_set_custom_metric): self.request.META[USE_JWT_COOKIE_HEADER] = 'true' self.middleware.process_view(self.request, None, None, None) self.assertIsNone(self.request.COOKIES.get(jwt_cookie_name())) mock_set_custom_metric.assert_called_once_with('request_jwt_cookie', 'missing-both') @patch('edx_django_utils.monitoring.set_custom_metric') def test_success(self, mock_set_custom_metric): self.request.META[USE_JWT_COOKIE_HEADER] = 'true' self.request.COOKIES[ jwt_cookie_header_payload_name()] = 'header.payload' self.request.COOKIES[jwt_cookie_signature_name()] = 'signature' self.middleware.process_view(self.request, None, None, None) self.assertEqual(self.request.COOKIES[jwt_cookie_name()], 'header.payload.signature') mock_set_custom_metric.assert_called_once_with('request_jwt_cookie', 'success') _LOG_WARN_AUTHENTICATION_FAILED = 0 _LOG_WARN_MISSING_JWT_AUTHENTICATION_CLASS = 1 @patch('edx_rest_framework_extensions.auth.jwt.middleware.log') @ddt.data( ('/nopermissionsrequired/', True, True, True, None), ('/nopermissionsrequired/', True, False, False, None), ('/nopermissionsrequired/', False, False, True, _LOG_WARN_AUTHENTICATION_FAILED), ('/nopermissionsrequired/', False, False, False, None), ('/unauthenticated/', True, False, True, _LOG_WARN_MISSING_JWT_AUTHENTICATION_CLASS), ('/unauthenticated/', True, False, False, None), ) @ddt.unpack def test_set_request_user_with_use_jwt_cookie( self, url, is_cookie_valid, is_request_user_set, is_toggle_enabled, log_warning, mock_log, ): header = {USE_JWT_COOKIE_HEADER: 'true'} self.client.cookies = _get_test_cookie(is_cookie_valid=is_cookie_valid) check_user_middleware_assertion_class = ( 'CheckRequestUserForJwtAuthMiddleware' if is_request_user_set else 'CheckRequestUserAnonymousForJwtAuthMiddleware') with override_settings( ROOT_URLCONF= 'edx_rest_framework_extensions.auth.jwt.tests.test_middleware', MIDDLEWARE= ( 'django.contrib.sessions.middleware.SessionMiddleware', 'edx_rest_framework_extensions.auth.jwt.middleware.JwtAuthCookieMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'edx_rest_framework_extensions.auth.jwt.tests.test_middleware.{}' .format(check_user_middleware_assertion_class), ), EDX_DRF_EXTENSIONS={ ENABLE_SET_REQUEST_USER_FOR_JWT_COOKIE: is_toggle_enabled, }): response = self.client.get(url, **header) self.assertEqual(200, response.status_code) if log_warning == self._LOG_WARN_AUTHENTICATION_FAILED: mock_log.warning.assert_called_once_with( 'Jwt Authentication failed and request.user could not be set.' ) elif log_warning == self._LOG_WARN_MISSING_JWT_AUTHENTICATION_CLASS: mock_log.warning.assert_called_once_with( 'Jwt Authentication expected, but view %s is not using a JwtAuthentication class.', ANY) else: mock_log.warn.assert_not_called()