From 79886a63f6f3f8f659e789641eac0356d15fd5d2 Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Fri, 8 Nov 2024 19:13:14 +0530 Subject: [PATCH] fix: component asset api views (#35765) (#35800) Uses drf view to authenticate user before allowing them to access library static assets. (cherry picked from commit ca7da3754f061997b29fb23233a045becf1f2c23) --- .../tests/test_content_libraries.py | 4 +- .../tests/test_static_assets.py | 4 +- .../core/djangoapps/content_libraries/urls.py | 4 +- .../djangoapps/content_libraries/views.py | 38 +++++++++++++------ 4 files changed, 33 insertions(+), 17 deletions(-) diff --git a/openedx/core/djangoapps/content_libraries/tests/test_content_libraries.py b/openedx/core/djangoapps/content_libraries/tests/test_content_libraries.py index 8977464dd4b6..2f670d8880a7 100644 --- a/openedx/core/djangoapps/content_libraries/tests/test_content_libraries.py +++ b/openedx/core/djangoapps/content_libraries/tests/test_content_libraries.py @@ -1110,8 +1110,8 @@ def test_library_paste_clipboard(self): self._get_library_block_asset(pasted_usage_key, "static/hello.txt") # Compare the two text files - src_data = self.client.get(f"/library_assets/blocks/{usage_key}/static/hello.txt").content - dest_data = self.client.get(f"/library_assets/blocks/{pasted_usage_key}/static/hello.txt").content + src_data = self.client.get(f"/library_assets/blocks/{usage_key}/static/hello.txt").getvalue() + dest_data = self.client.get(f"/library_assets/blocks/{pasted_usage_key}/static/hello.txt").getvalue() assert src_data == dest_data # Check that the new block was created after the paste and it's content matches diff --git a/openedx/core/djangoapps/content_libraries/tests/test_static_assets.py b/openedx/core/djangoapps/content_libraries/tests/test_static_assets.py index b903a0ca977a..69f5cd2d797c 100644 --- a/openedx/core/djangoapps/content_libraries/tests/test_static_assets.py +++ b/openedx/core/djangoapps/content_libraries/tests/test_static_assets.py @@ -168,12 +168,12 @@ def test_anonymous_user(self): response = self.client.get( f"/library_assets/component_versions/{self.draft_component_version.uuid}/static/test.svg" ) - assert response.status_code == 403 + assert response.status_code == 401 def test_unauthorized_user(self): """User who is not a Content Library staff should not have access.""" self.client.logout() - student = UserFactory.create( + UserFactory.create( username="student", email="student@example.com", password="student-pass", diff --git a/openedx/core/djangoapps/content_libraries/urls.py b/openedx/core/djangoapps/content_libraries/urls.py index 5bf36162d56d..857126eef7c9 100644 --- a/openedx/core/djangoapps/content_libraries/urls.py +++ b/openedx/core/djangoapps/content_libraries/urls.py @@ -79,12 +79,12 @@ path('library_assets/', include([ path( 'component_versions//', - views.component_version_asset, + views.LibraryComponentAssetView.as_view(), name='library-assets', ), path( 'blocks//', - views.component_draft_asset, + views.LibraryComponentDraftAssetView.as_view(), name='library-draft-assets', ), ]) diff --git a/openedx/core/djangoapps/content_libraries/views.py b/openedx/core/djangoapps/content_libraries/views.py index 9357d54ca7ce..57a499899f25 100644 --- a/openedx/core/djangoapps/content_libraries/views.py +++ b/openedx/core/djangoapps/content_libraries/views.py @@ -80,7 +80,6 @@ from django.utils.translation import gettext as _ from django.views.decorators.clickjacking import xframe_options_exempt from django.views.decorators.csrf import csrf_exempt -from django.views.decorators.http import require_safe from django.views.generic.base import TemplateResponseMixin, View from pylti1p3.contrib.django import DjangoCacheDataStorage, DjangoDbToolConf, DjangoMessageLaunch, DjangoOIDCLogin from pylti1p3.exception import LtiException, OIDCException @@ -1168,8 +1167,7 @@ def get(self, request): return JsonResponse(self.lti_tool_config.get_jwks(), safe=False) -@require_safe -def component_version_asset(request, component_version_uuid, asset_path): +def get_component_version_asset(request, component_version_uuid, asset_path): """ Serves static assets associated with particular Component versions. @@ -1239,16 +1237,34 @@ def component_version_asset(request, component_version_uuid, asset_path): ) -@require_safe -def component_draft_asset(request, usage_key, asset_path): +@view_auth_classes() +class LibraryComponentAssetView(APIView): + """ + Serves static assets associated with particular Component versions. + """ + @convert_exceptions + def get(self, request, component_version_uuid, asset_path): + """ + GET API for fetching static asset for given component_version_uuid. + """ + return get_component_version_asset(request, component_version_uuid, asset_path) + + +@view_auth_classes() +class LibraryComponentDraftAssetView(APIView): """ Serves the draft version of static assets associated with a Library Component. - See `component_version_asset` for more details + See `get_component_version_asset` for more details """ - try: - component_version_uuid = api.get_component_from_usage_key(usage_key).versioning.draft.uuid - except ObjectDoesNotExist as exc: - raise Http404() from exc + @convert_exceptions + def get(self, request, usage_key, asset_path): + """ + Fetches component_version_uuid for given usage_key and returns component asset. + """ + try: + component_version_uuid = api.get_component_from_usage_key(usage_key).versioning.draft.uuid + except ObjectDoesNotExist as exc: + raise Http404() from exc - return component_version_asset(request, component_version_uuid, asset_path) + return get_component_version_asset(request, component_version_uuid, asset_path)