From 27476093b2537147510cffd6f37418ff7f08be8f Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Tue, 2 Jan 2024 16:31:21 -0500 Subject: [PATCH 1/3] fix: Update the date to the max year possible in a test. (#33995) This test broke on January 1, 2024. --- openedx/features/survey_report/tests/test_query_methods.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/openedx/features/survey_report/tests/test_query_methods.py b/openedx/features/survey_report/tests/test_query_methods.py index b8d60bd79cb..9a4b6431ed5 100644 --- a/openedx/features/survey_report/tests/test_query_methods.py +++ b/openedx/features/survey_report/tests/test_query_methods.py @@ -45,10 +45,7 @@ def test_get_unique_courses_offered(self): """ Test that get_unique_courses_offered returns the correct number of courses. """ - end_time = datetime.now() + timedelta(days=365) - end_time_formatted = end_time.strftime('%Y-%m-%d') - course_overview = CourseOverviewFactory.create(id=self.first_course.id, start="2019-01-01", - end=end_time_formatted) + course_overview = CourseOverviewFactory.create(id=self.first_course.id, start="2019-01-01", end="9999-01-01") CourseEnrollmentFactory.create(user=self.user, course_id=course_overview.id) CourseEnrollmentFactory.create(user=self.user1, course_id=course_overview.id) CourseEnrollmentFactory.create(user=self.user2, course_id=course_overview.id) From 53e0e77fb293c83f0a20caaa70789d70f16ea615 Mon Sep 17 00:00:00 2001 From: Piotr Surowiec Date: Tue, 16 Jan 2024 01:39:45 +0100 Subject: [PATCH 2/3] Merge pull request #34048 from open-craft/agrendalath/xblock-callback-jwt-restricted-application-check-palm fix(palm): add `JwtRestrictedApplication` check to XBlock callback --- lms/djangoapps/courseware/block_render.py | 12 ++++++++-- .../courseware/tests/test_block_render.py | 22 ++++++++++++++++++- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/lms/djangoapps/courseware/block_render.py b/lms/djangoapps/courseware/block_render.py index e9cf15678ee..a741e3219f3 100644 --- a/lms/djangoapps/courseware/block_render.py +++ b/lms/djangoapps/courseware/block_render.py @@ -27,6 +27,7 @@ from edx_proctoring.api import get_attempt_status_summary from edx_proctoring.services import ProctoringService from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication +from edx_rest_framework_extensions.permissions import JwtRestrictedApplication from edx_when.field_data import DateLookupFieldData from eventtracking import tracker from opaque_keys import InvalidKeyError @@ -841,12 +842,19 @@ def handle_xblock_callback(request, course_id, usage_id, handler, suffix=None): ) else: if user_auth_tuple is not None: - request.user, _ = user_auth_tuple + # When using JWT authentication, the second element contains the JWT token. We need it to determine + # whether the application that issued the token is restricted. + request.user, request.auth = user_auth_tuple + # This is verified by the `JwtRestrictedApplication` before it decodes the token. + request.successful_authenticator = authenticator break # NOTE (CCB): Allow anonymous GET calls (e.g. for transcripts). Modifying this view is simpler than updating # the XBlocks to use `handle_xblock_callback_noauth`, which is practically identical to this view. - if request.method != 'GET' and not (request.user and request.user.is_authenticated): + # Block all request types coming from restricted applications. + if ( + request.method != 'GET' and not (request.user and request.user.is_authenticated) + ) or JwtRestrictedApplication().has_permission(request, None): # type: ignore return HttpResponseForbidden('Unauthenticated') request.user.known = request.user.is_authenticated diff --git a/lms/djangoapps/courseware/tests/test_block_render.py b/lms/djangoapps/courseware/tests/test_block_render.py index 53e10b5c513..b0c14e3dbf0 100644 --- a/lms/djangoapps/courseware/tests/test_block_render.py +++ b/lms/djangoapps/courseware/tests/test_block_render.py @@ -81,7 +81,7 @@ from lms.djangoapps.lms_xblock.field_data import LmsFieldData from openedx.core.djangoapps.credit.api import set_credit_requirement_status, set_credit_requirements from openedx.core.djangoapps.credit.models import CreditCourse -from openedx.core.djangoapps.oauth_dispatch.jwt import create_jwt_for_user +from openedx.core.djangoapps.oauth_dispatch.jwt import _create_jwt, create_jwt_for_user from openedx.core.djangoapps.oauth_dispatch.tests.factories import AccessTokenFactory, ApplicationFactory from openedx.core.lib.courses import course_image_url from openedx.core.lib.gating import api as gating_api @@ -375,6 +375,26 @@ def test_jwt_authentication(self): response = self.client.post(dispatch_url, {}, **headers) assert 200 == response.status_code + def test_jwt_authentication_with_restricted_application(self): + """Test that the XBlock endpoint disallows JWT authentication with restricted applications.""" + + def _mock_create_restricted_jwt(*args, **kwargs): + """Pass an additional argument to `_create_jwt` without modifying the signature of `create_jwt_for_user`.""" + kwargs['is_restricted'] = True + return _create_jwt(*args, **kwargs) + + with patch('openedx.core.djangoapps.oauth_dispatch.jwt._create_jwt', _mock_create_restricted_jwt): + token = create_jwt_for_user(self.mock_user) + + dispatch_url = self._get_dispatch_url() + headers = {'HTTP_AUTHORIZATION': 'JWT ' + token} + + response = self.client.get(dispatch_url, {}, **headers) + assert 403 == response.status_code + + response = self.client.post(dispatch_url, {}, **headers) + assert 403 == response.status_code + def test_missing_position_handler(self): """ Test that sending POST request without or invalid position argument don't raise server error From 86fe5be23c9b0e15e69294b49014d11665390eb5 Mon Sep 17 00:00:00 2001 From: Kaustav Banerjee Date: Tue, 6 Feb 2024 04:29:28 +0530 Subject: [PATCH 3/3] fix: add missing function import in certificate template (#33904) (#34171) * fix: add missing function import in certificate template * test: add test case to check certificates generated when GA4 is enabled (cherry picked from commit d0a49d1a01bda06722139513919b1aafcf739fa9) --- lms/djangoapps/certificates/tests/test_views.py | 16 ++++++++++++++++ .../certificates/accomplishment-base.html | 6 ++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/certificates/tests/test_views.py b/lms/djangoapps/certificates/tests/test_views.py index 32db30d12f6..520baa2a8fb 100644 --- a/lms/djangoapps/certificates/tests/test_views.py +++ b/lms/djangoapps/certificates/tests/test_views.py @@ -165,3 +165,19 @@ def test_html_view_site_configuration_missing(self): response, 'This should not survive being overwritten by static content', ) + + @override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED, GOOGLE_ANALYTICS_4_ID='GA-abc') + @with_site_configuration(configuration={'platform_name': 'My Platform Site'}) + def test_html_view_with_g4(self): + test_url = get_certificate_url( + user_id=self.user.id, + course_id=str(self.course.id), + uuid=self.cert.verify_uuid + ) + self._add_course_certificates(count=1, signatory_count=2) + response = self.client.get(test_url) + self.assertContains( + response, + 'awarded this My Platform Site Honor Code Certificate of Completion', + ) + self.assertContains(response, 'googletagmanager') diff --git a/lms/templates/certificates/accomplishment-base.html b/lms/templates/certificates/accomplishment-base.html index 92c58dc138f..e742078384b 100644 --- a/lms/templates/certificates/accomplishment-base.html +++ b/lms/templates/certificates/accomplishment-base.html @@ -1,7 +1,9 @@ <%page expression_filter="h"/> <%namespace name='static' file='/static_content.html'/> -<%! from django.utils.translation import gettext as _%> - +<%! +from django.utils.translation import gettext as _ +from openedx.core.djangolib.js_utils import js_escaped_string +%> <% # set doc language direction from django.utils.translation import get_language_bidi