From 8444dc4437229abae05329205edab29dd0f5130f Mon Sep 17 00:00:00 2001 From: Nicolas Clerc Date: Fri, 21 Jul 2023 17:00:43 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=82(backend)=20use=20playlist=20token?= =?UTF-8?q?=20in=20markdown=20apis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As we are now using playlist tokens, it has to be allowed on apis. Also, old routes can't be used anymore, as token resource is now a playlist. --- src/backend/marsha/markdown/api.py | 26 ++++++++-- src/backend/marsha/markdown/permissions.py | 46 +++++------------ src/backend/marsha/markdown/serializers.py | 9 +--- .../api/markdown_documents/test_create.py | 6 ++- .../api/markdown_documents/test_delete.py | 6 ++- .../tests/api/markdown_documents/test_list.py | 6 ++- .../markdown_documents/test_render_latex.py | 6 ++- .../api/markdown_documents/test_retrieve.py | 8 +-- .../api/markdown_documents/test_update.py | 8 +-- .../test_update_translations.py | 6 ++- .../tests/api/markdown_images/test_create.py | 34 +++++++++---- .../tests/api/markdown_images/test_delete.py | 41 +++++++++------ .../markdown_images/test_initiate_upload.py | 31 ++++++++---- .../api/markdown_images/test_retrieve.py | 50 +++++++++++++------ src/backend/marsha/markdown/urls.py | 10 +++- 15 files changed, 180 insertions(+), 113 deletions(-) diff --git a/src/backend/marsha/markdown/api.py b/src/backend/marsha/markdown/api.py index 4eca605f91..733249aac6 100644 --- a/src/backend/marsha/markdown/api.py +++ b/src/backend/marsha/markdown/api.py @@ -23,6 +23,22 @@ from .utils.converter import LatexConversionException, render_latex_to_image +class ObjectMarkdownDocumentRelatedMixin: + """ + Get the related markdown document id contained in resource. + + It exposes a function used to get the related markdown document. + It is also useful to avoid URL crafting (when the url markdown_document_id doesn't + match token resource markdown document id). + """ + + def get_related_markdown_document_id(self): + """Get the related markdown document ID from the request.""" + + # The video ID in the URL is mandatory. + return self.kwargs.get("markdown_document_id") + + class MarkdownDocumentFilter(django_filters.FilterSet): """Filter for file depository.""" @@ -57,7 +73,7 @@ class MarkdownDocumentViewSet( permission_classes = [ ( - core_permissions.IsTokenResourceRouteObject + core_permissions.IsPlaylistToken & (core_permissions.IsTokenInstructor | core_permissions.IsTokenAdmin) ) | markdown_permissions.IsMarkdownDocumentPlaylistOrOrganizationAdmin @@ -272,6 +288,7 @@ class MarkdownImageViewSet( APIViewMixin, ObjectPkMixin, ObjectRelatedMixin, + ObjectMarkdownDocumentRelatedMixin, mixins.CreateModelMixin, mixins.DestroyModelMixin, mixins.RetrieveModelMixin, @@ -293,9 +310,7 @@ def get_permissions(self): else: permission_classes = [ markdown_permissions.IsTokenResourceRouteObjectRelatedMarkdownDocument - & core_permissions.IsTokenInstructor - | markdown_permissions.IsTokenResourceRouteObjectRelatedMarkdownDocument - & core_permissions.IsTokenAdmin + & (core_permissions.IsTokenInstructor | core_permissions.IsTokenAdmin) | IsRelatedMarkdownDocumentPlaylistOrOrganizationAdmin ] return [permission() for permission in permission_classes] @@ -306,7 +321,8 @@ def get_queryset(self): """ if self.request.resource: return MarkdownImage.objects.filter( - markdown_document__id=self.request.resource.id, + markdown_document__id=self.get_related_markdown_document_id(), + markdown_document__playlist__id=self.request.resource.id, ) return MarkdownImage.objects.all() diff --git a/src/backend/marsha/markdown/permissions.py b/src/backend/marsha/markdown/permissions.py index 95661e2574..ec6f659138 100644 --- a/src/backend/marsha/markdown/permissions.py +++ b/src/backend/marsha/markdown/permissions.py @@ -4,6 +4,7 @@ from rest_framework import permissions from marsha.core import models +from marsha.markdown.models import MarkdownDocument def _is_organization_admin(user_id, markdown_document_id): @@ -32,26 +33,25 @@ def _is_playlist_admin(user_id, markdown_document_id): ).exists() -class IsTokenResourceRouteObjectRelatedResource(permissions.BasePermission): +class IsTokenResourceRouteObjectRelatedMarkdownDocument(permissions.BasePermission): """ - Base permission class for JWT Tokens related to a resource object linked to a resource. + Base permission class for JWT Tokens related to a resource object linked to a + Markdown document. - These permissions grant access to users authenticated with a resource JWT token built from a - resource. + These permissions grants access to users authenticated with a JWT token built from a + resource ie related to a TokenUser as defined in `rest_framework_simplejwt`. """ - linked_resource_attribute = "" - def has_permission(self, request, view): """ - Allow the request if the JWT resource matches the resource - related to the object in the url. + Allow the request if the JWT resource matches the Markdown document related to the object + in the url. Parameters ---------- - request : Type[rest_framework.request.Request] + request : Type[django.http.request.HttpRequest] The request that holds the authenticated user - view : Type[rest_framework.viewsets or rest_framework.views] + view : Type[restframework.viewsets or restframework.views] The API view for which permissions are being checked Returns @@ -62,29 +62,9 @@ def has_permission(self, request, view): if not request.resource: return False - try: - return ( - str( - getattr( - view.get_related_object(), - self.linked_resource_attribute, - ).id - ) - == request.resource.id - ) - except ObjectDoesNotExist: - return False - - -class IsTokenResourceRouteObjectRelatedMarkdownDocument( - IsTokenResourceRouteObjectRelatedResource -): - """ - Base permission class for JWT Tokens related to a resource object - linked to a Markdown document. - """ - - linked_resource_attribute = "markdown_document" + return MarkdownDocument.objects.filter( + pk=view.get_related_markdown_document_id(), playlist_id=request.resource.id + ).exists() class IsMarkdownDocumentPlaylistOrOrganizationAdmin(permissions.BasePermission): diff --git a/src/backend/marsha/markdown/serializers.py b/src/backend/marsha/markdown/serializers.py index 9e1725ba35..1f3bac005a 100644 --- a/src/backend/marsha/markdown/serializers.py +++ b/src/backend/marsha/markdown/serializers.py @@ -64,14 +64,9 @@ def create(self, validated_data): The "validated_data" dictionary is returned after modification. """ - # resource here is a Markdown document - resource = self.context["request"].resource - markdown_document_id = self.context["request"].data.get("markdown_document") + markdown_document_id = self.context["view"].get_related_markdown_document_id() if not validated_data.get("markdown_document_id"): - if resource: - validated_data["markdown_document_id"] = resource.id - elif markdown_document_id: - validated_data["markdown_document_id"] = markdown_document_id + validated_data["markdown_document_id"] = markdown_document_id return super().create(validated_data) diff --git a/src/backend/marsha/markdown/tests/api/markdown_documents/test_create.py b/src/backend/marsha/markdown/tests/api/markdown_documents/test_create.py index 0bccad4ead..40a44a0b28 100644 --- a/src/backend/marsha/markdown/tests/api/markdown_documents/test_create.py +++ b/src/backend/marsha/markdown/tests/api/markdown_documents/test_create.py @@ -38,7 +38,7 @@ def test_api_document_create_student(self): markdown_document = MarkdownDocumentFactory() jwt_token = StudentLtiTokenFactory( - resource=markdown_document, + resource=markdown_document.playlist, permissions__can_update=True, ) @@ -60,7 +60,9 @@ def test_api_document_create_instructor(self): """An instructor should not be able to create a Markdown document.""" markdown_document = MarkdownDocumentFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=markdown_document) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=markdown_document.playlist + ) response = self.client.post( "/api/markdown-documents/", diff --git a/src/backend/marsha/markdown/tests/api/markdown_documents/test_delete.py b/src/backend/marsha/markdown/tests/api/markdown_documents/test_delete.py index c5454e6088..09a597ae78 100644 --- a/src/backend/marsha/markdown/tests/api/markdown_documents/test_delete.py +++ b/src/backend/marsha/markdown/tests/api/markdown_documents/test_delete.py @@ -38,7 +38,7 @@ def test_api_document_delete_student(self): markdown_document = MarkdownDocumentFactory() jwt_token = StudentLtiTokenFactory( - resource=markdown_document, + resource=markdown_document.playlist, permissions__can_update=True, ) @@ -52,7 +52,9 @@ def test_api_document_delete_instructor(self): """An instructor should not be able to create a Markdown document.""" markdown_document = MarkdownDocumentFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=markdown_document) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=markdown_document.playlist + ) response = self.client.delete( f"/api/markdown-documents/{markdown_document.pk}/", diff --git a/src/backend/marsha/markdown/tests/api/markdown_documents/test_list.py b/src/backend/marsha/markdown/tests/api/markdown_documents/test_list.py index 308ff8195c..b2826d860c 100644 --- a/src/backend/marsha/markdown/tests/api/markdown_documents/test_list.py +++ b/src/backend/marsha/markdown/tests/api/markdown_documents/test_list.py @@ -36,7 +36,7 @@ def test_api_document_fetch_list_student(self): markdown_document = MarkdownDocumentFactory() jwt_token = StudentLtiTokenFactory( - resource=markdown_document, + resource=markdown_document.playlist, permissions__can_update=True, ) @@ -49,7 +49,9 @@ def test_api_document_fetch_list_instructor(self): """An instrustor should not be able to fetch a Markdown document list.""" markdown_document = MarkdownDocumentFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=markdown_document) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=markdown_document.playlist + ) response = self.client.get( "/api/markdown-documents/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}" diff --git a/src/backend/marsha/markdown/tests/api/markdown_documents/test_render_latex.py b/src/backend/marsha/markdown/tests/api/markdown_documents/test_render_latex.py index c41ccffcbe..9bee8a7969 100644 --- a/src/backend/marsha/markdown/tests/api/markdown_documents/test_render_latex.py +++ b/src/backend/marsha/markdown/tests/api/markdown_documents/test_render_latex.py @@ -32,7 +32,7 @@ def test_api_document_render_latex_student(self): markdown_document = MarkdownDocumentFactory() jwt_token = StudentLtiTokenFactory( - resource=markdown_document, + resource=markdown_document.playlist, permissions__can_update=True, ) @@ -48,7 +48,9 @@ def test_api_document_render_latex_instructor(self): """An instructor should be able to render LaTeX content.""" markdown_document = MarkdownDocumentFactory(is_draft=True) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=markdown_document) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=markdown_document.playlist + ) response = self.client.post( f"/api/markdown-documents/{markdown_document.pk}/latex-rendering/", diff --git a/src/backend/marsha/markdown/tests/api/markdown_documents/test_retrieve.py b/src/backend/marsha/markdown/tests/api/markdown_documents/test_retrieve.py index 0a09c55f09..c9dbc8f740 100644 --- a/src/backend/marsha/markdown/tests/api/markdown_documents/test_retrieve.py +++ b/src/backend/marsha/markdown/tests/api/markdown_documents/test_retrieve.py @@ -43,7 +43,7 @@ def test_api_document_fetch_student(self): markdown_document = MarkdownDocumentFactory() jwt_token = StudentLtiTokenFactory( - resource=markdown_document, + resource=markdown_document.playlist, permissions__can_update=True, ) @@ -69,7 +69,9 @@ def test_api_document_fetch_instructor(self): translations__rendered_content="

