From a19ee189cd8d6e785ce2804a04c8095c91e1a504 Mon Sep 17 00:00:00 2001 From: Juan David Buitrago Date: Fri, 31 Mar 2023 12:21:59 +0200 Subject: [PATCH] feat: adds TenantAwareLinkRenderStarted filter --- cms/djangoapps/contentstore/views/assets.py | 25 +++- common/djangoapps/util/course.py | 25 +++- common/djangoapps/util/tests/test_filters.py | 126 +++++++++++++++++++ 3 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 common/djangoapps/util/tests/test_filters.py diff --git a/cms/djangoapps/contentstore/views/assets.py b/cms/djangoapps/contentstore/views/assets.py index 38f11db62c53..2e1fbf448228 100644 --- a/cms/djangoapps/contentstore/views/assets.py +++ b/cms/djangoapps/contentstore/views/assets.py @@ -16,6 +16,7 @@ from django.views.decorators.csrf import ensure_csrf_cookie from django.views.decorators.http import require_http_methods, require_POST from opaque_keys.edx.keys import AssetKey, CourseKey +from openedx_filters.learning.filters import TenantAwareLinkRenderStarted from pymongo import ASCENDING, DESCENDING from common.djangoapps.edxmako.shortcuts import render_to_response @@ -598,7 +599,21 @@ def _get_asset_json(display_name, content_type, date, location, thumbnail_locati Helper method for formatting the asset information to send to client. ''' asset_url = StaticContent.serialize_asset_key_with_slash(location) - external_url = urljoin(configuration_helpers.get_value('LMS_ROOT_URL', settings.LMS_ROOT_URL), asset_url) + lms_root = configuration_helpers.get_value('LMS_ROOT_URL', settings.LMS_ROOT_URL) + + try: + ## .. filter_implemented_name: TenantAwareLinkRenderStarted + ## .. filter_type: org.openedx.learning.tenant_aware_link.render.started.v1 + lms_root = TenantAwareLinkRenderStarted.run_filter( + context=lms_root, + org=location.org, + val_name='LMS_ROOT_URL', + default=settings.LMS_ROOT_URL + ) + except TenantAwareLinkRenderStarted.PreventTenantAwarelinkRender as exc: + raise TenantAwareRenderNotAllowed(str(exc)) from exc + + external_url = urljoin(lms_root, asset_url) portable_url = StaticContent.get_static_path_from_location(location) return { 'display_name': display_name, @@ -613,3 +628,11 @@ def _get_asset_json(display_name, content_type, date, location, thumbnail_locati # needed for Backbone delete/update. 'id': str(location) } + + +class TenantAwareRenderException(Exception): + pass + + +class TenantAwareRenderNotAllowed(TenantAwareRenderException): + pass diff --git a/common/djangoapps/util/course.py b/common/djangoapps/util/course.py index 721410ff6976..6dcc59853899 100644 --- a/common/djangoapps/util/course.py +++ b/common/djangoapps/util/course.py @@ -9,6 +9,7 @@ from django.conf import settings from opaque_keys.edx.keys import CourseKey, UsageKey +from openedx_filters.learning.filters import TenantAwareLinkRenderStarted from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers log = logging.getLogger(__name__) @@ -54,14 +55,36 @@ def get_link_for_about_page(course): elif settings.FEATURES.get('ENABLE_MKTG_SITE') and getattr(course, 'marketing_url', None): course_about_url = course.marketing_url else: + about_base = configuration_helpers.get_value('LMS_ROOT_URL', settings.LMS_ROOT_URL) + + try: + ## .. filter_implemented_name: TenantAwareLinkRenderStarted + ## .. filter_type: org.openedx.learning.tenant_aware_link.render.started.v1 + about_base = TenantAwareLinkRenderStarted.run_filter( + context=about_base, + org=course.id.org, + val_name='LMS_ROOT_URL', + default=settings.LMS_ROOT_URL + ) + except TenantAwareLinkRenderStarted.PreventTenantAwarelinkRender as exc: + raise TenantAwareRenderNotAllowed(str(exc)) from exc + course_about_url = '{about_base_url}/courses/{course_key}/about'.format( - about_base_url=configuration_helpers.get_value('LMS_ROOT_URL', settings.LMS_ROOT_URL), + about_base_url=about_base, course_key=str(course.id), ) return course_about_url +class TenantAwareRenderException(Exception): + pass + + +class TenantAwareRenderNotAllowed(TenantAwareRenderException): + pass + + def has_certificates_enabled(course): """ Arguments: diff --git a/common/djangoapps/util/tests/test_filters.py b/common/djangoapps/util/tests/test_filters.py new file mode 100644 index 000000000000..8b475c15cb6f --- /dev/null +++ b/common/djangoapps/util/tests/test_filters.py @@ -0,0 +1,126 @@ +""" +Test that various filters are fired for models/views in the student app. +""" +from django.http import HttpResponse +from django.test import override_settings +from common.djangoapps.util import course +from openedx_filters import PipelineStep +from openedx_filters.learning.filters import TenantAwareLinkRenderStarted +from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from xmodule.modulestore.tests.factories import CourseFactory + +from common.djangoapps.util.course import TenantAwareRenderNotAllowed +from openedx.core.djangolib.testing.utils import skip_unless_lms + + +class TestTenantAwareRenderPipelineStep(PipelineStep): + """ + Utility class used when getting steps for pipeline. + """ + + def run_filter(self, context, org, val_name, default): # pylint: disable=arguments-differ + """Pipeline step that modifies tenant aware links.""" + context = "https://tenant-aware-link" + return { + "context": context + } + + +class TestTenantAwareFilterPrevent(PipelineStep): + """ + Utility class used when getting steps for pipeline. + """ + + def run_filter(self, context, org, val_name, default): # pylint: disable=arguments-differ + """Pipeline step that changes dashboard view response before the dashboard is rendered.""" + response = HttpResponse("This is a custom response.") + raise TenantAwareLinkRenderStarted.PreventTenantAwarelinkRender( + "Can't render tenant aware link.", + response=response, + ) + + +@skip_unless_lms +class TenantAwareLinkFiltersTest(ModuleStoreTestCase): + """ + Tests for the Open edX Filters associated with the Tenant aware link process. + + This class guarantees that the following filters are triggered during the microsite render: + + - TenantAwareLinkRenderStarted + """ + + def setUp(self): # pylint: disable=arguments-differ + super().setUp() + self.course = CourseFactory.create() + self.org = "test" + self.val_name = 'LMS_ROOT_URL' + self.default = "https://lms-base" + + @override_settings( + OPEN_EDX_FILTERS_CONFIG={ + "org.openedx.learning.tenant_aware_link.render.started.v1": { + "pipeline": [ + "common.djangoapps.util.tests.test_filters.TestTenantAwareRenderPipelineStep", + ], + "fail_silently": False, + }, + }, + ) + def test_tenant_aware_filter_executed(self): + """ + Test whether the tenant aware link filter is triggered before the user's + render site process. + + Expected result: + - TenantAwareLinkRenderStarted is triggered and executes TestTenantAwareRenderPipelineStep. + - The arguments that the receiver gets are the arguments used by the filter. + """ + course_about_url = course.get_link_for_about_page(self.course) + + expected_course_about = '{about_base_url}/courses/{course_key}/about'.format( + about_base_url='https://tenant-aware-link', + course_key=str(self.course.id), + ) + + self.assertEqual(expected_course_about, course_about_url) + + @override_settings( + OPEN_EDX_FILTERS_CONFIG={ + "org.openedx.learning.tenant_aware_link.render.started.v1": { + "pipeline": [ + "common.djangoapps.util.tests.test_filters.TestTenantAwareFilterPrevent", + ], + "fail_silently": False, + }, + }, + ) + def test_tenant_aware_filter_prevent(self): + """ + Test prevent the tenant aware link filter through a pipeline step. + + Expected result: + - TenantAwareLinkRenderStarted is triggered and executes TestTenantAwareFilterPrevent. + - The user can't get tenant aware links. + """ + with self.assertRaises(TenantAwareRenderNotAllowed): + course.get_link_for_about_page(self.course) + + @override_settings(OPEN_EDX_FILTERS_CONFIG={}, LMS_ROOT_URL="https://lms-base") + def test_enrollment_without_filter_configuration(self): + """ + Test usual get link for about page process, without filter's intervention. + + Expected result: + - Returns the course sharing url, this can be one of course's social sharing url, marketing url, or + lms course about url. + - The get process ends successfully. + """ + course_about_url = course.get_link_for_about_page(self.course) + + expected_course_about = '{about_base_url}/courses/{course_key}/about'.format( + about_base_url='https://lms-base', + course_key=str(self.course.id), + ) + + self.assertEqual(expected_course_about, course_about_url)