Skip to content

Commit

Permalink
Secure enrollment endpoint (#119)
Browse files Browse the repository at this point in the history
* Add JWT as an authentication class for Enrollment endpoint

* Add settings for default athentication classes

* Add permission class for enrollment view

* Add test for enrollement auth

* Continue modifying test to use requests instead of client.post

* Add tests for using JWT token and modify permission in enrollment view

* Remove unused test function and add setting for default authentication classes

* Remove unused import for base64

* Modify order of default rest authentication classes

* Change bracket type for authentication settings
  • Loading branch information
wondrousWebWorks authored Mar 26, 2024
1 parent 994de46 commit 11ea3aa
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 1 deletion.
110 changes: 110 additions & 0 deletions lms/djangoapps/student_enrollment/tests/test_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import base64

from django.test import TestCase, override_settings
from django.contrib.auth.models import User
from datetime import datetime
from oauth2_provider.models import Application

from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator

from openedx.core.djangoapps.oauth_dispatch.tests.factories import ApplicationFactory, AccessTokenFactory
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.oauth_dispatch.jwt import create_jwt_for_user

from ci_program.models import Program, CourseCode, ProgramCourseCode


# ENROLL_ENDPOINT = 'http://127.0.0.1:8000/enrollment/enroll/'
ENROLLMENT_ENDPOINT = '/enrollment/enroll/'
ACCESS_TOKEN_ENDPOINT = '/oauth2/access_token/'

course_overview_data = {
'_location': BlockUsageLocator(CourseLocator('CodeInstitute', 'HE101', '2020', None, None), 'course', 'course'),
'advertised_start': None,
'announcement': None,
'catalog_visibility': 'both',
'cert_html_view_enabled': True,
'cert_name_long': '',
'cert_name_short': '',
'certificate_available_date': None,
'certificates_display_behavior': 'end',
'certificates_show_before_end': False,
'course_image_url': '/asset-v1:CodeInstitute+HE101+2020+type@asset+block@images_course_image.jpg',
'course_video_url': None,
'created': datetime.now(),
'days_early_for_beta': None,
'display_name': 'HTML Essentials',
'display_number_with_default': 'HE101',
'display_org_with_default': 'CodeInstitute',
'effort': None,
'eligible_for_financial_aid': True,
'end': None,
'end_date': None,
'end_of_course_survey_url': None,
'enrollment_domain': None,
'enrollment_end': None,
'enrollment_start': None,
'has_any_active_web_certificate': False,
'id': CourseLocator('CodeInstitute', 'HE101', '2020', None, None),
'invitation_only': False,
'language': 'en',
'lowest_passing_grade': 0.5,
'marketing_url': None,
'max_student_enrollments_allowed': None,
'mobile_available': False,
'modified': datetime.now(),
'org': 'CodeInstitute',
'self_paced': False,
'short_description': '',
'social_sharing_url': None,
'start': datetime.now(),
'start_date': datetime.now(),
'version': 11,
'visible_to_staff_only': False
}


class AuthTestCase(TestCase):
"""
- make a call to oauth endpoint to get user's JWT token
- make a call to enrollment endpoint with AUTHHEADER including JWT
- make a call to enrollment endpoint without AUTHHEADER
"""
def setUp(self):
self.enrollment_user = User.objects.create_superuser(username='enrollment_user', email='enrollment@codeinstitute.net', password='arglebargle')
self.oauth_client = ApplicationFactory.create()
self.access_token = AccessTokenFactory.create(user=self.enrollment_user, application=self.oauth_client).token
self.course_overview = CourseOverview.objects.create(**course_overview_data)
self.program = Program.objects.create(name='Enrollment Test', program_code='t1')
self.course_code = CourseCode.objects.create(key='CodeInstitute+HE101+2020', display_name='HTML Essentials')
self.program_course_code = ProgramCourseCode.objects.create(program=self.program, course_code=self.course_code, position=1)
with override_settings(JWT_EXPIRATION=300, OAUTH_ID_TOKEN_EXPIRATION=300):
self.expected_jwt = create_jwt_for_user(self.enrollment_user)

@classmethod
def tearDownClass(cls):
pass

def tearDown(self):
self.enrollment_user.delete()
self.oauth_client.delete()
self.course_overview.delete()
self.program.delete()
self.course_code.delete()
self.program_course_code.delete()

def test_enrollment_with_token(self):
email = "bob@bob.com"
headers = {
"HTTP_AUTHORIZATION": "JWT {access_token}".format(access_token=self.expected_jwt)
}
resp = self.client.post(ENROLLMENT_ENDPOINT, data={
"full_name": email, "email": email, "course_code": "t1"}, **headers)
self.assertEqual(resp.status_code, 201)

def test_enrollment_without_token(self):
email = 'bob@bob.com'
resp = self.client.post(ENROLLMENT_ENDPOINT, data={
"full_name": email, "email": email, "course_code": "t1"})
self.assertEqual(resp.status_code, 401)

5 changes: 4 additions & 1 deletion lms/djangoapps/student_enrollment/views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from datetime import date
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
from logging import getLogger
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
from rest_framework.response import Response
from ci_program.api import get_program_by_program_code
Expand Down Expand Up @@ -39,9 +41,10 @@ class StudentEnrollment(APIView):
will enroll a given student in a program using the email address
and program code provided.
"""
authentication_classes = (JwtAuthentication, )
permission_classes = (IsAuthenticated,)

def post(self, request):

log.info("Received request from enrollment API")
data = request.data
serializer = EnrollmentSerializer(data=data)
Expand Down
5 changes: 5 additions & 0 deletions lms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -2597,6 +2597,11 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring
'service_user': '800/minute',
'registration_validation': '30/minute',
},
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
'edx_rest_framework_extensions.auth.jwt.authentication.JwtAuthentication'
],
}

SWAGGER_SETTINGS = {
Expand Down

0 comments on commit 11ea3aa

Please sign in to comment.