From e17d65f422b7d62984383926f004f43f17fb7e0d Mon Sep 17 00:00:00 2001 From: Zach Hancock Date: Fri, 3 Nov 2023 10:57:30 -0400 Subject: [PATCH 1/4] feat: roster endpoint POC --- edx_exams/apps/lti/urls.py | 1 + edx_exams/apps/lti/views.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/edx_exams/apps/lti/urls.py b/edx_exams/apps/lti/urls.py index 42a59b91..9c39053d 100644 --- a/edx_exams/apps/lti/urls.py +++ b/edx_exams/apps/lti/urls.py @@ -12,4 +12,5 @@ path('end_assessment/', views.end_assessment, name='end_assessment'), path('start_proctoring/', views.start_proctoring, name='start_proctoring'), path('exam//instructor_tool', views.launch_instructor_tool, name='instructor_tool'), + path('exam//instructor_tool/roster', views.exam_roster, name='exam_roster'), ] diff --git a/edx_exams/apps/lti/views.py b/edx_exams/apps/lti/views.py index 7742e0a9..a58a3fdf 100644 --- a/edx_exams/apps/lti/views.py +++ b/edx_exams/apps/lti/views.py @@ -7,9 +7,12 @@ from decimal import Decimal from urllib.parse import urljoin +from django.conf import settings from django.contrib.auth import login +from django.http import JsonResponse from django.shortcuts import redirect from django.urls import reverse +from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_http_methods from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication from lti_consumer.api import get_end_assessment_return, get_lti_1p3_launch_start_url @@ -25,6 +28,7 @@ from edx_exams.apps.core.api import ( get_attempt_by_id, get_attempt_for_user_with_attempt_number_and_resource_id, + get_exam_attempts, get_exam_by_id, get_exam_url_path, update_attempt_status @@ -337,6 +341,7 @@ def end_assessment(request, attempt_id): @authentication_classes((JwtAuthentication,)) @permission_classes((IsAuthenticated,)) def launch_instructor_tool(request, exam_id): + # pragma: no cover """ View to initiate an LTI launch of the Instructor Tool for an exam. """ @@ -360,6 +365,37 @@ def launch_instructor_tool(request, exam_id): resource_link_id=exam.resource_id, external_user_id=str(user.anonymous_user_id), context_id=exam.course_id, + custom_parameters={ + 'roster_url': settings.LTI_API_BASE + reverse('lti:exam_roster', kwargs={'exam_id': exam.id}), + } ) + # user is authenticated via JWT so use that to create a + # session with this service's authentication backend + request.user.backend = EDX_OAUTH_BACKEND + login(request, user) + return redirect(get_lti_1p3_launch_start_url(launch_data)) + + +@csrf_exempt +@require_http_methods(['GET']) +def exam_roster(request, exam_id): + """ + Temporary endpoint to prove we can authenticate this request properly + """ + user = request.user + exam = get_exam_by_id(exam_id) + if not user.is_staff and not user.has_course_staff_permission(exam.course_id): + return Response(status=status.HTTP_403_FORBIDDEN) + + attempts = get_exam_attempts(exam_id) + attempts.select_related('user') + + users = set(attempt.user for attempt in attempts) + roster = [ + (user.anonymous_user_id, user.username) + for user in users + ] + + return JsonResponse(roster, safe=False) From 503f23caef4732cca15b2ae14bee91b314c8ecf5 Mon Sep 17 00:00:00 2001 From: Zach Hancock Date: Fri, 3 Nov 2023 14:15:37 -0400 Subject: [PATCH 2/4] fix: missing test setting --- edx_exams/settings/local.py | 1 + edx_exams/settings/test.py | 1 + 2 files changed, 2 insertions(+) diff --git a/edx_exams/settings/local.py b/edx_exams/settings/local.py index 5a752b68..ba38c9a9 100644 --- a/edx_exams/settings/local.py +++ b/edx_exams/settings/local.py @@ -118,6 +118,7 @@ SESSION_COOKIE_DOMAIN = 'localhost' ROOT_URL = 'http://localhost:18740' +LTI_API_BASE = 'http://localhost:18740' LMS_ROOT_URL = 'http://localhost:18000' LEARNING_MICROFRONTEND_URL = 'http://localhost:2000' EXAMS_DASHBOARD_MFE_URL = 'http://localhost:2020' diff --git a/edx_exams/settings/test.py b/edx_exams/settings/test.py index 01c0084a..633c37dd 100644 --- a/edx_exams/settings/test.py +++ b/edx_exams/settings/test.py @@ -23,6 +23,7 @@ ) ROOT_URL = 'http://test.exams:18740' +LTI_API_BASE = 'http://test.exams:18740' LMS_ROOT_URL = 'http://test.lms:18000' LEARNING_MICROFRONTEND_URL = 'http://test.learning:2000' From e7eb8e5e424e626cca68216628caf27348765e2d Mon Sep 17 00:00:00 2001 From: Zach Hancock Date: Fri, 3 Nov 2023 14:31:02 -0400 Subject: [PATCH 3/4] test: fixup failing test --- edx_exams/apps/lti/tests/test_views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/edx_exams/apps/lti/tests/test_views.py b/edx_exams/apps/lti/tests/test_views.py index 7cbf675d..80d5eeb9 100644 --- a/edx_exams/apps/lti/tests/test_views.py +++ b/edx_exams/apps/lti/tests/test_views.py @@ -640,6 +640,9 @@ def test_lti_launch(self, mock_create_launch_url): resource_link_id=self.exam.resource_id, external_user_id=str(self.course_staff_user.anonymous_user_id), context_id=self.exam.course_id, + custom_parameters={ + 'roster_url': 'http://test.exams:18740/lti/exam/1/instructor_tool/roster', + } ) ) self.assertEqual(response.status_code, 302) From 9cf36ec1cf4d41a71c6b1ecd758fc73b731bb98b Mon Sep 17 00:00:00 2001 From: Zach Hancock Date: Fri, 3 Nov 2023 14:42:05 -0400 Subject: [PATCH 4/4] style: nocover temp changes --- edx_exams/apps/lti/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edx_exams/apps/lti/views.py b/edx_exams/apps/lti/views.py index a58a3fdf..b525dedb 100644 --- a/edx_exams/apps/lti/views.py +++ b/edx_exams/apps/lti/views.py @@ -341,7 +341,6 @@ def end_assessment(request, attempt_id): @authentication_classes((JwtAuthentication,)) @permission_classes((IsAuthenticated,)) def launch_instructor_tool(request, exam_id): - # pragma: no cover """ View to initiate an LTI launch of the Instructor Tool for an exam. """ @@ -381,6 +380,7 @@ def launch_instructor_tool(request, exam_id): @csrf_exempt @require_http_methods(['GET']) def exam_roster(request, exam_id): + # pragma: no cover """ Temporary endpoint to prove we can authenticate this request properly """