From 531ed418225bdae9ed7d345d52e0d8927f8aadf9 Mon Sep 17 00:00:00 2001 From: Clinton Blackburn Date: Tue, 15 Nov 2016 23:36:31 -0500 Subject: [PATCH] Updated the Credit API to support JWT authentication --- .../djangoapps/credit/tests/test_views.py | 37 +++++++++++++------ openedx/core/djangoapps/credit/views.py | 34 ++++++++++------- 2 files changed, 46 insertions(+), 25 deletions(-) diff --git a/openedx/core/djangoapps/credit/tests/test_views.py b/openedx/core/djangoapps/credit/tests/test_views.py index 5bb6f7d75d..a438b4b20e 100644 --- a/openedx/core/djangoapps/credit/tests/test_views.py +++ b/openedx/core/djangoapps/credit/tests/test_views.py @@ -5,30 +5,31 @@ Tests for credit app views. # pylint: disable=no-member from __future__ import unicode_literals + import datetime import json -from nose.plugins.attrib import attr import unittest import ddt +import pytz from django.conf import settings from django.core.urlresolvers import reverse from django.test import TestCase, Client from django.test.utils import override_settings from edx_oauth2_provider.tests.factories import AccessTokenFactory, ClientFactory +from nose.plugins.attrib import attr from opaque_keys.edx.keys import CourseKey -import pytz -from openedx.core.djangoapps.credit.signature import signature -from openedx.core.djangoapps.credit.serializers import CreditProviderSerializer, CreditEligibilitySerializer -from openedx.core.djangoapps.credit.tests.factories import ( - CreditProviderFactory, - CreditEligibilityFactory, - CreditCourseFactory, CreditRequestFactory) -from student.tests.factories import UserFactory, AdminFactory from openedx.core.djangoapps.credit.models import ( - CreditCourse, - CreditProvider, CreditRequest, CreditRequirement, CreditRequirementStatus) + CreditCourse, CreditProvider, CreditRequest, CreditRequirement, CreditRequirementStatus, +) +from openedx.core.djangoapps.credit.serializers import CreditProviderSerializer, CreditEligibilitySerializer +from openedx.core.djangoapps.credit.signature import signature +from openedx.core.djangoapps.credit.tests.factories import ( + CreditProviderFactory, CreditEligibilityFactory, CreditCourseFactory, CreditRequestFactory, +) +from openedx.core.lib.token_utils import JwtBuilder +from student.tests.factories import UserFactory, AdminFactory from util.date_utils import to_timestamp JSON = 'application/json' @@ -87,6 +88,18 @@ class AuthMixin(object): response = self.client.get(self.path) self.assertEqual(response.status_code, 200) + def test_jwt_auth(self): + """ verify the endpoints JWT authentication. """ + scopes = ['email', 'profile'] + expires_in = settings.OAUTH_ID_TOKEN_EXPIRATION + token = JwtBuilder(self.user).build_token(scopes, expires_in) + headers = { + 'HTTP_AUTHORIZATION': 'JWT ' + token + } + self.client.logout() + response = self.client.get(self.path, **headers) + self.assertEqual(response.status_code, 200) + @ddt.ddt class ReadOnlyMixin(object): @@ -101,7 +114,7 @@ class ReadOnlyMixin(object): @attr(shard=2) @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') -class CreditCourseViewSetTests(UserMixin, TestCase): +class CreditCourseViewSetTests(AuthMixin, UserMixin, TestCase): """ Tests for the CreditCourse endpoints. GET/POST /api/v1/credit/creditcourse/ diff --git a/openedx/core/djangoapps/credit/views.py b/openedx/core/djangoapps/credit/views.py index 1be3f90cfe..c376a9a0c5 100644 --- a/openedx/core/djangoapps/credit/views.py +++ b/openedx/core/djangoapps/credit/views.py @@ -2,15 +2,17 @@ Views for the credit Django app. """ from __future__ import unicode_literals -import logging -import datetime +import datetime +import logging + +import pytz from django.conf import settings from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt +from edx_rest_framework_extensions.authentication import JwtAuthentication from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey -import pytz from rest_framework import viewsets, mixins, permissions, views, generics from rest_framework.authentication import SessionAuthentication from rest_framework.exceptions import ValidationError @@ -18,16 +20,22 @@ from rest_framework.response import Response from rest_framework_oauth.authentication import OAuth2Authentication from openedx.core.djangoapps.credit.api import create_credit_request -from openedx.core.djangoapps.credit.exceptions import (UserNotEligibleException, InvalidCourseKey, CreditApiBadRequest, - InvalidCreditRequest) -from openedx.core.djangoapps.credit.models import (CreditCourse, CreditProvider, CREDIT_PROVIDER_ID_REGEX, - CreditEligibility, CreditRequest) -from openedx.core.djangoapps.credit.serializers import (CreditCourseSerializer, CreditProviderSerializer, - CreditEligibilitySerializer, CreditProviderCallbackSerializer) +from openedx.core.djangoapps.credit.exceptions import ( + UserNotEligibleException, InvalidCourseKey, CreditApiBadRequest, InvalidCreditRequest, +) +from openedx.core.djangoapps.credit.models import ( + CreditCourse, CreditProvider, CREDIT_PROVIDER_ID_REGEX, + CreditEligibility, CreditRequest, +) +from openedx.core.djangoapps.credit.serializers import ( + CreditCourseSerializer, CreditProviderSerializer, + CreditEligibilitySerializer, CreditProviderCallbackSerializer, +) from openedx.core.lib.api.mixins import PutAsCreateMixin from openedx.core.lib.api.permissions import IsStaffOrOwner log = logging.getLogger(__name__) +AUTHENTICATION_CLASSES = (JwtAuthentication, OAuth2Authentication, SessionAuthentication,) class CreditProviderViewSet(viewsets.ReadOnlyModelViewSet): @@ -35,7 +43,7 @@ class CreditProviderViewSet(viewsets.ReadOnlyModelViewSet): lookup_field = 'provider_id' lookup_value_regex = CREDIT_PROVIDER_ID_REGEX - authentication_classes = (OAuth2Authentication, SessionAuthentication,) + authentication_classes = AUTHENTICATION_CLASSES pagination_class = None permission_classes = (permissions.IsAuthenticated,) queryset = CreditProvider.objects.all() @@ -57,7 +65,7 @@ class CreditProviderViewSet(viewsets.ReadOnlyModelViewSet): class CreditProviderRequestCreateView(views.APIView): """ Creates a credit request for the given user and course, if the user is eligible for credit.""" - authentication_classes = (OAuth2Authentication, SessionAuthentication,) + authentication_classes = AUTHENTICATION_CLASSES permission_classes = (permissions.IsAuthenticated, IsStaffOrOwner,) def post(self, request, provider_id): @@ -127,7 +135,7 @@ class CreditProviderCallbackView(views.APIView): class CreditEligibilityView(generics.ListAPIView): """ Returns eligibility for a user-course combination. """ - authentication_classes = (OAuth2Authentication, SessionAuthentication,) + authentication_classes = AUTHENTICATION_CLASSES pagination_class = None permission_classes = (permissions.IsAuthenticated, IsStaffOrOwner) serializer_class = CreditEligibilitySerializer @@ -161,7 +169,7 @@ class CreditCourseViewSet(PutAsCreateMixin, mixins.UpdateModelMixin, viewsets.Re lookup_value_regex = settings.COURSE_KEY_REGEX queryset = CreditCourse.objects.all() serializer_class = CreditCourseSerializer - authentication_classes = (OAuth2Authentication, SessionAuthentication,) + authentication_classes = AUTHENTICATION_CLASSES permission_classes = (permissions.IsAuthenticated, permissions.IsAdminUser) # In Django Rest Framework v3, there is a default pagination