Heading1

\n

Some content

", ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=markdown_document) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=markdown_document.playlist + ) response = self.client.get( f"/api/markdown-documents/{markdown_document.pk}/", @@ -130,7 +132,7 @@ def test_api_document_fetch_instructor_read_only(self): markdown_document = MarkdownDocumentFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=markdown_document, + resource=markdown_document.playlist, permissions__can_update=False, ) diff --git a/src/backend/marsha/markdown/tests/api/markdown_documents/test_update.py b/src/backend/marsha/markdown/tests/api/markdown_documents/test_update.py index 7a73c9b581..cb2dd9d3cc 100644 --- a/src/backend/marsha/markdown/tests/api/markdown_documents/test_update.py +++ b/src/backend/marsha/markdown/tests/api/markdown_documents/test_update.py @@ -48,7 +48,7 @@ def test_api_document_update_student(self): markdown_document = MarkdownDocumentFactory() jwt_token = StudentLtiTokenFactory( - resource=markdown_document, + resource=markdown_document.playlist, permissions__can_update=True, ) data = {"title": "new title"} @@ -66,7 +66,7 @@ def test_api_document_update_instructor_read_only(self): markdown_document = MarkdownDocumentFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=markdown_document, + resource=markdown_document.playlist, permissions__can_update=False, ) data = {"title": "new title"} @@ -99,7 +99,9 @@ def test_api_document_update_instructor(self): """An instructor should be able to update a Markdown document.""" markdown_document = MarkdownDocumentFactory(is_draft=True) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=markdown_document) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=markdown_document.playlist + ) data = {"is_draft": False} diff --git a/src/backend/marsha/markdown/tests/api/markdown_documents/test_update_translations.py b/src/backend/marsha/markdown/tests/api/markdown_documents/test_update_translations.py index 24979934de..7689c350c7 100644 --- a/src/backend/marsha/markdown/tests/api/markdown_documents/test_update_translations.py +++ b/src/backend/marsha/markdown/tests/api/markdown_documents/test_update_translations.py @@ -30,7 +30,7 @@ def test_api_document_translation_update_student(self): markdown_document = MarkdownDocumentFactory() jwt_token = StudentLtiTokenFactory( - resource=markdown_document, + resource=markdown_document.playlist, permissions__can_update=True, ) @@ -53,7 +53,9 @@ def test_api_document_translation_update_instructor(self): """An instructor should be able to update a Markdown document translated content.""" markdown_document = MarkdownDocumentFactory(is_draft=True) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=markdown_document) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=markdown_document.playlist + ) data = { "language_code": "en", diff --git a/src/backend/marsha/markdown/tests/api/markdown_images/test_create.py b/src/backend/marsha/markdown/tests/api/markdown_images/test_create.py index 974f993b53..c5e8959694 100644 --- a/src/backend/marsha/markdown/tests/api/markdown_images/test_create.py +++ b/src/backend/marsha/markdown/tests/api/markdown_images/test_create.py @@ -25,17 +25,21 @@ class MarkdownImageCreateApiTest(TestCase): def test_api_markdown_image_create_anonymous(self): """Anonymous users should not be able to create a Markdown image.""" - response = self.client.post("/api/markdown-images/") + markdown_document = MarkdownDocumentFactory() + response = self.client.post( + f"/api/markdown-documents/{markdown_document.id}/markdown-images/" + ) self.assertEqual(response.status_code, 401) def test_api_markdown_image_create_student(self): """Student users should not be able to create a Markdown image.""" markdown_document = MarkdownDocumentFactory() - jwt_token = StudentLtiTokenFactory(resource=markdown_document) + jwt_token = StudentLtiTokenFactory(resource=markdown_document.playlist) response = self.client.post( - "/api/markdown-images/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}" + f"/api/markdown-documents/{markdown_document.id}/markdown-images/", + HTTP_AUTHORIZATION=f"Bearer {jwt_token}", ) self.assertEqual(response.status_code, 403) @@ -44,10 +48,13 @@ def test_api_markdown_image_create_instructor(self): """Instructors users should be able to create a Markdown image.""" markdown_document = MarkdownDocumentFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=markdown_document) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=markdown_document.playlist + ) response = self.client.post( - "/api/markdown-images/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}" + f"/api/markdown-documents/{markdown_document.id}/markdown-images/", + HTTP_AUTHORIZATION=f"Bearer {jwt_token}", ) self.assertEqual(response.status_code, 201) @@ -75,10 +82,13 @@ def test_api_markdown_image_create_already_existing_instructor(self): markdown_document = MarkdownDocumentFactory() MarkdownImageFactory(markdown_document=markdown_document) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=markdown_document) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=markdown_document.playlist + ) response = self.client.post( - "/api/markdown-images/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}" + f"/api/markdown-documents/{markdown_document.id}/markdown-images/", + HTTP_AUTHORIZATION=f"Bearer {jwt_token}", ) self.assertEqual(response.status_code, 201) @@ -108,7 +118,8 @@ def test_api_markdown_image_instructor_create_in_read_only(self): ) response = self.client.post( - "/api/markdown-images/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}" + f"/api/markdown-documents/{markdown_image.markdown_document.id}/markdown-images/", + HTTP_AUTHORIZATION=f"Bearer {jwt_token}", ) self.assertEqual(response.status_code, 403) @@ -122,7 +133,8 @@ def test_api_markdown_image_create_user_access_token(self): jwt_token = UserAccessTokenFactory(user=organization_access.user) response = self.client.post( - "/api/markdown-images/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}" + f"/api/markdown-documents/{markdown_document.id}/markdown-images/", + HTTP_AUTHORIZATION=f"Bearer {jwt_token}", ) self.assertEqual(response.status_code, 403) @@ -138,7 +150,7 @@ def test_api_markdown_image_create_user_access_token_organization_admin(self): jwt_token = UserAccessTokenFactory(user=organization_access.user) response = self.client.post( - "/api/markdown-images/", + f"/api/markdown-documents/{markdown_document.id}/markdown-images/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}", data={"markdown_document": str(markdown_document.id)}, ) @@ -167,7 +179,7 @@ def test_api_markdown_image_create_user_access_token_playlist_admin(self): jwt_token = UserAccessTokenFactory(user=playlist_access.user) response = self.client.post( - "/api/markdown-images/", + f"/api/markdown-documents/{markdown_document.id}/markdown-images/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}", data={"markdown_document": str(markdown_document.id)}, ) diff --git a/src/backend/marsha/markdown/tests/api/markdown_images/test_delete.py b/src/backend/marsha/markdown/tests/api/markdown_images/test_delete.py index a5bb933bd5..deeaa9f402 100644 --- a/src/backend/marsha/markdown/tests/api/markdown_images/test_delete.py +++ b/src/backend/marsha/markdown/tests/api/markdown_images/test_delete.py @@ -25,17 +25,23 @@ def test_api_markdown_image_delete_anonymous(self): """Anonymous users should not be able to delete a Markdown image.""" markdown_image = MarkdownImageFactory() - response = self.client.delete(f"/api/markdown-images/{markdown_image.id}/") + response = self.client.delete( + f"/api/markdown-documents/{markdown_image.markdown_document.id}" + f"/markdown-images/{markdown_image.id}/" + ) self.assertEqual(response.status_code, 401) def test_api_markdown_image_delete_student(self): """Student users should not be able to delete a Markdown image.""" markdown_image = MarkdownImageFactory() - jwt_token = StudentLtiTokenFactory(resource=markdown_image.markdown_document) + jwt_token = StudentLtiTokenFactory( + resource=markdown_image.markdown_document.playlist + ) response = self.client.delete( - f"/api/markdown-images/{markdown_image.id}/", + f"/api/markdown-documents/{markdown_image.markdown_document.id}" + f"/markdown-images/{markdown_image.id}/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}", ) self.assertEqual(response.status_code, 403) @@ -45,13 +51,14 @@ def test_api_markdown_image_delete_instructor(self): markdown_image = MarkdownImageFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=markdown_image.markdown_document, + resource=markdown_image.markdown_document.playlist, ) self.assertEqual(MarkdownImage.objects.count(), 1) response = self.client.delete( - f"/api/markdown-images/{markdown_image.id}/", + f"/api/markdown-documents/{markdown_image.markdown_document.id}" + f"/markdown-images/{markdown_image.id}/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}", ) self.assertEqual(response.status_code, 204) @@ -68,7 +75,8 @@ def test_api_markdown_image_delete_instructor(self): # Creating a new Markdown image should be allowed. response = self.client.post( - "/api/markdown-images/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}" + f"/api/markdown-documents/{markdown_image.markdown_document.id}/markdown-images/", + HTTP_AUTHORIZATION=f"Bearer {jwt_token}", ) self.assertEqual(response.status_code, 201) @@ -78,12 +86,13 @@ def test_api_markdown_image_delete_instructor_in_read_only(self): markdown_image = MarkdownImageFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=markdown_image.markdown_document, + resource=markdown_image.markdown_document.playlist, permissions__can_update=False, ) response = self.client.delete( - f"/api/markdown-images/{markdown_image.id}/", + f"/api/markdown-documents/{markdown_image.markdown_document.id}" + f"/markdown-images/{markdown_image.id}/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}", ) @@ -101,7 +110,8 @@ def test_api_markdown_image_delete_instructor_other_markdown_document(self): jwt_token = InstructorOrAdminLtiTokenFactory(resource=markdown_document_token) response = self.client.delete( - f"/api/markdown-images/{markdown_image.id}/", + f"/api/markdown-documents/{markdown_image.markdown_document.id}" + f"/markdown-images/{markdown_image.id}/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}", ) self.assertEqual(response.status_code, 403) @@ -115,7 +125,8 @@ def test_api_markdown_image_delete_user_access_token(self): jwt_token = UserAccessTokenFactory(user=organization_access.user) response = self.client.delete( - f"/api/markdown-images/{markdown_image.id}/", + f"/api/markdown-documents/{markdown_image.markdown_document.id}" + f"/markdown-images/{markdown_image.id}/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}", ) self.assertEqual(response.status_code, 403) @@ -134,7 +145,8 @@ def test_api_markdown_image_delete_user_access_token_organization_admin(self): self.assertEqual(MarkdownImage.objects.count(), 1) response = self.client.delete( - f"/api/markdown-images/{markdown_image.id}/", + f"/api/markdown-documents/{markdown_image.markdown_document.id}" + f"/markdown-images/{markdown_image.id}/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}", ) self.assertEqual(response.status_code, 204) @@ -151,7 +163,7 @@ def test_api_markdown_image_delete_user_access_token_organization_admin(self): # Creating a new Markdown image should be allowed. response = self.client.post( - "/api/markdown-images/", + f"/api/markdown-documents/{markdown_image.markdown_document.id}/markdown-images/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}", data={"markdown_document": str(markdown_image.markdown_document.id)}, ) @@ -173,7 +185,8 @@ def test_api_markdown_image_delete_user_access_token_playlist_admin(self): self.assertEqual(MarkdownImage.objects.count(), 1) response = self.client.delete( - f"/api/markdown-images/{markdown_image.id}/", + f"/api/markdown-documents/{markdown_image.markdown_document.id}" + f"/markdown-images/{markdown_image.id}/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}", ) self.assertEqual(response.status_code, 204) @@ -190,7 +203,7 @@ def test_api_markdown_image_delete_user_access_token_playlist_admin(self): # Creating a new Markdown image should be allowed. response = self.client.post( - "/api/markdown-images/", + f"/api/markdown-documents/{markdown_image.markdown_document.id}/markdown-images/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}", data={"markdown_document": str(markdown_image.markdown_document.id)}, ) diff --git a/src/backend/marsha/markdown/tests/api/markdown_images/test_initiate_upload.py b/src/backend/marsha/markdown/tests/api/markdown_images/test_initiate_upload.py index b5322758d3..1bbdc4a255 100644 --- a/src/backend/marsha/markdown/tests/api/markdown_images/test_initiate_upload.py +++ b/src/backend/marsha/markdown/tests/api/markdown_images/test_initiate_upload.py @@ -30,17 +30,21 @@ def test_api_markdown_image_initiate_upload_anonymous(self): markdown_image = MarkdownImageFactory() response = self.client.post( - f"/api/markdown-images/{markdown_image.id}/initiate-upload/" + f"/api/markdown-documents/{markdown_image.markdown_document.id}" + f"/markdown-images/{markdown_image.id}/initiate-upload/" ) self.assertEqual(response.status_code, 401) def test_api_markdown_image_initiate_upload_student(self): """Student users should not be allowed to initiate an upload.""" markdown_image = MarkdownImageFactory() - jwt_token = StudentLtiTokenFactory(resource=markdown_image.markdown_document) + jwt_token = StudentLtiTokenFactory( + resource=markdown_image.markdown_document.playlist + ) response = self.client.post( - f"/api/markdown-images/{markdown_image.id}/initiate-upload/", + f"/api/markdown-documents/{markdown_image.markdown_document.id}" + f"/markdown-images/{markdown_image.id}/initiate-upload/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}", ) self.assertEqual(response.status_code, 403) @@ -55,7 +59,9 @@ def test_api_markdown_image_initiate_upload_instructor(self): markdown_document=markdown_document, upload_state="ready", ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=markdown_document) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=markdown_document.playlist + ) # Get the upload policy for this Markdown image # It should generate a key file with the Unix timestamp of the present time @@ -65,7 +71,8 @@ def test_api_markdown_image_initiate_upload_instructor(self): ) as mock_dt: mock_dt.utcnow = mock.Mock(return_value=now) response = self.client.post( - f"/api/markdown-images/{markdown_image.id}/initiate-upload/", + f"/api/markdown-documents/{markdown_image.markdown_document.id}" + f"/markdown-images/{markdown_image.id}/initiate-upload/", data={"filename": "not_used.png", "mimetype": "image/png", "size": 10}, HTTP_AUTHORIZATION=f"Bearer {jwt_token}", ) @@ -111,12 +118,13 @@ def test_api_markdown_image_initiate_upload_instructor_read_only(self): markdown_image = MarkdownImageFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=markdown_image.markdown_document, + resource=markdown_image.markdown_document.playlist, permissions__can_update=False, ) response = self.client.post( - f"/api/markdown-images/{markdown_image.id}/initiate-upload/", + f"/api/markdown-documents/{markdown_image.markdown_document.id}" + f"/markdown-images/{markdown_image.id}/initiate-upload/", data={"filename": "not_used.gif", "mimetype": "image/gif", "size": 10}, HTTP_AUTHORIZATION=f"Bearer {jwt_token}", ) @@ -132,7 +140,8 @@ def test_api_markdown_image_initiate_upload_user_access_token(self): jwt_token = UserAccessTokenFactory(user=organization_access.user) response = self.client.post( - f"/api/markdown-images/{markdown_image.id}/initiate-upload/", + f"/api/markdown-documents/{markdown_image.markdown_document.id}" + f"/markdown-images/{markdown_image.id}/initiate-upload/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}", ) self.assertEqual(response.status_code, 403) @@ -160,7 +169,8 @@ def test_api_markdown_image_initiate_upload_user_access_token_organization_admin ) as mock_dt: mock_dt.utcnow = mock.Mock(return_value=now) response = self.client.post( - f"/api/markdown-images/{markdown_image.id}/initiate-upload/", + f"/api/markdown-documents/{markdown_image.markdown_document.id}" + f"/markdown-images/{markdown_image.id}/initiate-upload/", data={"filename": "not_used.png", "mimetype": "image/png", "size": 10}, HTTP_AUTHORIZATION=f"Bearer {jwt_token}", ) @@ -221,7 +231,8 @@ def test_api_markdown_image_initiate_upload_user_access_token_playlist_admin(sel ) as mock_dt: mock_dt.utcnow = mock.Mock(return_value=now) response = self.client.post( - f"/api/markdown-images/{markdown_image.id}/initiate-upload/", + f"/api/markdown-documents/{markdown_image.markdown_document.id}" + f"/markdown-images/{markdown_image.id}/initiate-upload/", data={"filename": "not_used.png", "mimetype": "image/png", "size": 10}, HTTP_AUTHORIZATION=f"Bearer {jwt_token}", ) diff --git a/src/backend/marsha/markdown/tests/api/markdown_images/test_retrieve.py b/src/backend/marsha/markdown/tests/api/markdown_images/test_retrieve.py index a02af8e09d..21dde7b8d8 100644 --- a/src/backend/marsha/markdown/tests/api/markdown_images/test_retrieve.py +++ b/src/backend/marsha/markdown/tests/api/markdown_images/test_retrieve.py @@ -26,7 +26,10 @@ class MarkdownImageRetrieveApiTest(TestCase): def test_api_markdown_image_read_detail_anonymous(self): """Anonymous users should not be allowed to retrieve a Markdown image.""" markdown_image = MarkdownImageFactory() - response = self.client.get(f"/api/markdown-images/{markdown_image.id}/") + response = self.client.get( + f"/api/markdown-documents/{markdown_image.markdown_document.id}" + f"/markdown-images/{markdown_image.id}/" + ) self.assertEqual(response.status_code, 401) content = json.loads(response.content) self.assertEqual( @@ -37,10 +40,13 @@ def test_api_markdown_image_read_detail_student(self): """Students users should not be allowed to read a Markdown image detail.""" markdown_image = MarkdownImageFactory() - jwt_token = StudentLtiTokenFactory(resource=markdown_image.markdown_document) + jwt_token = StudentLtiTokenFactory( + resource=markdown_image.markdown_document.playlist + ) response = self.client.get( - f"/api/markdown-images/{markdown_image.id}/", + f"/api/markdown-documents/{markdown_image.markdown_document.id}" + f"/markdown-images/{markdown_image.id}/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}", ) @@ -51,12 +57,13 @@ def test_api_markdown_image_instructor_read_detail_in_read_only(self): markdown_image = MarkdownImageFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=markdown_image.markdown_document, + resource=markdown_image.markdown_document.playlist, permissions__can_update=False, ) response = self.client.get( - f"/api/markdown-images/{markdown_image.id}/", + f"/api/markdown-documents/{markdown_image.markdown_document.id}" + f"/markdown-images/{markdown_image.id}/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}", ) @@ -74,10 +81,13 @@ def test_api_markdown_image_read_detail_token_user(self): upload_state="pending", ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=markdown_document) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=markdown_document.playlist + ) response = self.client.get( - f"/api/markdown-images/{markdown_image.id}/", + f"/api/markdown-documents/{markdown_image.markdown_document.id}" + f"/markdown-images/{markdown_image.id}/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}", ) @@ -102,12 +112,13 @@ def test_api_markdown_image_administrator_read_detail_in_read_only(self): markdown_image = MarkdownImageFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=markdown_image.markdown_document, + resource=markdown_image.markdown_document.playlist, permissions__can_update=False, ) response = self.client.get( - f"/api/markdown-images/{markdown_image.id}/", + f"/api/markdown-documents/{markdown_image.markdown_document.id}" + f"/markdown-images/{markdown_image.id}/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}", ) @@ -126,12 +137,13 @@ def test_api_markdown_image_read_detail_admin_user(self): ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=markdown_document, + resource=markdown_document.playlist, roles=["administrator"], ) response = self.client.get( - f"/api/markdown-images/{markdown_image.id}/", + f"/api/markdown-documents/{markdown_image.markdown_document.id}" + f"/markdown-images/{markdown_image.id}/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}", ) @@ -164,10 +176,13 @@ def test_api_markdown_image_read_ready_markdown_image(self): extension="gif", ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=markdown_document) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=markdown_document.playlist + ) response = self.client.get( - f"/api/markdown-images/{markdown_image.id}/", + f"/api/markdown-documents/{markdown_image.markdown_document.id}" + f"/markdown-images/{markdown_image.id}/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}", ) @@ -200,7 +215,8 @@ def test_api_markdown_image_read_detail_user_access_token(self): jwt_token = UserAccessTokenFactory(user=organization_access.user) response = self.client.get( - f"/api/markdown-images/{markdown_image.id}/", + f"/api/markdown-documents/{markdown_image.markdown_document.id}" + f"/markdown-images/{markdown_image.id}/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}", ) @@ -219,7 +235,8 @@ def test_api_markdown_image_read_detail_user_access_token_organization_admin(sel jwt_token = UserAccessTokenFactory(user=organization_access.user) response = self.client.get( - f"/api/markdown-images/{markdown_image.id}/", + f"/api/markdown-documents/{markdown_image.markdown_document.id}" + f"/markdown-images/{markdown_image.id}/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}", ) @@ -253,7 +270,8 @@ def test_api_markdown_image_read_detail_user_access_token_playlist_admin(self): jwt_token = UserAccessTokenFactory(user=playlist_access.user) response = self.client.get( - f"/api/markdown-images/{markdown_image.id}/", + f"/api/markdown-documents/{markdown_image.markdown_document.id}" + f"/markdown-images/{markdown_image.id}/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}", ) diff --git a/src/backend/marsha/markdown/urls.py b/src/backend/marsha/markdown/urls.py index 82bb374881..9f712fdc39 100644 --- a/src/backend/marsha/markdown/urls.py +++ b/src/backend/marsha/markdown/urls.py @@ -16,7 +16,11 @@ MarkdownDocumentViewSet, basename="markdown-documents", ) -router.register("markdown-images", MarkdownImageViewSet, basename="markdown-images") + +markdown_document_related_router = DefaultRouter() +markdown_document_related_router.register( + "markdown-images", MarkdownImageViewSet, basename="markdown-images" +) urlpatterns = [ path( @@ -25,4 +29,8 @@ name="markdown_document_lti_view", ), path("api/", include(router.urls)), + path( + "api/markdown-documents//", + include(markdown_document_related_router.urls), + ), ]