From 6dd0e682b36629037d0d1f650f603b39a1784d49 Mon Sep 17 00:00:00 2001 From: Nicolas Clerc Date: Tue, 18 Jul 2023 12:49:31 +0200 Subject: [PATCH 1/7] =?UTF-8?q?=F0=9F=9B=82(backend)=20send=20playlist=20t?= =?UTF-8?q?oken=20for=20LTI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We want to use playlist token instead of resource ones. See #1342 --- .../marsha/bbb/tests/test_views_lti.py | 4 +- src/backend/marsha/core/simple_jwt/tokens.py | 8 ++-- .../core/tests/simple_jwt/test_factories.py | 5 ++- .../core/tests/simple_jwt/test_tokens.py | 37 +++++-------------- .../core/tests/views/test_lti_document.py | 6 +-- .../marsha/core/tests/views/test_lti_video.py | 22 +++++------ src/backend/marsha/core/views.py | 1 + .../marsha/deposit/tests/test_views_lti.py | 4 +- .../tests/test_views_lti_development.py | 6 +-- .../marsha/markdown/tests/test_views_lti.py | 4 +- 10 files changed, 41 insertions(+), 56 deletions(-) diff --git a/src/backend/marsha/bbb/tests/test_views_lti.py b/src/backend/marsha/bbb/tests/test_views_lti.py index e96c1461ba..e4b3aeef62 100644 --- a/src/backend/marsha/bbb/tests/test_views_lti.py +++ b/src/backend/marsha/bbb/tests/test_views_lti.py @@ -250,7 +250,7 @@ def test_views_lti_classroom_instructor_same_playlist( context = json.loads(html.unescape(match.group(1))) jwt_token = ResourceAccessToken(context.get("jwt")) - self.assertEqual(jwt_token.payload["resource_id"], str(classroom.id)) + self.assertEqual(jwt_token.payload["resource_id"], str(classroom.playlist.id)) self.assertEqual( jwt_token.payload["user"], { @@ -589,7 +589,7 @@ def test_views_lti_classroom_instructor_same_playlist( context = json.loads(html.unescape(match.group(1))) jwt_token = ResourceAccessToken(context.get("jwt")) - self.assertEqual(jwt_token.payload["resource_id"], str(classroom.id)) + self.assertEqual(jwt_token.payload["resource_id"], str(classroom.playlist.id)) self.assertEqual( jwt_token.payload["user"], { diff --git a/src/backend/marsha/core/simple_jwt/tokens.py b/src/backend/marsha/core/simple_jwt/tokens.py index c9613f7e96..a9b80b70ae 100644 --- a/src/backend/marsha/core/simple_jwt/tokens.py +++ b/src/backend/marsha/core/simple_jwt/tokens.py @@ -127,7 +127,7 @@ def for_lti( lti, permissions, session_id, - playlist_id=None, + playlist_id, ): """ Returns an authorization token for the resource in the LTI request that will be provided @@ -167,7 +167,7 @@ def for_lti( - user_fullname """ token = cls.for_resource_id( - str(lti.resource_id), + str(playlist_id), session_id, permissions=permissions, roles=lti.roles, @@ -180,9 +180,7 @@ def for_lti( } ) - if playlist_id: - assert lti.is_instructor or lti.is_admin # nosec - token.payload["playlist_id"] = playlist_id + token.payload["playlist_id"] = playlist_id user_id = getattr(lti, "user_id", None) if user_id: diff --git a/src/backend/marsha/core/tests/simple_jwt/test_factories.py b/src/backend/marsha/core/tests/simple_jwt/test_factories.py index b8455f8611..889f1172a1 100644 --- a/src/backend/marsha/core/tests/simple_jwt/test_factories.py +++ b/src/backend/marsha/core/tests/simple_jwt/test_factories.py @@ -33,6 +33,7 @@ def test_payload_keys(self): "lis_person_contact_email_primary": "jane@test-mooc.fr", }, ) + playlist_id = uuid.uuid4() request = RequestFactory().post( url, @@ -43,9 +44,9 @@ def test_payload_keys(self): self.assertTrue(lti.verify()) session_id = uuid.uuid4() - jwt = ResourceAccessToken.for_lti(lti, {}, session_id) + jwt = ResourceAccessToken.for_lti(lti, {}, session_id, playlist_id) - jwt_from_factory = LTIResourceAccessTokenFactory() + jwt_from_factory = LTIResourceAccessTokenFactory(playlist_id=playlist_id) self.assertSetEqual( set(jwt.payload.keys()), diff --git a/src/backend/marsha/core/tests/simple_jwt/test_tokens.py b/src/backend/marsha/core/tests/simple_jwt/test_tokens.py index 3774978d3b..a67e7e7742 100644 --- a/src/backend/marsha/core/tests/simple_jwt/test_tokens.py +++ b/src/backend/marsha/core/tests/simple_jwt/test_tokens.py @@ -141,8 +141,14 @@ def test_for_lti(self): session_id = str(uuid.uuid4()) resource_id = str(uuid.uuid4()) lti, passport = self.make_lti_instance(resource_id=resource_id) + playlist_id = str(uuid.uuid4()) - refresh_token = ResourceRefreshToken.for_lti(lti, permissions, session_id) + refresh_token = ResourceRefreshToken.for_lti( + lti, + permissions, + session_id, + playlist_id=playlist_id, + ) token = refresh_token.access_token refresh_token.verify() # Must not raise @@ -150,7 +156,7 @@ def test_for_lti(self): self.assertEqual(refresh_token.payload["access_token_type"], "resource_access") self.assertEqual(token.payload["token_type"], "resource_access") self.assertEqual(token.payload["session_id"], session_id) - self.assertEqual(token.payload["resource_id"], resource_id) + self.assertEqual(token.payload["resource_id"], playlist_id) self.assertEqual(token.payload["roles"], [INSTRUCTOR]) self.assertEqual(token.payload["locale"], "en_US") self.assertDictEqual(token.payload["permissions"], permissions) @@ -189,7 +195,7 @@ def test_for_lti_with_playlist(self): self.assertEqual(refresh_token.payload["access_token_type"], "resource_access") self.assertEqual(token.payload["token_type"], "resource_access") self.assertEqual(token.payload["session_id"], session_id) - self.assertEqual(token.payload["resource_id"], resource_id) + self.assertEqual(token.payload["resource_id"], playlist_id) self.assertEqual(token.payload["roles"], [INSTRUCTOR]) self.assertEqual(token.payload["locale"], "en_US") self.assertDictEqual(token.payload["permissions"], permissions) @@ -208,31 +214,6 @@ def test_for_lti_with_playlist(self): ) self.assertEqual(token.payload["playlist_id"], playlist_id) - def test_for_lti_with_playlist_for_student(self): - """Test JWT initialization from `for_lti` method with a playlist but student role.""" - permissions = {"can_access_dashboard": False, "can_update": False} - session_id = str(uuid.uuid4()) - resource_id = str(uuid.uuid4()) - lti, _passport = self.make_lti_instance(resource_id=resource_id, role=STUDENT) - - playlist_id = str(uuid.uuid4()) - - with self.assertRaises(AssertionError): - ResourceAccessToken.for_lti( - lti, - permissions, - session_id, - playlist_id=playlist_id, - ) - - with self.assertRaises(AssertionError): - ResourceRefreshToken.for_lti( - lti, - permissions, - session_id, - playlist_id=playlist_id, - ) - def test_for_live_session_anonymous(self): """Test JWT initialization from `for_live_session` method with public session.""" permissions = {"can_access_dashboard": False, "can_update": False} diff --git a/src/backend/marsha/core/tests/views/test_lti_document.py b/src/backend/marsha/core/tests/views/test_lti_document.py index 418353524a..36025d3ff7 100644 --- a/src/backend/marsha/core/tests/views/test_lti_document.py +++ b/src/backend/marsha/core/tests/views/test_lti_document.py @@ -72,7 +72,7 @@ def test_views_lti_document_instructor_same_playlist( context = json.loads(html.unescape(match.group(1))) jwt_token = ResourceAccessToken(context.get("jwt")) ResourceRefreshToken(context.get("refresh_token")) # Must not raise - self.assertEqual(jwt_token.payload["resource_id"], str(document.id)) + self.assertEqual(jwt_token.payload["resource_id"], str(document.playlist.id)) self.assertEqual( jwt_token.payload["user"], { @@ -137,7 +137,7 @@ def test_views_lti_document_instructor_other_playlist( context = json.loads(html.unescape(match.group(1))) jwt_token = ResourceAccessToken(context.get("jwt")) ResourceRefreshToken(context.get("refresh_token")) # Must not raise - self.assertEqual(jwt_token.payload["resource_id"], str(document.id)) + self.assertEqual(jwt_token.payload["resource_id"], str(document.playlist.id)) self.assertEqual( jwt_token.payload["user"], { @@ -198,7 +198,7 @@ def test_views_lti_document_student_with_video( context = json.loads(html.unescape(match.group(1))) jwt_token = ResourceAccessToken(context.get("jwt")) ResourceRefreshToken(context.get("refresh_token")) # Must not raise - self.assertEqual(jwt_token.payload["resource_id"], str(document.id)) + self.assertEqual(jwt_token.payload["resource_id"], str(document.playlist.id)) self.assertEqual( jwt_token.payload["user"], { diff --git a/src/backend/marsha/core/tests/views/test_lti_video.py b/src/backend/marsha/core/tests/views/test_lti_video.py index 5106cc65f8..59b4df871c 100644 --- a/src/backend/marsha/core/tests/views/test_lti_video.py +++ b/src/backend/marsha/core/tests/views/test_lti_video.py @@ -93,7 +93,7 @@ def test_views_lti_video_post_instructor(self, mock_get_consumer_site, mock_veri jwt_token = ResourceAccessToken(context.get("jwt")) ResourceRefreshToken(context.get("refresh_token")) # Must not raise self.assertEqual(context.get("frontend_home_url"), "https://marsha.education") - self.assertEqual(jwt_token.payload["resource_id"], str(video.id)) + self.assertEqual(jwt_token.payload["resource_id"], str(video.playlist.id)) self.assertEqual(jwt_token.payload["context_id"], data["context_id"]) self.assertEqual( jwt_token.payload["consumer_site"], str(passport.consumer_site.id) @@ -249,7 +249,7 @@ def test_views_lti_video_instructor_live_mode_on( context = json.loads(unescape(match.group(1))) jwt_token = ResourceAccessToken(context.get("jwt")) ResourceRefreshToken(context.get("refresh_token")) # Must not raise - self.assertEqual(jwt_token.payload["resource_id"], str(video.id)) + self.assertEqual(jwt_token.payload["resource_id"], str(video.playlist.id)) self.assertEqual( jwt_token.payload["user"], { @@ -430,7 +430,7 @@ def test_views_lti_video_instructor_live_mode_and_chat_on( context = json.loads(unescape(match.group(1))) jwt_token = ResourceAccessToken(context.get("jwt")) ResourceRefreshToken(context.get("refresh_token")) # Must not raise - self.assertEqual(jwt_token.payload["resource_id"], str(video.id)) + self.assertEqual(jwt_token.payload["resource_id"], str(video.playlist.id)) self.assertEqual( jwt_token.payload["user"], { @@ -651,7 +651,7 @@ def test_views_lti_video_student_live_mode_on( context = json.loads(unescape(match.group(1))) jwt_token = ResourceAccessToken(context.get("jwt")) ResourceRefreshToken(context.get("refresh_token")) # Must not raise - self.assertEqual(jwt_token.payload["resource_id"], str(video.id)) + self.assertEqual(jwt_token.payload["resource_id"], str(video.playlist.id)) self.assertEqual( jwt_token.payload["user"], { @@ -808,7 +808,7 @@ def test_views_lti_video_post_administrator( context = json.loads(unescape(match.group(1))) jwt_token = ResourceAccessToken(context.get("jwt")) ResourceRefreshToken(context.get("refresh_token")) # Must not raise - self.assertEqual(jwt_token.payload["resource_id"], str(video.id)) + self.assertEqual(jwt_token.payload["resource_id"], str(video.playlist.id)) self.assertEqual( jwt_token.payload["user"], { @@ -1056,7 +1056,7 @@ def test_views_lti_video_restricted_resolutions_list( context = json.loads(unescape(match.group(1))) jwt_token = ResourceAccessToken(context.get("jwt")) ResourceRefreshToken(context.get("refresh_token")) # Must not raise - self.assertEqual(jwt_token.payload["resource_id"], str(video.id)) + self.assertEqual(jwt_token.payload["resource_id"], str(video.playlist.id)) self.assertEqual( jwt_token.payload["user"], { @@ -1196,7 +1196,7 @@ def test_views_lti_video_harvested_live_state_student( context = json.loads(unescape(match.group(1))) jwt_token = ResourceAccessToken(context.get("jwt")) ResourceRefreshToken(context.get("refresh_token")) # Must not raise - self.assertEqual(jwt_token.payload["resource_id"], str(video.id)) + self.assertEqual(jwt_token.payload["resource_id"], str(video.playlist.id)) self.assertEqual( jwt_token.payload["user"], { @@ -1310,7 +1310,7 @@ def test_views_lti_video_harvested_live_state_instructor( context = json.loads(unescape(match.group(1))) jwt_token = ResourceAccessToken(context.get("jwt")) ResourceRefreshToken(context.get("refresh_token")) # Must not raise - self.assertEqual(jwt_token.payload["resource_id"], str(video.id)) + self.assertEqual(jwt_token.payload["resource_id"], str(video.playlist.id)) self.assertEqual( jwt_token.payload["user"], { @@ -1452,7 +1452,7 @@ def test_views_lti_video_post_student_with_video( context = json.loads(unescape(match.group(1))) jwt_token = ResourceAccessToken(context.get("jwt")) ResourceRefreshToken(context.get("refresh_token")) # Must not raise - self.assertEqual(jwt_token.payload["resource_id"], str(video.id)) + self.assertEqual(jwt_token.payload["resource_id"], str(video.playlist.id)) self.assertEqual( jwt_token.payload["user"], { @@ -1596,7 +1596,7 @@ def test_views_lti_video_without_user_id_parameter( context = json.loads(unescape(match.group(1))) jwt_token = ResourceAccessToken(context.get("jwt")) ResourceRefreshToken(context.get("refresh_token")) # Must not raise - self.assertEqual(jwt_token.payload["resource_id"], str(video.id)) + self.assertEqual(jwt_token.payload["resource_id"], str(video.playlist.id)) self.assertEqual(jwt_token.payload["context_id"], data["context_id"]) self.assertEqual( jwt_token.payload["consumer_site"], str(passport.consumer_site.id) @@ -1730,7 +1730,7 @@ def test_views_lti_video_with_timed_text(self, mock_get_consumer_site, mock_veri context = json.loads(unescape(match.group(1))) jwt_token = ResourceAccessToken(context.get("jwt")) ResourceRefreshToken(context.get("refresh_token")) # Must not raise - self.assertEqual(jwt_token.payload["resource_id"], str(video.id)) + self.assertEqual(jwt_token.payload["resource_id"], str(video.playlist.id)) self.assertEqual(jwt_token.payload["context_id"], data["context_id"]) self.assertEqual( jwt_token.payload["consumer_site"], str(passport.consumer_site.id) diff --git a/src/backend/marsha/core/views.py b/src/backend/marsha/core/views.py index fa2512b1ef..1da47d1c45 100644 --- a/src/backend/marsha/core/views.py +++ b/src/backend/marsha/core/views.py @@ -539,6 +539,7 @@ def _get_app_data(self): lti=self.lti, permissions=permissions, session_id=session_id, + playlist_id=str(app_data["resource"]["playlist"]["id"]), ) jwt_token = refresh_token.access_token app_data["jwt"] = str(jwt_token) diff --git a/src/backend/marsha/deposit/tests/test_views_lti.py b/src/backend/marsha/deposit/tests/test_views_lti.py index db8f59f42a..2995fa2159 100644 --- a/src/backend/marsha/deposit/tests/test_views_lti.py +++ b/src/backend/marsha/deposit/tests/test_views_lti.py @@ -219,7 +219,9 @@ def test_views_lti_file_depository_instructor_same_playlist( context = json.loads(html.unescape(match.group(1))) jwt_token = ResourceAccessToken(context.get("jwt")) - self.assertEqual(jwt_token.payload["resource_id"], str(file_depository.id)) + self.assertEqual( + jwt_token.payload["resource_id"], str(file_depository.playlist.id) + ) self.assertEqual( jwt_token.payload["user"], { diff --git a/src/backend/marsha/development/tests/test_views_lti_development.py b/src/backend/marsha/development/tests/test_views_lti_development.py index 3bbbc44007..fb7a486875 100644 --- a/src/backend/marsha/development/tests/test_views_lti_development.py +++ b/src/backend/marsha/development/tests/test_views_lti_development.py @@ -67,7 +67,7 @@ def test_views_lti_development_post_bypass_lti_student(self): context = json.loads(unescape(match.group(1))) jwt_token = ResourceAccessToken(context.get("jwt")) - self.assertEqual(jwt_token.payload["resource_id"], str(video.id)) + self.assertEqual(jwt_token.payload["resource_id"], str(video.playlist.id)) self.assertEqual( jwt_token.payload["user"], { @@ -121,7 +121,7 @@ def test_views_lti_development_post_bypass_lti_instructor(self): context = json.loads(unescape(match.group(1))) jwt_token = ResourceAccessToken(context.get("jwt")) - self.assertEqual(jwt_token.payload["resource_id"], str(video.id)) + self.assertEqual(jwt_token.payload["resource_id"], str(video.playlist.id)) self.assertEqual( jwt_token.payload["user"], { @@ -214,7 +214,7 @@ def test_views_lti_development_post_bypass_lti_instructor_no_video(self): context = json.loads(unescape(match.group(1))) jwt_token = ResourceAccessToken(context.get("jwt")) video = Video.objects.get() - self.assertEqual(jwt_token.payload["resource_id"], str(video.id)) + self.assertEqual(jwt_token.payload["resource_id"], str(video.playlist.id)) self.assertEqual( jwt_token.payload["user"], { diff --git a/src/backend/marsha/markdown/tests/test_views_lti.py b/src/backend/marsha/markdown/tests/test_views_lti.py index 0576cad18f..9fd6fa709b 100644 --- a/src/backend/marsha/markdown/tests/test_views_lti.py +++ b/src/backend/marsha/markdown/tests/test_views_lti.py @@ -157,7 +157,9 @@ def test_views_lti_markdown_document_instructor_same_playlist( context = json.loads(html.unescape(match.group(1))) jwt_token = ResourceAccessToken(context.get("jwt")) - self.assertEqual(jwt_token.payload["resource_id"], str(markdown_document.id)) + self.assertEqual( + jwt_token.payload["resource_id"], str(markdown_document.playlist.id) + ) self.assertEqual( jwt_token.payload["user"], { From 96a13f770eae5840b55e7dd69410ff223b59298f Mon Sep 17 00:00:00 2001 From: Nicolas Clerc Date: Tue, 18 Jul 2023 14:07:24 +0200 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=9B=82(backend)=20use=20playlist=20to?= =?UTF-8?q?ken=20in=20video=20api?= 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 video apis. --- src/backend/marsha/core/api/video.py | 7 +- src/backend/marsha/core/models/video.py | 12 +++- src/backend/marsha/core/permissions/token.py | 22 ++++++ .../core/tests/api/video/test_bulk_destroy.py | 6 +- .../core/tests/api/video/test_create.py | 2 +- .../core/tests/api/video/test_destroy.py | 6 +- .../core/tests/api/video/test_harvest_live.py | 14 ++-- .../tests/api/video/test_initiate_live.py | 14 ++-- .../tests/api/video/test_initiate_upload.py | 8 +-- .../core/tests/api/video/test_jitsi_info.py | 14 ++-- .../marsha/core/tests/api/video/test_list.py | 2 +- .../core/tests/api/video/test_live_pairing.py | 10 +-- .../test_live_participants_asking_to_join.py | 26 +++---- .../video/test_live_participants_joined.py | 20 +++--- .../core/tests/api/video/test_live_to_vod.py | 4 +- .../core/tests/api/video/test_metadata.py | 4 +- .../core/tests/api/video/test_retrieve.py | 30 ++++---- .../tests/api/video/test_shared_live_media.py | 28 ++++---- .../core/tests/api/video/test_start_live.py | 14 ++-- .../api/video/test_start_stop_recording.py | 12 ++-- .../marsha/core/tests/api/video/test_stats.py | 2 +- .../core/tests/api/video/test_stop_live.py | 12 ++-- .../core/tests/api/video/test_update.py | 68 +++++++++---------- .../test_send_reminders.py | 2 +- 24 files changed, 186 insertions(+), 153 deletions(-) diff --git a/src/backend/marsha/core/api/video.py b/src/backend/marsha/core/api/video.py index 9e95e8dddc..d07e11fd6d 100644 --- a/src/backend/marsha/core/api/video.py +++ b/src/backend/marsha/core/api/video.py @@ -133,7 +133,7 @@ def get_permissions(self): # `VideoConsumer` websocket consumer. permission_classes = [ # With LTI: anyone with a valid token for the video can access - permissions.IsTokenResourceRouteObject + permissions.IsPlaylistToken # With standalone site, only playlist admin or organization admin can access | permissions.IsObjectPlaylistAdminOrInstructor | permissions.IsObjectPlaylistOrganizationAdmin @@ -167,10 +167,9 @@ def get_permissions(self): permission_classes = [ # With LTI: playlist admin or instructor admin or playlist access can access ( - permissions.IsTokenResourceRouteObject + permissions.IsPlaylistToken & (permissions.IsTokenInstructor | permissions.IsTokenAdmin) ) - | permissions.HasPlaylistToken # With standalone site, only playlist admin or instructor # and organization admin can access | permissions.IsObjectPlaylistAdminOrInstructor @@ -197,7 +196,7 @@ def get_permissions(self): ]: permission_classes = [ # With LTI: playlist admin or instructor admin can access - permissions.IsTokenResourceRouteObject + permissions.IsPlaylistToken & (permissions.IsTokenInstructor | permissions.IsTokenAdmin) # With standalone site, playlist admin or instructor can access | permissions.IsObjectPlaylistAdminOrInstructor diff --git a/src/backend/marsha/core/models/video.py b/src/backend/marsha/core/models/video.py index fe49ff8706..8774a65fc3 100644 --- a/src/backend/marsha/core/models/video.py +++ b/src/backend/marsha/core/models/video.py @@ -643,6 +643,16 @@ class Thumbnail(AbstractImage): on_delete=models.CASCADE, ) + @property + def playlist(self): + """Return the playlist of the video.""" + return self.video.playlist + + @property + def playlist_id(self): + """Return the playlist id of the video.""" + return self.video.playlist_id + class Meta: """Options for the ``Thumbnail`` model.""" @@ -951,7 +961,7 @@ def video_access_reminder_url(self): """Returns url to access video from mails.""" return ( f"//{Site.objects.get_current()}" - f"{reverse('video_direct_access',kwargs={'uuid': self.video.pk})}" + f"{reverse('video_direct_access', kwargs={'uuid': self.video.pk})}" f"?lrpk={self.pk}&key={self.get_generate_salted_hmac()}" ) diff --git a/src/backend/marsha/core/permissions/token.py b/src/backend/marsha/core/permissions/token.py index 7674144136..91c7beb1d6 100644 --- a/src/backend/marsha/core/permissions/token.py +++ b/src/backend/marsha/core/permissions/token.py @@ -159,6 +159,28 @@ def has_permission(self, request, view): return False +class IsPlaylistToken(permissions.BasePermission): + """ + Allow a request to proceed. Permission class. + + Only if the user has a playlist token payload. + """ + + def has_permission(self, request, view): + """ + Allow the request. + + Only if the playlist exists. + """ + if request.resource: + playlist_id = request.resource.id + return models.Playlist.objects.filter(id=playlist_id).exists() and ( + str(view.get_queryset().get(id=view.get_object_pk()).playlist_id) + == playlist_id + ) + return False + + class HasPlaylistToken(permissions.BasePermission): """ Allow a request to proceed. Permission class. diff --git a/src/backend/marsha/core/tests/api/video/test_bulk_destroy.py b/src/backend/marsha/core/tests/api/video/test_bulk_destroy.py index 4aca08fdd8..e4483885f4 100644 --- a/src/backend/marsha/core/tests/api/video/test_bulk_destroy.py +++ b/src/backend/marsha/core/tests/api/video/test_bulk_destroy.py @@ -34,7 +34,7 @@ def test_api_video_bulk_delete_detail_anonymous(self): def test_api_video_bulk_delete_detail_token_user(self): """A token user associated to a video should not be able to delete it or any other.""" video1 = factories.VideoFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video1) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video1.playlist) # Try deleting the video linked to the JWT token and the other one response = self.client.delete( @@ -50,7 +50,7 @@ def test_api_video_bulk_delete_detail_token_user(self): def test_api_video_bulk_delete_detail_student(self): """Student users should not be able to delete a video.""" video1 = factories.VideoFactory() - jwt_token = StudentLtiTokenFactory(resource=video1) + jwt_token = StudentLtiTokenFactory(resource=video1.playlist) response = self.client.delete( self._api_url(), @@ -241,7 +241,7 @@ def test_api_video_instructor_bulk_delete_video_in_read_only(self): video1 = factories.VideoFactory() video2 = factories.VideoFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video1, + resource=video1.playlist, permissions__can_update=False, ) diff --git a/src/backend/marsha/core/tests/api/video/test_create.py b/src/backend/marsha/core/tests/api/video/test_create.py index 73fb0eea61..81e6b22a9e 100644 --- a/src/backend/marsha/core/tests/api/video/test_create.py +++ b/src/backend/marsha/core/tests/api/video/test_create.py @@ -39,7 +39,7 @@ def test_api_video_create_token_user_playlist_preexists(self): def test_api_video_create_student(self): """Student users should not be able to create videos.""" video = factories.VideoFactory() - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) response = self.client.post( "/api/videos/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}", diff --git a/src/backend/marsha/core/tests/api/video/test_destroy.py b/src/backend/marsha/core/tests/api/video/test_destroy.py index 04d1facfad..5ba977c630 100644 --- a/src/backend/marsha/core/tests/api/video/test_destroy.py +++ b/src/backend/marsha/core/tests/api/video/test_destroy.py @@ -30,7 +30,7 @@ def test_api_video_delete_detail_anonymous(self): def test_api_video_delete_detail_token_user(self): """A token user associated to a video should not be able to delete it or any other.""" videos = factories.VideoFactory.create_batch(2) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=videos[0]) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=videos[0].playlist) # Try deleting the video linked to the JWT token and the other one for video in videos: @@ -44,7 +44,7 @@ def test_api_video_delete_detail_token_user(self): def test_api_video_delete_detail_student(self): """Student users should not be able to delete a video.""" video = factories.VideoFactory() - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) response = self.client.delete( f"/api/videos/{video.id}/", @@ -180,7 +180,7 @@ def test_api_video_instructor_delete_video_in_read_only(self): """An instructor with read_only set to true should not be able to delete the video.""" video = factories.VideoFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, permissions__can_update=False, ) diff --git a/src/backend/marsha/core/tests/api/video/test_harvest_live.py b/src/backend/marsha/core/tests/api/video/test_harvest_live.py index cd01e23b17..789434b265 100644 --- a/src/backend/marsha/core/tests/api/video/test_harvest_live.py +++ b/src/backend/marsha/core/tests/api/video/test_harvest_live.py @@ -180,7 +180,7 @@ def test_api_video_instructor_harvest_live_in_read_only(self): """An instructor with read_only set to true should not be able to harvest a live.""" video = factories.VideoFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, permissions__can_update=False, ) @@ -193,7 +193,7 @@ def test_api_video_instructor_harvest_live_in_read_only(self): def test_api_video_student_harvest_live(self): """A student should not be able to harvest a live.""" video = factories.VideoFactory() - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) response = self.client.post( f"/api/videos/{video.id}/harvest-live/", @@ -227,7 +227,7 @@ def test_api_video_instructor_harvest_idle_live(self): live_type=JITSI, ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.post( f"/api/videos/{video.id}/harvest-live/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}", @@ -280,7 +280,7 @@ def test_api_video_instructor_harvest_paused_live_recording_slice(self): }, ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) with mock.patch.object(timezone, "now", return_value=stop), mock.patch.object( api.video, "delete_aws_element_stack" @@ -431,7 +431,7 @@ def test_api_video_instructor_harvest_paused_live_no_recording_slice(self): }, ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) with mock.patch.object(timezone, "now", return_value=stop), mock.patch.object( api.video, "delete_aws_element_stack" @@ -495,7 +495,7 @@ def test_api_video_instructor_harvest_paused_live_missing_manifest(self): }, ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) with mock.patch.object( api.video, "delete_aws_element_stack" @@ -595,7 +595,7 @@ def test_api_video_instructor_harvest_live_wrong_live_state(self): ), live_type=JITSI, ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) with mock.patch( "marsha.websocket.utils.channel_layers_utils.dispatch_video_to_groups" diff --git a/src/backend/marsha/core/tests/api/video/test_initiate_live.py b/src/backend/marsha/core/tests/api/video/test_initiate_live.py index 707ac44c4e..70b84f1489 100644 --- a/src/backend/marsha/core/tests/api/video/test_initiate_live.py +++ b/src/backend/marsha/core/tests/api/video/test_initiate_live.py @@ -36,7 +36,7 @@ def test_api_video_instructor_initiate_live_in_read_only(self): """An instructor with read_only set to true should not be able to initiate a live.""" video = factories.VideoFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, permissions__can_update=False, ) @@ -49,7 +49,7 @@ def test_api_video_instructor_initiate_live_in_read_only(self): def test_api_video_student_initiate_live(self): """A student should not be able to initiate a live.""" video = factories.VideoFactory() - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) response = self.client.post( f"/api/videos/{video.id}/initiate-live/", @@ -82,7 +82,7 @@ def test_api_video_instructor_initiate_live(self): playlist__title="foo bar", playlist__lti_id="course-v1:ufr+mathematics+00001", ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) with mock.patch( "marsha.websocket.utils.channel_layers_utils.dispatch_video" ) as mock_dispatch_video: @@ -155,7 +155,9 @@ def test_api_video_instructor_initiate_live_with_playlist_token(self): playlist__title="foo bar", playlist__lti_id="course-v1:ufr+mathematics+00001", ) - jwt_token = PlaylistLtiTokenFactory(playlist=video.playlist) + jwt_token = PlaylistLtiTokenFactory( + resource=video.playlist, playlist=video.playlist + ) with mock.patch( "marsha.websocket.utils.channel_layers_utils.dispatch_video" ) as mock_dispatch_video: @@ -224,7 +226,7 @@ def test_api_video_instructor_initiate_jitsi_live(self): playlist__title="foo bar", playlist__lti_id="course-v1:ufr+mathematics+00001", ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) with mock.patch( "marsha.websocket.utils.channel_layers_utils.dispatch_video" @@ -305,7 +307,7 @@ def test_api_video_instructor_initiate_jitsi_live_with_token(self): playlist__title="foo bar", playlist__lti_id="course-v1:ufr+mathematics+00001", ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) now = datetime(2022, 5, 4, tzinfo=baseTimezone.utc) with mock.patch( diff --git a/src/backend/marsha/core/tests/api/video/test_initiate_upload.py b/src/backend/marsha/core/tests/api/video/test_initiate_upload.py index 39da5c04bd..083c637763 100644 --- a/src/backend/marsha/core/tests/api/video/test_initiate_upload.py +++ b/src/backend/marsha/core/tests/api/video/test_initiate_upload.py @@ -37,7 +37,7 @@ def test_api_video_instructor_initiate_upload_in_read_only(self): """An instructor with read_only set to true should not be able to initiate an upload.""" video = factories.VideoFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, permissions__can_update=False, ) @@ -54,7 +54,7 @@ def test_api_video_initiate_upload_token_user(self): id="27a23f52-3379-46a2-94fa-697b59cfe3c7", upload_state=random.choice(["ready", "error"]), ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) # Create another video to check that its upload state is unaffected other_video = factories.VideoFactory( @@ -374,7 +374,7 @@ def test_api_video_initiate_upload_file_without_size(self): id="27a23f52-3379-46a2-94fa-697b59cfe3c7", upload_state=random.choice(["ready", "error"]), ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) now = datetime(2018, 8, 8, tzinfo=baseTimezone.utc) with mock.patch.object(timezone, "now", return_value=now), mock.patch( @@ -399,7 +399,7 @@ def test_api_video_initiate_upload_file_too_large(self): id="27a23f52-3379-46a2-94fa-697b59cfe3c7", upload_state=random.choice(["ready", "error"]), ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) now = datetime(2018, 8, 8, tzinfo=baseTimezone.utc) with mock.patch.object(timezone, "now", return_value=now), mock.patch( diff --git a/src/backend/marsha/core/tests/api/video/test_jitsi_info.py b/src/backend/marsha/core/tests/api/video/test_jitsi_info.py index 98043b4b13..bc7a77b3f4 100644 --- a/src/backend/marsha/core/tests/api/video/test_jitsi_info.py +++ b/src/backend/marsha/core/tests/api/video/test_jitsi_info.py @@ -33,7 +33,7 @@ def test_jitsi_info_anonymous(self): def test_jitsi_info_for_a_student(self): """A student user can not fetch jitsi info.""" video = VideoFactory(live_state=RUNNING, live_type=JITSI) - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) response = self.client.get( f"/api/videos/{video.id}/jitsi/", @@ -46,7 +46,7 @@ def test_jitsi_info_for_an_admin(self): An instructor or an adminstrator with a resource token should be able to fetch jitsi info. """ video = VideoFactory(live_state=RUNNING, live_type=JITSI) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.get( f"/api/videos/{video.id}/jitsi/", @@ -75,7 +75,7 @@ def test_jitsi_info_for_an_admin_moderator_not_specified(self): When the moderator query string is not specified, the False value should be used. """ video = VideoFactory(live_state=RUNNING, live_type=JITSI) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.get( f"/api/videos/{video.id}/jitsi/", @@ -117,7 +117,7 @@ def test_jitsi_info_admin_user_moderator_false_specified(self): not be in the response. """ video = VideoFactory(live_state=RUNNING, live_type=JITSI) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.get( f"/api/videos/{video.id}/jitsi/?moderator=false", @@ -159,7 +159,7 @@ def test_jitsi_info_admin_user_moderator_true_specified(self): be in the response. """ video = VideoFactory(live_state=RUNNING, live_type=JITSI) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.get( f"/api/videos/{video.id}/jitsi/?moderator=true", @@ -277,7 +277,7 @@ def test_jitsi_info_on_non_live_video(self): """ video = VideoFactory(live_state=None, live_type=None) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.get( f"/api/videos/{video.id}/jitsi/", @@ -291,7 +291,7 @@ def test_jitsi_info_on_live_not_jitsi(self): """ video = VideoFactory(live_state=RUNNING, live_type=RAW) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.get( f"/api/videos/{video.id}/jitsi/", diff --git a/src/backend/marsha/core/tests/api/video/test_list.py b/src/backend/marsha/core/tests/api/video/test_list.py index 8a8ebb5ab6..4642eafb7a 100644 --- a/src/backend/marsha/core/tests/api/video/test_list.py +++ b/src/backend/marsha/core/tests/api/video/test_list.py @@ -31,7 +31,7 @@ def test_api_video_read_list_token_user(self): """ video = factories.VideoFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, permissions__can_update=False, ) diff --git a/src/backend/marsha/core/tests/api/video/test_live_pairing.py b/src/backend/marsha/core/tests/api/video/test_live_pairing.py index ce0dabc03b..964ae687b8 100644 --- a/src/backend/marsha/core/tests/api/video/test_live_pairing.py +++ b/src/backend/marsha/core/tests/api/video/test_live_pairing.py @@ -170,7 +170,7 @@ def test_pairing_secret_by_playlist_admin(self): def test_api_video_student_pairing_secret(self): """A student should not be able to request a live pairing secret.""" video = factories.VideoFactory() - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) response = self.client.get( f"/api/videos/{video.id}/pairing-secret/", @@ -199,7 +199,7 @@ def test_api_video_pairing_secret_staff_or_user(self): def test_api_video_instructor_pairing_secret_non_jitsi(self): """A request related to a non jitsi video should raise a 400 error.""" video = factories.VideoFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.get( f"/api/videos/{video.id}/pairing-secret/", @@ -215,7 +215,7 @@ def test_api_video_instructor_pairing_secret_non_jitsi(self): def test_api_video_instructor_pairing_secret_1st_request(self): """An instructor should be able to request a live pairing secret.""" video = factories.VideoFactory(live_state=IDLE, live_type=JITSI) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.get( f"/api/videos/{video.id}/pairing-secret/", @@ -235,7 +235,7 @@ def test_api_video_instructor_pairing_secret_2nd_request(self): live_pairing = LivePairingFactory(video=video) previous_secret = live_pairing.secret - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.get( f"/api/videos/{video.id}/pairing-secret/", @@ -256,7 +256,7 @@ def test_api_video_pairing_secret_delete_expired(self): ) with mock.patch.object(timezone, "now", return_value=expired_date): video = factories.VideoFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) self.client.get( f"/api/videos/{video.id}/pairing-secret/", diff --git a/src/backend/marsha/core/tests/api/video/test_live_participants_asking_to_join.py b/src/backend/marsha/core/tests/api/video/test_live_participants_asking_to_join.py index 1c6b0611e2..0b844e7fc3 100644 --- a/src/backend/marsha/core/tests/api/video/test_live_participants_asking_to_join.py +++ b/src/backend/marsha/core/tests/api/video/test_live_participants_asking_to_join.py @@ -218,7 +218,7 @@ def test_api_video_participants_post_asking_to_join_student(self): video = VideoFactory() jwt_token = StudentLtiTokenFactory( - resource=video, + resource=video.playlist, context_id=str(video.playlist.lti_id), consumer_site=str(video.playlist.consumer_site.id), ) @@ -267,7 +267,7 @@ def test_api_video_participants_post_asking_to_join_instructor(self): ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, user__id="56255f3807599c377bf0e5bf072359fd", ) @@ -322,7 +322,7 @@ def test_api_video_participants_post_asking_to_join_invalid_participants(self): video = VideoFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, user__id="56255f3807599c377bf0e5bf072359fd", ) @@ -361,7 +361,7 @@ def test_api_video_participants_post_asking_to_join_invalid_empty(self): video = VideoFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, user__id="56255f3807599c377bf0e5bf072359fd", ) @@ -399,7 +399,7 @@ def test_api_video_participants_post_asking_to_join_extra_data(self): video = VideoFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, user__id="56255f3807599c377bf0e5bf072359fd", ) @@ -458,7 +458,7 @@ def test_api_video_participants_post_asking_to_join_no_change(self): ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, user__id="56255f3807599c377bf0e5bf072359fd", ) @@ -496,7 +496,7 @@ def test_api_video_participants_post_asking_to_join_join_mode_denied(self): video = VideoFactory(join_mode=DENIED) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, user__id="56255f3807599c377bf0e5bf072359fd", ) @@ -543,7 +543,7 @@ def test_api_video_participants_delete_asking_to_join_student(self): video = VideoFactory() jwt_token = StudentLtiTokenFactory( - resource=video, + resource=video.playlist, context_id=str(video.playlist.lti_id), consumer_site=str(video.playlist.consumer_site.id), ) @@ -592,7 +592,7 @@ def test_api_video_participants_delete_asking_to_join_instructor(self): ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, user__id="56255f3807599c377bf0e5bf072359fd", ) @@ -639,7 +639,7 @@ def test_api_video_participants_delete_asking_to_join_invalid_participants(self) video = VideoFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, user__id="56255f3807599c377bf0e5bf072359fd", ) @@ -678,7 +678,7 @@ def test_api_video_participants_delete_asking_to_join_invalid_empty(self): video = VideoFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, user__id="56255f3807599c377bf0e5bf072359fd", ) @@ -723,7 +723,7 @@ def test_api_video_participants_delete_asking_to_join_extra_data(self): ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, user__id="56255f3807599c377bf0e5bf072359fd", ) @@ -777,7 +777,7 @@ def test_api_video_participants_delete_asking_to_join_no_change(self): ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, user__id="56255f3807599c377bf0e5bf072359fd", ) diff --git a/src/backend/marsha/core/tests/api/video/test_live_participants_joined.py b/src/backend/marsha/core/tests/api/video/test_live_participants_joined.py index fbaaf1c0fe..d6c8244ae1 100644 --- a/src/backend/marsha/core/tests/api/video/test_live_participants_joined.py +++ b/src/backend/marsha/core/tests/api/video/test_live_participants_joined.py @@ -224,7 +224,7 @@ def test_api_video_participants_post_joined_student(self): video = VideoFactory() jwt_token = StudentLtiTokenFactory( - resource=video, + resource=video.playlist, context_id=str(video.playlist.lti_id), consumer_site=str(video.playlist.consumer_site.id), ) @@ -273,7 +273,7 @@ def test_api_video_participants_post_joined_instructor(self): ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, user__id="56255f3807599c377bf0e5bf072359fd", ) @@ -325,7 +325,7 @@ def test_api_video_participants_post_joined_invalid_participants(self): video = VideoFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, user__id="56255f3807599c377bf0e5bf072359fd", ) @@ -376,7 +376,7 @@ def test_api_video_participants_post_joined_instructor_join_mode_denied(self): ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, user__id="56255f3807599c377bf0e5bf072359fd", ) @@ -423,7 +423,7 @@ def test_api_video_participants_delete_joined_student(self): video = VideoFactory() jwt_token = StudentLtiTokenFactory( - resource=video, + resource=video.playlist, context_id=str(video.playlist.lti_id), consumer_site=str(video.playlist.consumer_site.id), ) @@ -472,7 +472,7 @@ def test_api_video_participants_delete_joined_instructor(self): ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, user__id="56255f3807599c377bf0e5bf072359fd", ) @@ -519,7 +519,7 @@ def test_api_video_participants_delete_joined_invalid_participants(self): video = VideoFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, user__id="56255f3807599c377bf0e5bf072359fd", ) @@ -558,7 +558,7 @@ def test_api_video_participants_delete_joined_invalid_empty(self): video = VideoFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, user__id="56255f3807599c377bf0e5bf072359fd", ) @@ -603,7 +603,7 @@ def test_api_video_participants_delete_joined_extra_data(self): ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, user__id="56255f3807599c377bf0e5bf072359fd", ) @@ -657,7 +657,7 @@ def test_api_video_participants_delete_joined_no_change(self): ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, user__id="56255f3807599c377bf0e5bf072359fd", ) diff --git a/src/backend/marsha/core/tests/api/video/test_live_to_vod.py b/src/backend/marsha/core/tests/api/video/test_live_to_vod.py index 7ebb931448..3117549688 100644 --- a/src/backend/marsha/core/tests/api/video/test_live_to_vod.py +++ b/src/backend/marsha/core/tests/api/video/test_live_to_vod.py @@ -165,7 +165,7 @@ def test_api_video_instructor_harvested_live_to_vod(self): uploaded_on="2019-09-24 07:24:40+00", resolutions=[240, 480, 720], ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) with mock.patch( "marsha.websocket.utils.channel_layers_utils.dispatch_video_to_groups" @@ -277,7 +277,7 @@ def test_api_video_instructor_non_harvested_live_to_vod(self): uploaded_on="2019-09-24 07:24:40+00", resolutions=[240, 480, 720], ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) with mock.patch( "marsha.websocket.utils.channel_layers_utils.dispatch_video_to_groups" diff --git a/src/backend/marsha/core/tests/api/video/test_metadata.py b/src/backend/marsha/core/tests/api/video/test_metadata.py index e0499a843d..5ef34af42d 100644 --- a/src/backend/marsha/core/tests/api/video/test_metadata.py +++ b/src/backend/marsha/core/tests/api/video/test_metadata.py @@ -20,7 +20,7 @@ def test_api_video_options_as_student(self): """A student can fetch the video options endpoint""" video = VideoFactory() - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) response = self.client.options( "/api/videos/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}" ) @@ -61,7 +61,7 @@ def test_api_video_options_as_instructor(self): video = VideoFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, permissions__can_update=False, ) diff --git a/src/backend/marsha/core/tests/api/video/test_retrieve.py b/src/backend/marsha/core/tests/api/video/test_retrieve.py index e6e2518715..c5a75d86ed 100644 --- a/src/backend/marsha/core/tests/api/video/test_retrieve.py +++ b/src/backend/marsha/core/tests/api/video/test_retrieve.py @@ -48,7 +48,7 @@ def test_api_video_read_detail_anonymous(self): def test_api_video_read_detail_student(self): """Student users should be allowed to read a video detail.""" video = factories.VideoFactory() - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) # Get the video linked to the JWT token response = self.client.get( f"/api/videos/{video.id}/", @@ -63,7 +63,7 @@ def test_api_video_read_detail_scheduled_video_student(self): live_state=IDLE, live_type=RAW, starting_at=starting_at ) self.assertTrue(video.is_scheduled) - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) # Get the video linked to the JWT token response = self.client.get( f"/api/videos/{video.id}/", @@ -83,7 +83,7 @@ def test_api_video_read_detail_scheduled_past_video_student(self): with mock.patch.object(timezone, "now", return_value=now): self.assertFalse(video.is_scheduled) self.assertEqual(video.live_state, IDLE) - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) # Get the video linked to the JWT token response = self.client.get( f"/api/videos/{video.id}/", @@ -95,7 +95,7 @@ def test_api_video_read_detail_student_other_video(self): """Student users should not be allowed to read an other video detail.""" video = factories.VideoFactory() other_video = factories.VideoFactory() - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) # Get the video linked to the JWT token response = self.client.get( f"/api/videos/{other_video.id}/", @@ -111,7 +111,7 @@ def test_api_video_read_detail_student_join_modes(self): live_state=RUNNING, live_type=JITSI, ) - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) # Get the video linked to the JWT token response = self.client.get( f"/api/videos/{video.id}/", @@ -126,7 +126,7 @@ def test_api_video_read_detail_admin_token_user(self): video = factories.VideoFactory(upload_state="pending") jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, roles=["administrator"], ) @@ -173,7 +173,7 @@ def test_api_video_read_detail_token_user(self): video=video, ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) # Get the video linked to the JWT token response = self.client.get( @@ -369,7 +369,7 @@ def test_api_video_read_detail_token_user_nested_shared_live_media_urls_signed( video=video, ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) # fix the time so that the url signature is deterministic and can be checked now = datetime(2021, 11, 30, tzinfo=baseTimezone.utc) @@ -588,7 +588,7 @@ def test_api_video_read_detail_token_student_user_nested_shared_live_media_urls_ video=video, ) - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) # fix the time so that the url signature is deterministic and can be checked now = datetime(2021, 11, 30, tzinfo=baseTimezone.utc) @@ -779,7 +779,7 @@ def test_api_video_read_detail_xmpp_enabled_live_state_idle(self): ) jwt_token = StudentLtiTokenFactory( - resource=video, + resource=video.playlist, context_id="Maths", consumer_site=str(video.playlist.consumer_site.id), ) @@ -842,7 +842,7 @@ def test_api_video_read_detail_as_instructor_in_read_only(self): video = factories.VideoFactory(upload_state="ready") jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, permissions__can_update=False, ) @@ -861,7 +861,7 @@ def test_api_video_read_detail_token_user_no_active_stamp(self): playlist__title="foo bar", playlist__lti_id="course-v1:ufr+mathematics+00001", ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) # Get the video linked to the JWT token response = self.client.get( @@ -929,7 +929,7 @@ def test_api_video_read_detail_token_user_not_sucessfully_uploaded(self): playlist__title="foo bar", playlist__lti_id="course-v1:ufr+mathematics+00001", ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) # Get the video linked to the JWT token response = self.client.get( @@ -1000,7 +1000,7 @@ def test_api_video_read_detail_token_user_signed_urls(self, _mock_open): resolutions=[144], playlist__title="foo", ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) # Get the video linked to the JWT token # fix the time so that the url signature is deterministic and can be checked @@ -1275,7 +1275,7 @@ def test_api_video_with_a_thumbnail(self): upload_state="ready", ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) # Get the video linked to the JWT token response = self.client.get( diff --git a/src/backend/marsha/core/tests/api/video/test_shared_live_media.py b/src/backend/marsha/core/tests/api/video/test_shared_live_media.py index 36d1afed19..e92283c357 100644 --- a/src/backend/marsha/core/tests/api/video/test_shared_live_media.py +++ b/src/backend/marsha/core/tests/api/video/test_shared_live_media.py @@ -547,7 +547,7 @@ def test_api_video_shared_live_media_start_student(self): shared_live_media = SharedLiveMediaFactory() jwt_token = StudentLtiTokenFactory( - resource=shared_live_media.video, + resource=shared_live_media.video.playlist, context_id=str(shared_live_media.video.playlist.lti_id), consumer_site=str(shared_live_media.video.playlist.consumer_site.id), ) @@ -569,7 +569,7 @@ def test_api_video_shared_live_media_navigate_student(self): shared_live_media = SharedLiveMediaFactory() jwt_token = StudentLtiTokenFactory( - resource=shared_live_media.video, + resource=shared_live_media.video.playlist, ) response = self.client.patch( @@ -589,7 +589,7 @@ def test_api_video_shared_live_media_end_student(self): shared_live_media = SharedLiveMediaFactory() jwt_token = StudentLtiTokenFactory( - resource=shared_live_media.video, + resource=shared_live_media.video.playlist, ) response = self.client.patch( @@ -692,7 +692,7 @@ def test_api_video_shared_live_media_start_instructor_ready(self): ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=shared_live_media.video, + resource=shared_live_media.video.playlist, ) with mock.patch.object( channel_layers_utils, "dispatch_video_to_groups" @@ -906,7 +906,7 @@ def test_api_video_shared_live_media_start_not_ready(self): ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=shared_live_media.video, + resource=shared_live_media.video.playlist, ) with mock.patch.object( @@ -959,7 +959,7 @@ def test_api_video_shared_live_media_start_wrong_sharedlivemedia_id(self): other_shared_live_media = SharedLiveMediaFactory(upload_state=READY) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=shared_live_media.video, + resource=shared_live_media.video.playlist, ) with mock.patch.object( @@ -1016,7 +1016,7 @@ def test_api_video_shared_live_media_start_already_started(self): shared_live_media = SharedLiveMediaFactory(video=video) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=shared_live_media.video, + resource=shared_live_media.video.playlist, ) with mock.patch.object( @@ -1075,7 +1075,7 @@ def test_api_video_shared_live_media_navigate_instructor(self): video.shared_live_medias.set([shared_live_media]) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=shared_live_media.video, + resource=shared_live_media.video.playlist, ) with mock.patch.object( @@ -1234,7 +1234,7 @@ def test_api_video_shared_live_media_navigate_no_active(self): ) video.shared_live_medias.set([shared_live_media]) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) with mock.patch.object( channel_layers_utils, "dispatch_video_to_groups" @@ -1295,7 +1295,7 @@ def test_api_video_shared_live_media_navigate_unexisting_page(self): video.shared_live_medias.set([shared_live_media]) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=shared_live_media.video, + resource=shared_live_media.video.playlist, ) with mock.patch.object( @@ -1357,7 +1357,7 @@ def test_api_video_shared_live_media_navigate_undefined_page(self): video.shared_live_medias.set([shared_live_media]) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=shared_live_media.video, + resource=shared_live_media.video.playlist, ) with mock.patch.object( @@ -1419,7 +1419,7 @@ def test_api_video_shared_live_media_navigate_missing_page(self): video.shared_live_medias.set([shared_live_media]) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=shared_live_media.video, + resource=shared_live_media.video.playlist, ) with mock.patch.object( @@ -1480,7 +1480,7 @@ def test_api_video_shared_live_media_end_instructor(self): video.shared_live_medias.set([shared_live_media]) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=shared_live_media.video, + resource=shared_live_media.video.playlist, ) with mock.patch.object( @@ -1626,7 +1626,7 @@ def test_api_video_shared_live_media_end_no_active(self): ) video.shared_live_medias.set([shared_live_media]) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) with mock.patch.object( channel_layers_utils, "dispatch_video_to_groups" diff --git a/src/backend/marsha/core/tests/api/video/test_start_live.py b/src/backend/marsha/core/tests/api/video/test_start_live.py index 6e642c66aa..fd30440d3a 100644 --- a/src/backend/marsha/core/tests/api/video/test_start_live.py +++ b/src/backend/marsha/core/tests/api/video/test_start_live.py @@ -203,7 +203,7 @@ def test_api_instructor_start_non_created_live(self): ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, user__id="56255f3807599c377bf0e5bf072359fd", ) @@ -372,7 +372,7 @@ def test_api_instructor_start_already_created_live(self): ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, user__id="56255f3807599c377bf0e5bf072359fd", ) @@ -527,7 +527,7 @@ def test_api_instructor_start_stopped_live(self): ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, user__id="56255f3807599c377bf0e5bf072359fd", ) @@ -686,7 +686,7 @@ def test_api_instructor_start_harvested_live(self): ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, user__id="56255f3807599c377bf0e5bf072359fd", ) @@ -867,7 +867,7 @@ def test_api_instructor_start_non_live_video(self): id="27a23f52-3379-46a2-94fa-697b59cfe3c7", upload_state=random.choice([s[0] for s in STATE_CHOICES]), ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) # start a live video, with mock.patch.object(api.video, "start_live_channel"), mock.patch( @@ -894,7 +894,7 @@ def test_api_instructor_start_aws_raising_exception(self): ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, user__id="56255f3807599c377bf0e5bf072359fd", ) @@ -944,7 +944,7 @@ def test_api_instructor_start_non_idle_or_stopped_live(self): ), live_type=RAW, ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) with mock.patch( "marsha.websocket.utils.channel_layers_utils.dispatch_video_to_groups" diff --git a/src/backend/marsha/core/tests/api/video/test_start_stop_recording.py b/src/backend/marsha/core/tests/api/video/test_start_stop_recording.py index be78a17c7c..b734f6d921 100644 --- a/src/backend/marsha/core/tests/api/video/test_start_stop_recording.py +++ b/src/backend/marsha/core/tests/api/video/test_start_stop_recording.py @@ -319,7 +319,7 @@ def test_api_video_recording_start_student(self): video = VideoFactory() jwt_token = StudentLtiTokenFactory( - resource=video, + resource=video.playlist, context_id=str(video.playlist.lti_id), consumer_site=str(video.playlist.consumer_site.id), ) @@ -341,7 +341,7 @@ def test_api_video_recording_stop_student(self): video = VideoFactory() jwt_token = StudentLtiTokenFactory( - resource=video, + resource=video.playlist, context_id=str(video.playlist.lti_id), consumer_site=str(video.playlist.consumer_site.id), ) @@ -419,7 +419,7 @@ def test_api_video_recording_start_instructor(self): self.assertEqual(video.recording_slices, []) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, user__id="56255f3807599c377bf0e5bf072359fd", ) @@ -553,7 +553,7 @@ def test_api_video_recording_start_not_allowed(self): self.assertEqual(video.recording_slices, []) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, user__id="56255f3807599c377bf0e5bf072359fd", ) @@ -618,7 +618,7 @@ def test_api_video_recording_start_live_not_running(self): self.assertEqual(video.recording_slices, []) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, user__id="56255f3807599c377bf0e5bf072359fd", ) @@ -685,7 +685,7 @@ def test_api_video_recording_stop_instructor_started_slice(self): self.assertEqual(video.recording_slices, [{"start": to_timestamp(start)}]) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, user__id="56255f3807599c377bf0e5bf072359fd", ) diff --git a/src/backend/marsha/core/tests/api/video/test_stats.py b/src/backend/marsha/core/tests/api/video/test_stats.py index 59846a85ff..78de6c658f 100644 --- a/src/backend/marsha/core/tests/api/video/test_stats.py +++ b/src/backend/marsha/core/tests/api/video/test_stats.py @@ -218,7 +218,7 @@ def test_api_video_stats_instructor(self): video = VideoFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, ) with mock.patch( diff --git a/src/backend/marsha/core/tests/api/video/test_stop_live.py b/src/backend/marsha/core/tests/api/video/test_stop_live.py index 678a6d0a72..2b4076757a 100644 --- a/src/backend/marsha/core/tests/api/video/test_stop_live.py +++ b/src/backend/marsha/core/tests/api/video/test_stop_live.py @@ -178,7 +178,7 @@ def test_api_video_instructor_stop_live_in_read_only(self): """An instructor with read_only set to true should not be able to stop a live.""" video = factories.VideoFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, permissions__can_update=False, ) @@ -191,7 +191,7 @@ def test_api_video_instructor_stop_live_in_read_only(self): def test_api_video_student_stop_live(self): """A student should not be able to stop a live.""" video = factories.VideoFactory() - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) response = self.client.post( f"/api/videos/{video.id}/stop-live/", @@ -249,7 +249,7 @@ def test_api_video_instructor_stop_live(self): }, live_type=RAW, ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) # stop a live video, now = datetime(2021, 11, 16, tzinfo=baseTimezone.utc) @@ -334,7 +334,7 @@ def test_api_instructor_stop_non_live_video(self): id="27a23f52-3379-46a2-94fa-697b59cfe3c7", upload_state=random.choice([s[0] for s in STATE_CHOICES]), ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) # stop a live video, with mock.patch.object( @@ -359,7 +359,7 @@ def test_api_instructor_stop_non_running_live(self): live_state=random.choice([s[0] for s in LIVE_CHOICES if s[0] != "running"]), live_type=RAW, ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) # stop a live video, with mock.patch.object( @@ -410,7 +410,7 @@ def test_api_video_instructor_stop_live_recording_slice(self): }, live_type=RAW, ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) # stop a live video, with mock.patch.object(timezone, "now", return_value=stop), mock.patch.object( diff --git a/src/backend/marsha/core/tests/api/video/test_update.py b/src/backend/marsha/core/tests/api/video/test_update.py index f7be09af35..2fd877a773 100644 --- a/src/backend/marsha/core/tests/api/video/test_update.py +++ b/src/backend/marsha/core/tests/api/video/test_update.py @@ -52,7 +52,7 @@ def test_api_video_update_detail_anonymous(self): def test_api_video_update_detail_student(self): """Student users should not be allowed to update a video through the API.""" video = factories.VideoFactory(title="my title") - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) data = {"title": "my new title"} response = self.client.put( @@ -72,7 +72,7 @@ def test_api_video_update_detail_student(self): def test_api_video_update_detail_token_user_title(self): """Token users should be able to update the title of their video through the API.""" video = factories.VideoFactory(title="my title") - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) data = {"title": "my new title"} response = self.client.put( f"/api/videos/{video.id}/", @@ -87,7 +87,7 @@ def test_api_video_update_detail_token_user_title(self): def test_api_video_update_detail_token_user_title_null(self): """Token users can not set a null title.""" video = factories.VideoFactory(title="my title") - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) data = {"title": None} response = self.client.put( f"/api/videos/{video.id}/", @@ -103,7 +103,7 @@ def test_api_video_update_detail_token_user_title_null(self): def test_api_video_update_detail_token_user_title_empty(self): """Token users can not set an empty title.""" video = factories.VideoFactory(title="my title") - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) data = {"title": " "} response = self.client.put( f"/api/videos/{video.id}/", @@ -128,7 +128,7 @@ def test_api_video_update_detail_token_scheduled_date_future(self): live_state=IDLE, live_type=JITSI, ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) # set microseconds to 0 to compare date surely as serializer truncate them starting_at = (timezone.now() + timedelta(hours=1)).replace(microsecond=0) data = { @@ -220,7 +220,7 @@ def test_api_video_update_detail_token_scheduled_date_future_live_session(self): should_send_reminders=True, video=video, ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) # we only change the title response = self.client.put( f"/api/videos/{video.id}/", @@ -292,7 +292,7 @@ def test_api_video_update_detail_token_scheduled_date_past(self): title="my title", ) self.assertTrue(video.is_scheduled) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) # try to set a date in the past # set microseconds to 0 to compare date surely as serializer truncate them @@ -330,7 +330,7 @@ def test_api_video_update_detail_token_scheduled_date_to_none(self): live_state=IDLE, live_type=JITSI, starting_at=starting_at, title="my title" ) self.assertTrue(video.is_scheduled) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) data = {"title": "title required", "starting_at": None} response = self.client.put( @@ -352,7 +352,7 @@ def test_api_video_update_detail_token_scheduled_with_previous_starting_at_alrea video = factories.VideoFactory( live_state=IDLE, live_type=RAW, starting_at=intial_starting_at ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) # now is set to after initial starting_at now = intial_starting_at + timedelta(days=10) with mock.patch.object(timezone, "now", return_value=now): @@ -382,7 +382,7 @@ def test_api_video_update_detail_token_scheduled_with_previous_starting_at_alrea def test_api_video_update_detail_token_user_description(self): """Token users should be able to update the description of their video through the API.""" video = factories.VideoFactory(description="my description") - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.get( f"/api/videos/{video.id}/", @@ -403,7 +403,7 @@ def test_api_video_update_detail_token_user_description(self): def test_api_video_update_detail_token_user_uploaded_on(self): """Token users trying to update "uploaded_on" through the API should be ignored.""" video = factories.VideoFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.get( f"/api/videos/{video.id}/", @@ -438,7 +438,7 @@ def test_api_video_update_detail_token_user_upload_state(self): uploaded_on="2019-09-24 07:24:40+00", resolutions=[240, 480, 720], ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.get( f"/api/videos/{video.id}/", @@ -540,7 +540,7 @@ def test_api_video_update_detail_token_user_join_mode(self): description="my description", join_mode=APPROVAL, ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.get( f"/api/videos/{video.id}/", @@ -564,7 +564,7 @@ def test_api_video_instructor_update_video_in_read_only(self): """An instructor with read_only set to true should not be able to update the video.""" video = factories.VideoFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, permissions__can_update=False, ) @@ -594,7 +594,7 @@ def test_api_video_patch_video_anonymous(self): def test_api_video_patch_video_student(self): """Student users should not be allowed to patch a video through the API.""" video = factories.VideoFactory(title="my title") - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) data = {"title": "my new title"} response = self.client.patch( @@ -615,7 +615,7 @@ def test_api_video_instructor_patch_video_in_read_only(self): """An instructor with read_only set to true should not be able to patch the video.""" video = factories.VideoFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, permissions__can_update=False, ) @@ -635,7 +635,7 @@ def test_api_video_patch_detail_token_user_stamp(self): this field can only be updated by AWS via the separate update-state API endpoint. """ video = factories.VideoFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) self.assertIsNone(video.uploaded_on) data = {"active_stamp": "1533686400"} @@ -655,7 +655,7 @@ def test_api_video_update_detail_token_user_id(self): """Token users trying to update the ID of a video they own should be ignored.""" video = factories.VideoFactory() original_id = video.id - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.get( f"/api/videos/{video.id}/", @@ -678,7 +678,7 @@ def test_api_video_update_detail_token_user_other_video(self): """Token users should not be allowed to update another video through the API.""" video_token = factories.VideoFactory() video_update = factories.VideoFactory(title="my title") - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video_token) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video_token.playlist) data = {"title": "my new title"} response = self.client.put( @@ -695,7 +695,7 @@ def test_api_video_update_detail_token_user_other_video(self): def test_api_video_patch_detail_token_user_description(self): """Token users should be able to patch fields on their video through the API.""" video = factories.VideoFactory(description="my description") - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) data = {"description": "my new description"} @@ -713,7 +713,7 @@ def test_api_video_patch_detail_token_user_is_public(self): """Instructors and administrators should be able to patch the public flag of their video through the API.""" video = factories.VideoFactory(is_public=False) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) data = {"is_public": True} response = self.client.patch( f"/api/videos/{video.id}/", @@ -731,7 +731,7 @@ def test_api_video_patch_detail_token_user_allow_recording(self): video = factories.VideoFactory() self.assertTrue(video.allow_recording) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) data = {"allow_recording": False} response = self.client.patch( f"/api/videos/{video.id}/", @@ -749,7 +749,7 @@ def test_api_video_patch_detail_token_user_tags(self): video = factories.VideoFactory() self.assertEqual(video.tags, []) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) data = {"tags": ["foo", "bar"]} response = self.client.patch( f"/api/videos/{video.id}/", @@ -767,7 +767,7 @@ def test_api_video_patch_detail_token_user_license(self): video = factories.VideoFactory() self.assertIsNone(video.license) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) data = {"license": CC_BY_SA} response = self.client.patch( f"/api/videos/{video.id}/", @@ -785,7 +785,7 @@ def test_api_video_patch_detail_token_user_estimated_duration(self): video = factories.VideoFactory() self.assertIsNone(video.estimated_duration) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) estimated_duration = timedelta(seconds=2100) data = {"estimated_duration": estimated_duration} response = self.client.patch( @@ -804,7 +804,7 @@ def test_api_video_patch_detail_token_user_estimated_duration_negative(self): video = factories.VideoFactory() self.assertIsNone(video.estimated_duration) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) # from -7 days to -1 second estimated_duration = timedelta(seconds=random.randint(-604800, -1)) data = {"estimated_duration": estimated_duration} @@ -833,7 +833,7 @@ def test_api_video_patch_detail_token_user_estimated_duration_negative_one_secon video = factories.VideoFactory() self.assertIsNone(video.estimated_duration) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) estimated_duration = timedelta(seconds=-1) data = {"estimated_duration": estimated_duration} @@ -861,7 +861,7 @@ def test_api_video_patch_detail_token_user_has_chat(self): video = factories.VideoFactory() self.assertTrue(video.has_chat) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) data = {"has_chat": False} response = self.client.patch( f"/api/videos/{video.id}/", @@ -879,7 +879,7 @@ def test_api_video_patch_detail_token_user_has_live_media(self): video = factories.VideoFactory() self.assertTrue(video.has_live_media) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) data = {"has_live_media": False} response = self.client.patch( f"/api/videos/{video.id}/", @@ -928,7 +928,7 @@ def test_api_video_patch_by_instructor_scheduling_date_future(self): # starting_at is None there is no event scheduled self.assertFalse(video.is_scheduled) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) # starting_at gets updated to a date in the future # set microseconds to 0 to compare date surely as serializer truncate them @@ -991,7 +991,7 @@ def test_api_patch_video_with_previous_starting_at_already_past(self): video = factories.VideoFactory( live_state=IDLE, live_type=RAW, starting_at=intial_starting_at ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) # now is set after video.starting_at now = intial_starting_at + timedelta(days=10) with mock.patch.object(timezone, "now", return_value=now): @@ -1028,7 +1028,7 @@ def test_api_patch_video_with_live_state_set(self): ) self.assertFalse(video.is_scheduled) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) starting_at = timezone.now() + timedelta(days=10) response = self.client.patch( @@ -1221,7 +1221,7 @@ def test_api_update_video_with_live_state_set(self): ) self.assertFalse(video.is_scheduled) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) starting_at = timezone.now() + timedelta(hours=1) response = self.client.put( f"/api/videos/{video.id}/", @@ -1259,7 +1259,7 @@ def test_api_update_video_with_starting_at_past(self): self.assertTrue(video.starting_at > timezone.now()) self.assertTrue(video.is_scheduled) self.assertEqual(video.live_state, IDLE) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) # Mock now to the future to check video gets set to not scheduled future = timezone.now() + timedelta(hours=1) with mock.patch.object(timezone, "now", return_value=future): diff --git a/src/backend/marsha/core/tests/management_commands/test_send_reminders.py b/src/backend/marsha/core/tests/management_commands/test_send_reminders.py index 1a10e1de0c..8af0693ec2 100644 --- a/src/backend/marsha/core/tests/management_commands/test_send_reminders.py +++ b/src/backend/marsha/core/tests/management_commands/test_send_reminders.py @@ -1201,7 +1201,7 @@ def test_scenario_video_date_has_changed(self): video = VideoFactory( title="my title", live_state=IDLE, live_type=JITSI, starting_at=None ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) self.assertFalse(video.is_scheduled) livesession = LiveSessionFactory( anonymous_id=uuid.uuid4(), From 12b9578596243fe948145ba3fe56d9a569cfeda0 Mon Sep 17 00:00:00 2001 From: Nicolas Clerc Date: Thu, 20 Jul 2023 16:56:46 +0200 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=9B=82(backend)=20use=20playlist=20to?= =?UTF-8?q?ken=20in=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. --- src/backend/marsha/core/api/file.py | 4 +- src/backend/marsha/core/api/live_session.py | 2 +- .../marsha/core/api/shared_live_media.py | 2 +- src/backend/marsha/core/api/thumbnail.py | 2 +- src/backend/marsha/core/api/xapi.py | 3 +- src/backend/marsha/core/permissions/token.py | 33 +++++++------ .../marsha/core/services/live_session.py | 4 +- .../marsha/core/simple_jwt/factories.py | 33 ++++++++++--- src/backend/marsha/core/simple_jwt/tokens.py | 2 +- .../tests/api/live_sessions/test_create.py | 46 +++++++++---------- .../api/live_sessions/test_display_name.py | 20 ++++---- .../core/tests/api/live_sessions/test_list.py | 10 ++-- .../live_sessions/test_list_attendances.py | 34 +++++++------- .../api/live_sessions/test_push_attendance.py | 16 +++---- .../tests/api/live_sessions/test_retrieve.py | 20 ++++---- .../tests/api/live_sessions/test_update.py | 14 +++--- .../tests/api/playlists/test_is_claimed.py | 8 ++-- .../core/tests/api/playlists/test_retrieve.py | 4 +- .../core/tests/api/playlists/test_update.py | 6 +-- .../api/portability_requests/test_create.py | 10 ++-- .../api/shared_live_media/test_create.py | 6 +-- .../api/shared_live_media/test_delete.py | 10 ++-- .../shared_live_media/test_initiate_upload.py | 18 ++++++-- .../tests/api/shared_live_media/test_list.py | 8 ++-- .../api/shared_live_media/test_retrieve.py | 16 +++---- .../api/shared_live_media/test_update.py | 6 ++- .../core/tests/api/thumbnails/test_create.py | 12 ++--- .../core/tests/api/thumbnails/test_delete.py | 8 ++-- .../api/thumbnails/test_initiate_upload.py | 8 ++-- .../core/tests/api/thumbnails/test_options.py | 4 +- .../tests/api/thumbnails/test_retrieve.py | 12 ++--- .../api/timed_text_tracks/test_create.py | 8 ++-- .../api/timed_text_tracks/test_delete.py | 4 +- .../timed_text_tracks/test_initiate_upload.py | 8 ++-- .../tests/api/timed_text_tracks/test_list.py | 2 +- .../api/timed_text_tracks/test_options.py | 10 ++-- .../api/timed_text_tracks/test_retrieve.py | 26 +++++++---- .../api/timed_text_tracks/test_update.py | 32 +++++++++---- .../core/tests/api/video/test_live_pairing.py | 12 +++-- .../tests/api/video/test_shared_live_media.py | 2 +- .../marsha/core/tests/api/video/test_stats.py | 2 +- .../core/tests/simple_jwt/test_tokens.py | 6 ++- .../marsha/core/tests/test_api_document.py | 36 +++++++-------- .../tests/test_api_playlist_portability.py | 8 ++-- .../core/tests/test_api_xapi_statement.py | 14 +++--- .../marsha/core/tests/views/test_lti_cache.py | 6 +-- .../core/tests/views/test_public_video.py | 4 +- .../marsha/websocket/consumers/video.py | 15 ++++-- .../websocket/tests/test_consumers_video.py | 14 +++--- 49 files changed, 337 insertions(+), 253 deletions(-) diff --git a/src/backend/marsha/core/api/file.py b/src/backend/marsha/core/api/file.py index 1cd8bfe3b0..376d8966a7 100644 --- a/src/backend/marsha/core/api/file.py +++ b/src/backend/marsha/core/api/file.py @@ -26,8 +26,8 @@ class DocumentViewSet( queryset = Document.objects.all() serializer_class = serializers.DocumentSerializer permission_classes = [ - permissions.IsTokenResourceRouteObject & permissions.IsTokenInstructor - | permissions.IsTokenResourceRouteObject & permissions.IsTokenAdmin + permissions.IsPlaylistToken + & (permissions.IsTokenInstructor | permissions.IsTokenAdmin) ] def get_permissions(self): diff --git a/src/backend/marsha/core/api/live_session.py b/src/backend/marsha/core/api/live_session.py index dad783403f..a9423389c9 100644 --- a/src/backend/marsha/core/api/live_session.py +++ b/src/backend/marsha/core/api/live_session.py @@ -296,7 +296,7 @@ def push_attendance(self, request, video_id=None): ): # LTI context token = self.request.resource.token token_user = self.request.resource.user - livesession, _ = get_livesession_from_lti(token) + livesession, _ = get_livesession_from_lti(token, video_id) # Update username only if defined in the token user if token_user.get("username"): diff --git a/src/backend/marsha/core/api/shared_live_media.py b/src/backend/marsha/core/api/shared_live_media.py index dbd6b84c7a..361398b493 100644 --- a/src/backend/marsha/core/api/shared_live_media.py +++ b/src/backend/marsha/core/api/shared_live_media.py @@ -107,7 +107,7 @@ def get_queryset(self): ) if self.request.resource: queryset = queryset.filter( - video__id=self.request.resource.id, + video__playlist__id=self.request.resource.id, ) return queryset diff --git a/src/backend/marsha/core/api/thumbnail.py b/src/backend/marsha/core/api/thumbnail.py index ef74b8ed40..59ef6c6cfa 100644 --- a/src/backend/marsha/core/api/thumbnail.py +++ b/src/backend/marsha/core/api/thumbnail.py @@ -69,7 +69,7 @@ def get_permissions(self): ] elif self.action in ["retrieve", "destroy", "initiate_upload"]: permission_classes = [ - permissions.IsTokenResourceRouteObjectRelatedVideo + permissions.IsPlaylistToken & (permissions.IsTokenInstructor | permissions.IsTokenAdmin) | permissions.IsRelatedVideoPlaylistAdminOrInstructor | permissions.IsRelatedVideoOrganizationAdmin diff --git a/src/backend/marsha/core/api/xapi.py b/src/backend/marsha/core/api/xapi.py index 00b9458809..06bb8eb6de 100644 --- a/src/backend/marsha/core/api/xapi.py +++ b/src/backend/marsha/core/api/xapi.py @@ -107,9 +107,8 @@ def post(self, request, resource_kind, resource_id): return Response(partial_xapi_statement.errors, status=400) if request.resource: - if request.resource.resource_id != str(resource_id): + if request.resource.resource_id != str(object_instance.playlist.id): return HttpResponseNotFound() - ( statement, lrs_url, diff --git a/src/backend/marsha/core/permissions/token.py b/src/backend/marsha/core/permissions/token.py index 91c7beb1d6..2178b0a7a0 100644 --- a/src/backend/marsha/core/permissions/token.py +++ b/src/backend/marsha/core/permissions/token.py @@ -1,6 +1,5 @@ """Custom permission classes for the Marsha project.""" -from django.core.exceptions import ObjectDoesNotExist -from django.db.models import Q +from django.core.exceptions import FieldError, ObjectDoesNotExist from rest_framework import permissions @@ -116,13 +115,8 @@ def has_permission(self, request, view): """ return ( request.resource - and models.Playlist.objects.filter( - Q(pk=view.get_object_pk()) - & ( - Q(videos__id=request.resource.id) - | Q(documents__id=request.resource.id) - ), - ).exists() + and request.resource.id == view.get_object_pk() + and models.Playlist.objects.filter(id=request.resource.id).exists() ) @@ -153,7 +147,8 @@ def has_permission(self, request, view): try: return ( request.resource - and str(view.get_related_object().video.id) == request.resource.id + and str(view.get_related_object().video.playlist.id) + == request.resource.id ) except ObjectDoesNotExist: return False @@ -174,10 +169,20 @@ def has_permission(self, request, view): """ if request.resource: playlist_id = request.resource.id - return models.Playlist.objects.filter(id=playlist_id).exists() and ( - str(view.get_queryset().get(id=view.get_object_pk()).playlist_id) - == playlist_id - ) + playlist_exists = models.Playlist.objects.filter(id=playlist_id).exists() + try: + return ( + playlist_exists + and view.get_queryset() + .filter(id=view.get_object_pk(), playlist__id=playlist_id) + .exists() + ) + except FieldError: + return playlist_exists and ( + view.get_queryset() + .filter(id=view.get_object_pk(), video__playlist__id=playlist_id) + .exists() + ) return False diff --git a/src/backend/marsha/core/services/live_session.py b/src/backend/marsha/core/services/live_session.py index d76b3abc2b..e7bf12db31 100644 --- a/src/backend/marsha/core/services/live_session.py +++ b/src/backend/marsha/core/services/live_session.py @@ -33,12 +33,12 @@ def is_public_token(token): ) -def get_livesession_from_lti(token): +def get_livesession_from_lti(token, video_id=None): """Get or create livesession for a LTI connection.""" if not is_lti_token(token): raise NotLtiTokenException() - video = Video.objects.get(pk=token.payload["resource_id"]) + video = Video.objects.get(pk=video_id) token_user = token.payload.get("user") consumer_site = ConsumerSite.objects.get(pk=token.payload["consumer_site"]) diff --git a/src/backend/marsha/core/simple_jwt/factories.py b/src/backend/marsha/core/simple_jwt/factories.py index b77d0152a6..2dea956228 100644 --- a/src/backend/marsha/core/simple_jwt/factories.py +++ b/src/backend/marsha/core/simple_jwt/factories.py @@ -6,6 +6,7 @@ import factory from faker.utils.text import slugify +from rest_framework_simplejwt.exceptions import TokenError from marsha.core.factories import ( ConsumerSiteFactory, @@ -13,7 +14,14 @@ PlaylistFactory, UserFactory, ) -from marsha.core.models import ADMINISTRATOR, INSTRUCTOR, LTI_ROLES, NONE, STUDENT +from marsha.core.models import ( + ADMINISTRATOR, + INSTRUCTOR, + LTI_ROLES, + NONE, + STUDENT, + Playlist, +) from marsha.core.simple_jwt.permissions import ResourceAccessPermissions from marsha.core.simple_jwt.tokens import ( ChallengeToken, @@ -128,13 +136,24 @@ def _build(cls, model_class, *args, **kwargs): class BaseResourceTokenFactory(BaseTokenFactory): """ Base class for all resource token factories. - This allows to provide a resource (video, document, ...) - to forge the JWT, or nothing to use a random UUID. + This forces to provide a playlist to forge the JWT, + or nothing to use a random UUID. """ - resource_id = factory.LazyAttribute( - lambda o: str(o.resource.id if o.resource else uuid.uuid4()) - ) + @staticmethod + def get_playlist_id(params): + """Ensure the resource is a playlist.""" + resource_id = uuid.uuid4() + if not params.resource: + pass + elif isinstance(params.resource, Playlist): + resource_id = params.resource.id + else: + raise TokenError("resource is not a playlist") + + return str(resource_id) + + resource_id = factory.LazyAttribute(get_playlist_id) class Meta: # pylint:disable=missing-class-docstring abstract = True @@ -242,7 +261,7 @@ class LiveSessionLtiTokenFactory(LTIResourceAccessTokenFactory): but this one allows to deeply customize the final JWT. """ - resource_id = factory.LazyAttribute(lambda o: str(o.live_session.video.id)) + resource_id = factory.LazyAttribute(lambda o: str(o.live_session.video.playlist.id)) roles = factory.fuzzy.FuzzyChoice([STUDENT, NONE], getter=lambda x: [x]) consumer_site = factory.LazyAttribute( diff --git a/src/backend/marsha/core/simple_jwt/tokens.py b/src/backend/marsha/core/simple_jwt/tokens.py index a9b80b70ae..817bcba93a 100644 --- a/src/backend/marsha/core/simple_jwt/tokens.py +++ b/src/backend/marsha/core/simple_jwt/tokens.py @@ -348,7 +348,7 @@ def for_live_session(cls, live_session, session_id): - anonymous_id (if not from LTI connection) """ token = cls.for_resource_id( - str(live_session.video.id), + str(live_session.video.playlist.id), session_id, locale=react_locale(live_session.language), ) diff --git a/src/backend/marsha/core/tests/api/live_sessions/test_create.py b/src/backend/marsha/core/tests/api/live_sessions/test_create.py index 651c745fd7..9df7a04c4c 100644 --- a/src/backend/marsha/core/tests/api/live_sessions/test_create.py +++ b/src/backend/marsha/core/tests/api/live_sessions/test_create.py @@ -166,7 +166,7 @@ def test_api_livesession_create_send_mail_fails(self, mock_logger, _mock_send_ma ) self.assertTrue(video.is_scheduled) # token with no context_id and no user information - jwt_token = ResourceAccessTokenFactory(resource=video) + jwt_token = ResourceAccessTokenFactory(resource=video.playlist) anonymous_id = uuid.uuid4() response = self.client.post( @@ -214,7 +214,7 @@ def test_api_livesession_create_public_token(self): ) self.assertTrue(video.is_scheduled) # token with no context_id and no user information - jwt_token = ResourceAccessTokenFactory(resource=video) + jwt_token = ResourceAccessTokenFactory(resource=video.playlist) anonymous_id = uuid.uuid4() response = self.client.post( self._post_url(video), @@ -260,7 +260,7 @@ def test_api_livesession_create_public_token_is_registered_false(self): ) self.assertTrue(video.is_scheduled) # token with no context_id and no user information - jwt_token = ResourceAccessTokenFactory(resource=video) + jwt_token = ResourceAccessTokenFactory(resource=video.playlist) anonymous_id = uuid.uuid4() now = datetime(2022, 4, 7, tzinfo=baseTimezone.utc) with mock.patch.object(LiveSessionTimezone, "now", return_value=now): @@ -310,7 +310,7 @@ def test_api_livesession_create_public_token_anonymous_mandatory( ) self.assertTrue(video.is_scheduled) # token with no context_id and no user information - jwt_token = ResourceAccessTokenFactory(resource=video) + jwt_token = ResourceAccessTokenFactory(resource=video.playlist) response = self.client.post( self._post_url(video), { @@ -339,7 +339,7 @@ def test_api_livesession_create_public_partially_lti1(self): starting_at=timezone.now() + timedelta(days=100), ) self.assertTrue(video.is_scheduled) - jwt_token = LTIResourceAccessTokenFactory(resource=video, user={}) + jwt_token = LTIResourceAccessTokenFactory(resource=video.playlist, user={}) response = self.client.post( self._post_url(video), @@ -373,7 +373,7 @@ def test_api_livesession_create_public_partially_lti2(self): ) self.assertTrue(video.is_scheduled) jwt_token = LTIResourceAccessTokenFactory( - resource=video, + resource=video.playlist, context_id=None, ) @@ -409,7 +409,7 @@ def test_api_livesession_create_public_partially_lti3(self): ) self.assertTrue(video.is_scheduled) jwt_token = LTIResourceAccessTokenFactory( - resource=video, + resource=video.playlist, consumer_site=None, ) @@ -442,7 +442,7 @@ def test_api_livesession_create_token_lti_email_with(self): self.assertTrue(video.is_scheduled) # token has same consumer_site than the video jwt_token = LTIResourceAccessTokenFactory( - resource=video, + resource=video.playlist, context_id="Maths", # explicit to be found in response consumer_site=str(video.playlist.consumer_site.id), user__id="56255f3807599c377bf0e5bf072359fd", # explicit to be found in response @@ -493,7 +493,7 @@ def test_api_livesession_create_token_lti_is_registered_false(self): self.assertTrue(video.is_scheduled) # token has same consumer_site than the video jwt_token = LTIResourceAccessTokenFactory( - resource=video, + resource=video.playlist, context_id="Maths", # explicit to be found in response consumer_site=str(video.playlist.consumer_site.id), user__id="56255f3807599c377bf0e5bf072359fd", # explicit to be found in response @@ -545,7 +545,7 @@ def test_api_livesession_create_token_lti_email_none(self): other_playlist = PlaylistFactory() # token has different context_id than the video jwt_token = LTIResourceAccessTokenFactory( - resource=video, + resource=video.playlist, context_id=str(other_playlist.lti_id), consumer_site=str(video.playlist.consumer_site.id), user__id="56255f3807599c377bf0e5bf072359fd", # explicit to be found in response @@ -600,7 +600,7 @@ def test_api_livesession_create_public_token_record_email_other_livesession_lti( ) self.assertEqual(LiveSession.objects.count(), 1) self.assertTrue(video.is_scheduled) - jwt_token = ResourceAccessTokenFactory(resource=video) + jwt_token = ResourceAccessTokenFactory(resource=video.playlist) anonymous_id = uuid.uuid4() response = self.client.post( self._post_url(video), @@ -660,7 +660,7 @@ def test_api_livesession_create_lti_token_record_email_other_consumer_site( self.assertEqual(LiveSession.objects.count(), 1) self.assertTrue(video.is_scheduled) jwt_token = LTIResourceAccessTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(other_consumer_site.id), context_id=live_session.lti_id, user__id=live_session.lti_user_id, @@ -721,7 +721,7 @@ def test_api_livesession_create_lti_token_record_email_other_context_id( other_context_id = f"{live_session.lti_id}_diff" jwt_token = LTIResourceAccessTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.playlist.consumer_site.id), context_id=other_context_id, user__id=live_session.lti_user_id, @@ -779,7 +779,7 @@ def test_api_livesession_create_lti_token_record_email_lti_user_id(self): self.assertEqual(LiveSession.objects.count(), 1) self.assertTrue(video.is_scheduled) jwt_token = LTIResourceAccessTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(live_session.consumer_site.id), context_id=live_session.lti_id, user__id="NEW", # explicit to be found in response @@ -833,7 +833,7 @@ def test_api_livesession_create_token_lti_email_restricted_token(self): self.assertTrue(video.is_scheduled) # token with different context_id jwt_token = LTIResourceAccessTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.playlist.consumer_site.id), ) @@ -864,7 +864,7 @@ def test_api_livesession_create_public_token_cant_register_when_not_scheduled( """Can't register if video is not scheduled.""" video = VideoFactory() self.assertFalse(video.is_scheduled) - jwt_token = ResourceAccessTokenFactory(resource=video) + jwt_token = ResourceAccessTokenFactory(resource=video.playlist) response = self.client.post( self._post_url(video), @@ -892,7 +892,7 @@ def test_api_livesession_create_lti_token_cant_register_when_not_scheduled( video = VideoFactory() self.assertFalse(video.is_scheduled) jwt_token = LTIResourceAccessTokenFactory( - resource=video, + resource=video.playlist, context_id=str(video.playlist.lti_id), consumer_site=str(video.playlist.consumer_site.id), ) @@ -1064,7 +1064,7 @@ def test_api_livesession_create_cant_register_same_email_same_consumer_none( AnonymousLiveSessionFactory(email="salome@test-fun-mooc.fr", video=video) self.assertTrue(video.is_scheduled) # token with no context_id leading to an undefined consumer_site - jwt_token = ResourceAccessTokenFactory(resource=video) + jwt_token = ResourceAccessTokenFactory(resource=video.playlist) response = self.client.post( self._post_url(video), { @@ -1141,7 +1141,7 @@ def test_api_livesession_create_public_token_same_email_different_video( # livesession with no consumer_site AnonymousLiveSessionFactory(email="chantal@test-fun-mooc.fr", video=video) # token with no context_id leading to no consumer_site - jwt_token = ResourceAccessTokenFactory(resource=video2) + jwt_token = ResourceAccessTokenFactory(resource=video2.playlist) anonymous_id = uuid.uuid4() # With the same email but other video, livesession is possible @@ -1206,7 +1206,7 @@ def test_api_livesession_create_token_lti_same_email_different_video( lti_user_id="56255f3807599c377bf0e5bf072359fd", ) jwt_token = LTIResourceAccessTokenFactory( - resource=video2, + resource=video2.playlist, context_id=live_session.lti_id, consumer_site=str(video.playlist.consumer_site.id), user__email=None, @@ -1260,7 +1260,7 @@ def test_api_livesession_create_username_doesnt_change( ) self.assertTrue(video.is_scheduled) jwt_token = LTIResourceAccessTokenFactory( - resource=video, + resource=video.playlist, context_id="Maths", consumer_site=str(video.playlist.consumer_site.id), user__email=None, @@ -1390,7 +1390,7 @@ def test_api_livesession_create_role_none_email_empty(self): self.assertTrue(video.is_scheduled) # token with context_id jwt_token = LTIResourceAccessTokenFactory( - resource=video, + resource=video.playlist, context_id=str(video.playlist.lti_id), consumer_site=str(video.playlist.consumer_site.id), roles=[NONE], @@ -1439,7 +1439,7 @@ def test_api_livesession_send_mail_i18n(self): other_playlist = PlaylistFactory() # token has different context_id than the video jwt_token = LTIResourceAccessTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.playlist.consumer_site.id), context_id=str(other_playlist.lti_id), user__id="56255f3807599c377bf0e5bf072359fd", diff --git a/src/backend/marsha/core/tests/api/live_sessions/test_display_name.py b/src/backend/marsha/core/tests/api/live_sessions/test_display_name.py index 6a42d8ef63..d5a385fc4a 100644 --- a/src/backend/marsha/core/tests/api/live_sessions/test_display_name.py +++ b/src/backend/marsha/core/tests/api/live_sessions/test_display_name.py @@ -152,7 +152,7 @@ def test_api_livesession_put_username_public_no_anonymous( ): """Field anonymous_id is mandatory when the JWT token is a public one.""" video = VideoFactory() - jwt_token = ResourceAccessTokenFactory(resource=video) + jwt_token = ResourceAccessTokenFactory(resource=video.playlist) response = self.client.put( self._put_url(video), {"display_name": "Antoine"}, @@ -167,7 +167,7 @@ def test_api_livesession_put_username_public_no_anonymous_no_displayname( ): """Field display_name is mandatory.""" video = VideoFactory() - jwt_token = ResourceAccessTokenFactory(resource=video) + jwt_token = ResourceAccessTokenFactory(resource=video.playlist) response = self.client.put( self._put_url(video), {"anonymous_id": uuid.uuid4()}, @@ -188,7 +188,7 @@ def test_api_livesession_put_username_public_session_existing( ) self.assertEqual(LiveSession.objects.count(), 1) - jwt_token = ResourceAccessTokenFactory(resource=video) + jwt_token = ResourceAccessTokenFactory(resource=video.playlist) response = self.client.put( self._put_url(video), {"anonymous_id": anonymous_id, "display_name": "Antoine"}, @@ -232,7 +232,7 @@ def test_api_livesession_put_username_public_session_exists_other_video( live_session = AnonymousLiveSessionFactory(display_name="Samuel", video=video2) self.assertEqual(LiveSession.objects.count(), 1) - jwt_token = ResourceAccessTokenFactory(resource=video) + jwt_token = ResourceAccessTokenFactory(resource=video.playlist) response = self.client.put( self._put_url(video), {"anonymous_id": live_session.anonymous_id, "display_name": "Antoine"}, @@ -274,7 +274,7 @@ def test_api_livesession_put_username_public_session_no( anonymous_id = uuid.uuid4() self.assertEqual(LiveSession.objects.count(), 0) - jwt_token = ResourceAccessTokenFactory(resource=video) + jwt_token = ResourceAccessTokenFactory(resource=video.playlist) response = self.client.put( self._put_url(video), {"anonymous_id": anonymous_id, "display_name": "Antoine"}, @@ -315,7 +315,7 @@ def test_api_livesession_put_username_public_already_exists( video = VideoFactory() AnonymousLiveSessionFactory(display_name="Samuel", video=video) self.assertEqual(LiveSession.objects.count(), 1) - jwt_token = ResourceAccessTokenFactory(resource=video) + jwt_token = ResourceAccessTokenFactory(resource=video.playlist) response = self.client.put( self._put_url(video), {"anonymous_id": uuid.uuid4(), "display_name": "Samuel"}, @@ -332,7 +332,7 @@ def test_api_livesession_put_username_lti_no_displayname(self): """Field display_name is mandatory.""" video = VideoFactory() jwt_token = LTIResourceAccessTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.playlist.consumer_site.id), ) response = self.client.put( @@ -418,7 +418,7 @@ def test_api_livesession_put_username_lti_session_exists_other_video(self): self.assertEqual(LiveSession.objects.count(), 1) jwt_token = LTIResourceAccessTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(live_session.consumer_site.id), context_id=live_session.lti_id, user__email="sabrina@fun-test.fr", @@ -463,7 +463,7 @@ def test_api_livesession_put_username_lti_session_no(self): self.assertEqual(LiveSession.objects.count(), 0) jwt_token = LTIResourceAccessTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.playlist.consumer_site.id), context_id="Maths", user__email="sabrina@fun-test.fr", @@ -511,7 +511,7 @@ def test_api_livesession_put_username_lti_already_exists( AnonymousLiveSessionFactory(display_name="Samuel", video=video) jwt_token = LTIResourceAccessTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.playlist.consumer_site.id), ) response = self.client.put( diff --git a/src/backend/marsha/core/tests/api/live_sessions/test_list.py b/src/backend/marsha/core/tests/api/live_sessions/test_list.py index 6865b096fa..a9b4e678ef 100644 --- a/src/backend/marsha/core/tests/api/live_sessions/test_list.py +++ b/src/backend/marsha/core/tests/api/live_sessions/test_list.py @@ -231,7 +231,7 @@ def test_list_livesession_public_token(self): AnonymousLiveSessionFactory(email=user.email, video=video2) # public token - jwt_token = ResourceAccessTokenFactory(resource=video) + jwt_token = ResourceAccessTokenFactory(resource=video.playlist) response = self.client.get( self._get_url(video), HTTP_AUTHORIZATION=f"Bearer {jwt_token}", @@ -245,7 +245,7 @@ def test_api_livesession_list_throttling(self): # first 3 requests shouldn't be throttled for _i in range(3): video = VideoFactory() - jwt_token = ResourceAccessTokenFactory(resource=video) + jwt_token = ResourceAccessTokenFactory(resource=video.playlist) response = self.client.get( f"{self._get_url(video)}?anonymous_id={uuid.uuid4()}", @@ -293,7 +293,7 @@ def test_api_livesession_list_throttling_no_anonymous(self): # first 3 requests shouldn't be throttled for _i in range(3): video = VideoFactory() - jwt_token = ResourceAccessTokenFactory(resource=video) + jwt_token = ResourceAccessTokenFactory(resource=video.playlist) response = self.client.get( self._get_url(video), @@ -346,7 +346,7 @@ def test_list_livesession_public_token_anonymous(self): ) # public token - jwt_token = ResourceAccessTokenFactory(resource=video) + jwt_token = ResourceAccessTokenFactory(resource=video.playlist) response = self.client.get( f"{self._get_url(video)}?anonymous_id={livesession.anonymous_id}", HTTP_AUTHORIZATION=f"Bearer {jwt_token}", @@ -528,7 +528,7 @@ def test_list_livesession_lti_token_role_admin_instructors(self): # context_id in the token jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, context_id="Maths", consumer_site=str(video.playlist.consumer_site.id), ) diff --git a/src/backend/marsha/core/tests/api/live_sessions/test_list_attendances.py b/src/backend/marsha/core/tests/api/live_sessions/test_list_attendances.py index af0758affb..0fa45fe367 100644 --- a/src/backend/marsha/core/tests/api/live_sessions/test_list_attendances.py +++ b/src/backend/marsha/core/tests/api/live_sessions/test_list_attendances.py @@ -222,7 +222,7 @@ def test_api_livesession_read_attendances_admin_video_unknown(self): # token with right context_id and lti_user_id jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.playlist.consumer_site.id), context_id=str(video.playlist.lti_id), ) @@ -303,7 +303,7 @@ def test_api_livesession_read_attendances_admin(self): ) # token with right context_id and lti_user_id jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.playlist.consumer_site.id), context_id=str(video.playlist.lti_id), ) @@ -386,7 +386,7 @@ def test_api_livesession_read_attendances_admin_target_record(self): # token with right context_id and lti_user_id jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.playlist.consumer_site.id), context_id=str(video.playlist.lti_id), ) @@ -468,7 +468,7 @@ def test_api_livesession_read_attendances_well_computed_start_and_end(self): ) # token with right context_id and lti_user_id jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.playlist.consumer_site.id), context_id=str(video.playlist.lti_id), ) @@ -588,7 +588,7 @@ def test_api_livesession_read_attendances_well_computed_before_start(self): # token with right context_id and lti_user_id jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.playlist.consumer_site.id), context_id=str(video.playlist.lti_id), ) @@ -748,7 +748,7 @@ def test_api_livesession_read_attendances_well_computed_start_and_live_running( # token with right context_id and lti_user_id jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.playlist.consumer_site.id), context_id=str(video.playlist.lti_id), ) @@ -883,7 +883,7 @@ def test_api_livesession_read_attendances_well_computed_start_and_live_running_c # token with right context_id and lti_user_id jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.playlist.consumer_site.id), context_id=str(video.playlist.lti_id), ) @@ -951,7 +951,7 @@ def test_api_livesession_read_attendances_well_computed_start_and_live_running_c # we now query the list of attendance for video2 jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video2, + resource=video2.playlist, consumer_site=str(video2.playlist.consumer_site.id), context_id=str(video2.playlist.lti_id), ) @@ -1036,7 +1036,7 @@ def test_api_livesession_reset_cache( # token with right context_id and lti_user_id jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.playlist.consumer_site.id), context_id=str(video.playlist.lti_id), ) @@ -1165,7 +1165,7 @@ def test_api_livesession_video_no_stopped_at_cache_has_timeout( # token with right context_id and lti_user_id jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.playlist.consumer_site.id), context_id=str(video.playlist.lti_id), ) @@ -1260,7 +1260,7 @@ def test_api_livesession_video_ended_cache_no_timeout( # token with right context_id and lti_user_id jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.playlist.consumer_site.id), context_id=str(video.playlist.lti_id), ) @@ -1340,7 +1340,7 @@ def test_api_livesession_read_attendances_same_keys( # token with right context_id and lti_user_id jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.playlist.consumer_site.id), context_id=str(video.playlist.lti_id), ) @@ -1406,7 +1406,7 @@ def test_api_livesession_read_attendances_well_computed_running_between_info_att # token with right context_id and lti_user_id jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.playlist.consumer_site.id), context_id=str(video.playlist.lti_id), ) @@ -1516,7 +1516,7 @@ def test_api_livesession_read_attendances_well_computed_start_and_live_page(self # token with right context_id and lti_user_id jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.playlist.consumer_site.id), context_id=str(video.playlist.lti_id), ) @@ -1627,7 +1627,7 @@ def test_api_livesession_read_attendances_no_timeline_video(self): livesession.refresh_from_db() # token with right context_id and lti_user_id jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.playlist.consumer_site.id), context_id=str(video.playlist.lti_id), ) @@ -1681,7 +1681,7 @@ def test_api_livesession_read_attendances_admin_is_registered_att_null_or_empty( # token with right context_id and lti_user_id jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.playlist.consumer_site.id), context_id=str(video.playlist.lti_id), ) @@ -1728,7 +1728,7 @@ def test_api_livesession_read_attendances_admin_live_attendance_key_string(self) # token with right context_id and lti_user_id jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.playlist.consumer_site.id), context_id=str(video.playlist.lti_id), ) diff --git a/src/backend/marsha/core/tests/api/live_sessions/test_push_attendance.py b/src/backend/marsha/core/tests/api/live_sessions/test_push_attendance.py index 08b0924a94..2de44fb8de 100644 --- a/src/backend/marsha/core/tests/api/live_sessions/test_push_attendance.py +++ b/src/backend/marsha/core/tests/api/live_sessions/test_push_attendance.py @@ -184,7 +184,7 @@ def test_api_livesession_post_attendance_no_attendance(self): """Request without attendance should raise an error.""" video = VideoFactory() jwt_token = LTIResourceAccessTokenFactory( - resource=video, + resource=video.playlist, context_id=str(video.playlist.lti_id), consumer_site=str(video.playlist.consumer_site.id), ) @@ -205,7 +205,7 @@ def test_api_livesession_post_attendance_token_lti_consumer_site_not_existing( """Pushing an attendance on a not existing video should fail.""" video = VideoFactory() jwt_token = LTIResourceAccessTokenFactory( - resource=video, + resource=video.playlist, context_id=str(video.playlist.lti_id), user__email=None, ) @@ -227,7 +227,7 @@ def test_api_livesession_post_attendance_token_lti_email_none_previous_none( """Endpoint push_attendance works with no email and no previous record.""" video = VideoFactory() jwt_token = LTIResourceAccessTokenFactory( - resource=video, + resource=video.playlist, context_id=str(video.playlist.lti_id), consumer_site=str(video.playlist.consumer_site.id), user__email=None, @@ -287,7 +287,7 @@ def test_api_livesession_post_attendance_token_lti_existing_record(self): ) self.assertEqual(LiveSession.objects.count(), 1) jwt_token = LTIResourceAccessTokenFactory( - resource=video, + resource=video.playlist, context_id=str(livesession.lti_id), consumer_site=str(video.playlist.consumer_site.id), user__email="chantal@aol.com", @@ -359,7 +359,7 @@ def test_api_livesession_post_new_attendance_token_public(self): video = VideoFactory() anonymous_id = uuid.uuid4() self.assertEqual(LiveSession.objects.count(), 0) - jwt_token = ResourceAccessTokenFactory(resource=video) + jwt_token = ResourceAccessTokenFactory(resource=video.playlist) response = self.client.post( f"/api/videos/{video.id}/livesessions/push_attendance/?anonymous_id={anonymous_id}", {"language": "fr", "live_attendance": {}}, @@ -395,7 +395,7 @@ def test_api_livesession_post_attendance_existing_token_public(self): self.assertEqual(LiveSession.objects.count(), 1) timestamp = to_timestamp(timezone.now()) - jwt_token = ResourceAccessTokenFactory(resource=livesession.video) + jwt_token = ResourceAccessTokenFactory(resource=livesession.video.playlist) response = self.client.post( f"/api/videos/{livesession.video_id}/livesessions/push_attendance/" f"?anonymous_id={livesession.anonymous_id}", @@ -447,7 +447,7 @@ def test_api_livesession_post_attendance_token_public_missing_anonymous_id(self) self.assertEqual(LiveSession.objects.count(), 0) timestamp = to_timestamp(timezone.now()) - jwt_token = ResourceAccessTokenFactory(resource=video) + jwt_token = ResourceAccessTokenFactory(resource=video.playlist) response = self.client.post( self._post_url(video), { @@ -800,7 +800,7 @@ def test_api_livesession_post_attendance_token_with_could_match_other_records( self.assertEqual(LiveSession.objects.count(), nb_created) # token with same email jwt_token = LTIResourceAccessTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.playlist.consumer_site.id), context_id="Maths", user__id="55555", diff --git a/src/backend/marsha/core/tests/api/live_sessions/test_retrieve.py b/src/backend/marsha/core/tests/api/live_sessions/test_retrieve.py index 0e26d19bf0..f6625bfddc 100644 --- a/src/backend/marsha/core/tests/api/live_sessions/test_retrieve.py +++ b/src/backend/marsha/core/tests/api/live_sessions/test_retrieve.py @@ -181,7 +181,7 @@ def test_api_livesession_read_token_public(self): video = VideoFactory() livesession = AnonymousLiveSessionFactory(video=video) # token has no consumer_site, no context_id and no user's info - jwt_token = ResourceAccessTokenFactory(resource=video) + jwt_token = ResourceAccessTokenFactory(resource=video.playlist) response = self.client.get( self._get_url(livesession.video, livesession), @@ -200,7 +200,7 @@ def test_api_livesession_read_token_public_with_anonymous(self): """ livesession = AnonymousLiveSessionFactory() # token has no consumer_site, no context_id and no user's info - jwt_token = ResourceAccessTokenFactory(resource=livesession.video) + jwt_token = ResourceAccessTokenFactory(resource=livesession.video.playlist) response = self.client.get( f"{self._get_url(livesession.video, livesession)}" @@ -366,7 +366,7 @@ def test_api_livesession_read_token_lti_record_consumer_none(self): # token has context_id so different consumer_site jwt_token = LTIResourceAccessTokenFactory( - resource=livesession.video, # as usual + resource=livesession.video.playlist, # as usual roles=[random.choice([STUDENT, NONE])], user__email=livesession.email, # as usual # below arguments are not usual for anonymous live session @@ -541,7 +541,7 @@ def test_api_livesession_read_token_lti_admin_instruct_token_email_ok(self): username="Sam", # explicit to be found in response ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=livesession.video, + resource=livesession.video.playlist, context_id=str(livesession.video.playlist.lti_id), consumer_site=str(livesession.consumer_site.id), ) @@ -586,7 +586,7 @@ def test_api_livesession_read_token_lti_admin_instruct_token_email_none(self): ) # token with right context_id jwt_token = InstructorOrAdminLtiTokenFactory( - resource=livesession.video, + resource=livesession.video.playlist, context_id=str(livesession.video.playlist.lti_id), consumer_site=str(livesession.consumer_site.id), user__email=None, @@ -627,7 +627,7 @@ def test_api_livesession_read_token_lti_admin_instruct_email_diff(self): jwt_token = InstructorOrAdminLtiTokenFactory( # context_id and consumer_site are not determinant (random uuid here) - resource=livesession.video, + resource=livesession.video.playlist, ) response = self.client.get( @@ -667,7 +667,7 @@ def test_api_livesession_read_token_lti_admin_instruct_record_consumer_diff(self # token with context_id leading to another consumer site jwt_token = InstructorOrAdminLtiTokenFactory( - resource=livesession.video, + resource=livesession.video.playlist, context_id=str(livesession.video.playlist.lti_id), # consumer_site is not other_consumer_site consumer_site=str(livesession.video.playlist.consumer_site.id), @@ -710,7 +710,7 @@ def test_api_livesession_read_token_lti_admin_instruct_record_course_diff(self): # token with context_id leading to another consumer site jwt_token = InstructorOrAdminLtiTokenFactory( - resource=livesession.video, + resource=livesession.video.playlist, context_id=f"{livesession.video.playlist.lti_id}_diff", # consumer_site is not other_consumer_site consumer_site=str(livesession.video.playlist.consumer_site.id), @@ -779,7 +779,7 @@ def test_api_livesession_read_token_public_other_video_context_none_role(self): livesession = AnonymousLiveSessionFactory() # token with no context_id leading to the same undefined consumer_site - jwt_token = ResourceAccessTokenFactory(resource=VideoFactory()) + jwt_token = ResourceAccessTokenFactory(resource=VideoFactory().playlist) response = self.client.get( self._get_url(livesession.video, livesession), @@ -793,7 +793,7 @@ def test_api_livesession_read_token_lti_other_video_context_none_role(self): livesession = LiveSessionFactory(is_from_lti_connection=True) jwt_token = LTIResourceAccessTokenFactory( - resource=VideoFactory(), # other video + resource=VideoFactory().playlist, # other video context_id=str(livesession.video.playlist.lti_id), consumer_site=str(livesession.video.playlist.consumer_site.id), user__email=None, diff --git a/src/backend/marsha/core/tests/api/live_sessions/test_update.py b/src/backend/marsha/core/tests/api/live_sessions/test_update.py index 715defbaab..02de338b31 100644 --- a/src/backend/marsha/core/tests/api/live_sessions/test_update.py +++ b/src/backend/marsha/core/tests/api/live_sessions/test_update.py @@ -210,7 +210,7 @@ def test_api_livesession_update_put_with_token_not_allowed(self): starting_at=timezone.now() + timedelta(days=100), ) live_session = AnonymousLiveSessionFactory(video=video) - jwt_token = ResourceAccessTokenFactory(resource=video) + jwt_token = ResourceAccessTokenFactory(resource=video.playlist) response = self.client.put( self._update_url(video, live_session), @@ -234,7 +234,7 @@ def test_api_livesession_put_not_allowed(self): ) jwt_token = LTIResourceAccessTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.playlist.consumer_site.id), context_id=live_session.lti_id, user__id=live_session.lti_user_id, @@ -631,7 +631,7 @@ def test_api_livesession_admin_can_patch_any_record_from_the_same_consumer_site( ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.playlist.consumer_site.id), context_id="Maths", ) @@ -699,7 +699,7 @@ def test_api_live_session_admin_using_existing_email(self): ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.playlist.consumer_site.id), context_id="Maths", ) @@ -732,7 +732,7 @@ def test_api_livesession_patch_email_from_anonymous_livesession(self): ) self.assertIsNone(live_session.registered_at) - jwt_token = ResourceAccessTokenFactory(resource=video) + jwt_token = ResourceAccessTokenFactory(resource=video.playlist) now = datetime(2022, 4, 7, tzinfo=baseTimezone.utc) with mock.patch.object(LiveSessionTimezone, "now", return_value=now): @@ -782,7 +782,7 @@ def test_api_livesession_update_email_with_another_anonymous_id(self): video=video, ) - jwt_token = ResourceAccessTokenFactory(resource=video) + jwt_token = ResourceAccessTokenFactory(resource=video.playlist) other_anonymous_id = uuid.uuid4() response = self.client.patch( @@ -813,7 +813,7 @@ def test_api_livesession_patch_language(self): self.assertIsNone(live_session.registered_at) self.assertEqual(live_session.language, "en") - jwt_token = ResourceAccessTokenFactory(resource=video) + jwt_token = ResourceAccessTokenFactory(resource=video.playlist) # if a wrong language is set response = self.client.patch( diff --git a/src/backend/marsha/core/tests/api/playlists/test_is_claimed.py b/src/backend/marsha/core/tests/api/playlists/test_is_claimed.py index 40ba6e5012..d1549b0f00 100644 --- a/src/backend/marsha/core/tests/api/playlists/test_is_claimed.py +++ b/src/backend/marsha/core/tests/api/playlists/test_is_claimed.py @@ -29,7 +29,7 @@ def test_playlist_is_claimed_anonymous(self): def test_playlist_is_claimed_student(self): """Random logged-in users cannot check if a playlist is claimed.""" video = factories.VideoFactory() - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) response = self.client.get( f"/api/playlists/{video.playlist.id}/is-claimed/", @@ -43,7 +43,7 @@ def test_playlist_is_claimed_LTI_administrator(self): video = factories.VideoFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, roles=[ADMINISTRATOR], ) @@ -61,7 +61,7 @@ def test_playlist_is_claimed_LTI_instructor(self): video = factories.VideoFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, roles=[INSTRUCTOR], ) @@ -79,7 +79,7 @@ def test_playlist_is_claimed_LTI_instructor_claimed(self): video = factories.VideoFactory(upload_state="pending") consumer_site = factories.ConsumerSiteFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, roles=[INSTRUCTOR], consumer_site=str(consumer_site.id), ) diff --git a/src/backend/marsha/core/tests/api/playlists/test_retrieve.py b/src/backend/marsha/core/tests/api/playlists/test_retrieve.py index b93299c287..0daf0fd827 100644 --- a/src/backend/marsha/core/tests/api/playlists/test_retrieve.py +++ b/src/backend/marsha/core/tests/api/playlists/test_retrieve.py @@ -54,7 +54,7 @@ def test_retrieve_playlist_through_video_token_instructor(self): """Playlist instructors can retrieve playlists through video token.""" video = factories.VideoFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.get( f"/api/playlists/{video.playlist.id}/", @@ -67,7 +67,7 @@ def test_retrieve_playlist_through_document_token_instructor(self): """Playlist instructors can retrieve playlists through document token.""" document = factories.DocumentFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=document) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=document.playlist) response = self.client.get( f"/api/playlists/{document.playlist.id}/", diff --git a/src/backend/marsha/core/tests/api/playlists/test_update.py b/src/backend/marsha/core/tests/api/playlists/test_update.py index 302983c450..bfd6198a90 100644 --- a/src/backend/marsha/core/tests/api/playlists/test_update.py +++ b/src/backend/marsha/core/tests/api/playlists/test_update.py @@ -139,7 +139,7 @@ def test_update_playlist_through_video_token_instructor(self): """Playlist instructors or admins can update playlists through video token.""" video = factories.VideoFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.get( f"/api/playlists/{video.playlist.id}/", @@ -163,7 +163,7 @@ def test_update_playlist_through_document_token_instructor(self): """Playlist instructors or admins can update playlists with document token.""" document = factories.DocumentFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=document) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=document.playlist) response = self.client.get( f"/api/playlists/{document.playlist.id}/", @@ -187,7 +187,7 @@ def test_partial_update_playlist_through_video_token_instructor(self): """Playlist instructors or admins can partially update playlists.""" video = factories.VideoFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.patch( f"/api/playlists/{video.playlist.id}/", diff --git a/src/backend/marsha/core/tests/api/portability_requests/test_create.py b/src/backend/marsha/core/tests/api/portability_requests/test_create.py index bbd0b722b8..d61cef1bbd 100644 --- a/src/backend/marsha/core/tests/api/portability_requests/test_create.py +++ b/src/backend/marsha/core/tests/api/portability_requests/test_create.py @@ -94,7 +94,7 @@ def test_create_api_portability_request_student(self): """ video = UploadedVideoFactory() - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) response = self.client.post( "/api/portability-requests/", @@ -119,7 +119,7 @@ def test_create_api_portability_request_instructor_no_playlist(self): when he has no access to the requesting playlist. """ video = UploadedVideoFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.post( "/api/portability-requests/", @@ -151,7 +151,7 @@ def test_create_api_portability_request_instructor_with_playlist(self): jwt_token = PlaylistLtiTokenFactory( consumer_site=str(consumer_site.pk), playlist_id=str(destination_playlist.pk), - resource=video, + resource=video.playlist, user__id=str(lti_user_id), ) @@ -227,7 +227,7 @@ def test_create_api_portability_request_instructor_with_associated_user(self): jwt_token = PlaylistLtiTokenFactory( consumer_site=str(consumer_site.pk), playlist_id=str(destination_playlist.pk), - resource=video, + resource=video.playlist, user__id=str(lti_user_id), ) @@ -305,7 +305,7 @@ def test_create_api_portability_request_instructor_with_playlist_errors(self): jwt_token = PlaylistLtiTokenFactory( consumer_site=str(consumer_site.pk), playlist_id=str(destination_playlist.pk), - resource=video, + resource=video.playlist, user__id=str(lti_user_id), ) diff --git a/src/backend/marsha/core/tests/api/shared_live_media/test_create.py b/src/backend/marsha/core/tests/api/shared_live_media/test_create.py index 1fe8497b19..7aa643b3db 100644 --- a/src/backend/marsha/core/tests/api/shared_live_media/test_create.py +++ b/src/backend/marsha/core/tests/api/shared_live_media/test_create.py @@ -40,7 +40,7 @@ def test_api_shared_live_media_create_instructor(self): """An instructor should be able to create a shared live media for an existing video.""" video = VideoFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.post( self._post_url(video), @@ -71,7 +71,7 @@ def test_api_shared_live_media_create_instructor_in_read_only(self): """An instructor in read only should not be able to create a shared live media.""" video = VideoFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, permissions__can_update=False, ) @@ -86,7 +86,7 @@ def test_api_shared_live_media_create_instructor_in_read_only(self): def test_api_shared_live_media_create_student(self): """A student should not be able to create a shared live media.""" video = VideoFactory() - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) response = self.client.post( self._post_url(video), diff --git a/src/backend/marsha/core/tests/api/shared_live_media/test_delete.py b/src/backend/marsha/core/tests/api/shared_live_media/test_delete.py index 583c9b0a55..3e2c1ec468 100644 --- a/src/backend/marsha/core/tests/api/shared_live_media/test_delete.py +++ b/src/backend/marsha/core/tests/api/shared_live_media/test_delete.py @@ -45,7 +45,7 @@ def test_api_shared_live_media_delete_student(self): """A student can not delete a shared live media.""" shared_live_media = SharedLiveMediaFactory() - jwt_token = StudentLtiTokenFactory(resource=shared_live_media.video) + jwt_token = StudentLtiTokenFactory(resource=shared_live_media.video.playlist) response = self.client.delete( self._delete_url(shared_live_media.video, shared_live_media), @@ -60,7 +60,9 @@ def test_api_shared_live_media_delete_instructor(self): video = VideoFactory() video.shared_live_medias.set([shared_live_media]) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=shared_live_media.video) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=shared_live_media.video.playlist + ) self.assertTrue(SharedLiveMedia.objects.exists()) @@ -247,7 +249,9 @@ def test_api_shared_live_media_delete_active(self): ) video.shared_live_medias.set([shared_live_media]) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=shared_live_media.video) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=shared_live_media.video.playlist + ) self.assertTrue(SharedLiveMedia.objects.exists()) diff --git a/src/backend/marsha/core/tests/api/shared_live_media/test_initiate_upload.py b/src/backend/marsha/core/tests/api/shared_live_media/test_initiate_upload.py index e48ce06bcb..3825a6c0d9 100644 --- a/src/backend/marsha/core/tests/api/shared_live_media/test_initiate_upload.py +++ b/src/backend/marsha/core/tests/api/shared_live_media/test_initiate_upload.py @@ -56,7 +56,7 @@ def test_api_shared_live_media_initiate_upload_student(self): shared_live_media = SharedLiveMediaFactory() - jwt_token = StudentLtiTokenFactory(resource=shared_live_media.video) + jwt_token = StudentLtiTokenFactory(resource=shared_live_media.video.playlist) response = self.client.post( self._post_url(shared_live_media.video, shared_live_media), @@ -79,7 +79,9 @@ def test_api_shared_live_media_initiate_upload_instructor(self): video__id="ed08da34-7447-4141-96ff-5740315d7b99", ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=shared_live_media.video) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=shared_live_media.video.playlist + ) now = datetime(2021, 12, 2, tzinfo=baseTimezone.utc) with mock.patch.object(timezone, "now", return_value=now), mock.patch( @@ -141,7 +143,9 @@ def test_api_shared_live_media_initiate_upload_file_without_extension(self): video__id="ed08da34-7447-4141-96ff-5740315d7b99", ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=shared_live_media.video) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=shared_live_media.video.playlist + ) now = datetime(2021, 12, 2, tzinfo=baseTimezone.utc) with mock.patch.object(timezone, "now", return_value=now), mock.patch( @@ -203,7 +207,9 @@ def test_api_shared_live_media_initiate_upload_file_without_mimetype(self): video__id="ed08da34-7447-4141-96ff-5740315d7b99", ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=shared_live_media.video) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=shared_live_media.video.playlist + ) now = datetime(2021, 12, 2, tzinfo=baseTimezone.utc) with mock.patch.object(timezone, "now", return_value=now), mock.patch( @@ -232,7 +238,9 @@ def test_api_shared_live_media_initiate_upload_file_wrong_mimetype(self): video__id="ed08da34-7447-4141-96ff-5740315d7b99", ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=shared_live_media.video) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=shared_live_media.video.playlist + ) now = datetime(2021, 12, 2, tzinfo=baseTimezone.utc) with mock.patch.object(timezone, "now", return_value=now), mock.patch( diff --git a/src/backend/marsha/core/tests/api/shared_live_media/test_list.py b/src/backend/marsha/core/tests/api/shared_live_media/test_list.py index 8b6bbe1706..74fb32a025 100644 --- a/src/backend/marsha/core/tests/api/shared_live_media/test_list.py +++ b/src/backend/marsha/core/tests/api/shared_live_media/test_list.py @@ -62,7 +62,7 @@ def test_api_shared_live_media_list_student(self): nb_pages=3, ) - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) response = self.client.get( self._get_url(video), @@ -94,7 +94,7 @@ def test_api_shared_live_media_list_instructor(self): nb_pages=3, ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.get( self._get_url(video), @@ -180,7 +180,7 @@ def test_api_shared_live_media_list_instructor_other_video(self): video=other_video, ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.get( self._get_url(other_video), @@ -224,7 +224,7 @@ def test_api_shared_live_media_list_instructor_ready_to_show_and_signed_url_acti nb_pages=3, ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) # fix the time so that the url signature is deterministic and can be checked now = datetime(2021, 11, 30, tzinfo=baseTimezone.utc) diff --git a/src/backend/marsha/core/tests/api/shared_live_media/test_retrieve.py b/src/backend/marsha/core/tests/api/shared_live_media/test_retrieve.py index cfebe2bc83..a94c50ceeb 100644 --- a/src/backend/marsha/core/tests/api/shared_live_media/test_retrieve.py +++ b/src/backend/marsha/core/tests/api/shared_live_media/test_retrieve.py @@ -54,7 +54,7 @@ def test_api_shared_live_media_read_detail_student_not_ready_to_show(self): nb_pages=None, ) - jwt_token = StudentLtiTokenFactory(resource=shared_live_media.video) + jwt_token = StudentLtiTokenFactory(resource=shared_live_media.video.playlist) response = self.client.get( self._get_url(shared_live_media.video, shared_live_media), @@ -92,7 +92,7 @@ def test_api_shared_live_media_read_detail_student_ready_to_show(self): video__id="d9d7049c-5a3f-4070-a494-e6bf0bd8b9fb", ) - jwt_token = StudentLtiTokenFactory(resource=shared_live_media.video) + jwt_token = StudentLtiTokenFactory(resource=shared_live_media.video.playlist) response = self.client.get( self._get_url(shared_live_media.video, shared_live_media), @@ -154,7 +154,7 @@ def test_api_shared_live_media_read_detail_student_ready_to_show_and_signed_url_ video__id="d9d7049c-5a3f-4070-a494-e6bf0bd8b9fb", ) - jwt_token = StudentLtiTokenFactory(resource=shared_live_media.video) + jwt_token = StudentLtiTokenFactory(resource=shared_live_media.video.playlist) # fix the time so that the url signature is deterministic and can be checked now = datetime(2021, 11, 30, tzinfo=baseTimezone.utc) @@ -244,7 +244,7 @@ def test_api_shared_live_media_read_detail_student_ready_to_show_and_show_downlo video__id="d9d7049c-5a3f-4070-a494-e6bf0bd8b9fb", ) - jwt_token = StudentLtiTokenFactory(resource=shared_live_media.video) + jwt_token = StudentLtiTokenFactory(resource=shared_live_media.video.playlist) # fix the time so that the url signature is deterministic and can be checked now = datetime(2021, 11, 30, tzinfo=baseTimezone.utc) @@ -315,7 +315,7 @@ def test_api_shared_live_media_read_detail_instructor_not_ready_to_show(self): ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=shared_live_media.video, + resource=shared_live_media.video.playlist, permissions__can_update=False, ) @@ -356,7 +356,7 @@ def test_api_shared_live_media_read_detail_instructor_ready_to_show(self): ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=shared_live_media.video, + resource=shared_live_media.video.playlist, permissions__can_update=False, ) @@ -421,7 +421,7 @@ def test_api_shared_live_media_read_detail_instructor_ready_to_show_and_signed_u ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=shared_live_media.video, + resource=shared_live_media.video.playlist, permissions__can_update=False, ) @@ -497,7 +497,7 @@ def test_api_shared_live_media_read_detail_other_video(self): other_video = VideoFactory() jwt_token = StudentLtiTokenFactory( - resource=other_video, + resource=other_video.playlist, roles=[random.choice(["instructor", "administrator", "student"])], ) diff --git a/src/backend/marsha/core/tests/api/shared_live_media/test_update.py b/src/backend/marsha/core/tests/api/shared_live_media/test_update.py index 7a5b2425b1..ae735b411a 100644 --- a/src/backend/marsha/core/tests/api/shared_live_media/test_update.py +++ b/src/backend/marsha/core/tests/api/shared_live_media/test_update.py @@ -47,7 +47,7 @@ def test_api_shared_live_media_update_student(self): """A student can not update a shared live media.""" shared_live_media = SharedLiveMediaFactory() - jwt_token = StudentLtiTokenFactory(resource=shared_live_media.video) + jwt_token = StudentLtiTokenFactory(resource=shared_live_media.video.playlist) response = self.client.put( self._update_url(shared_live_media.video, shared_live_media), @@ -62,7 +62,9 @@ def test_api_shared_live_media_update_instructor(self): """An instructor can update a shared live media.""" shared_live_media = SharedLiveMediaFactory(title="update me!") - jwt_token = InstructorOrAdminLtiTokenFactory(resource=shared_live_media.video) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=shared_live_media.video.playlist + ) with mock.patch( "marsha.websocket.utils.channel_layers_utils.dispatch_shared_live_media" diff --git a/src/backend/marsha/core/tests/api/thumbnails/test_create.py b/src/backend/marsha/core/tests/api/thumbnails/test_create.py index d3fb43ae60..5c985dc82e 100644 --- a/src/backend/marsha/core/tests/api/thumbnails/test_create.py +++ b/src/backend/marsha/core/tests/api/thumbnails/test_create.py @@ -159,7 +159,7 @@ def test_api_thumbnail_create_student(self): """Student users should not be able to create a thumbnail.""" video = VideoFactory() - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) response = self.client.post( self._post_url(video), HTTP_AUTHORIZATION=f"Bearer {jwt_token}" @@ -171,7 +171,7 @@ def test_api_thumbnail_create_instructor_or_admin(self): """LTI instructor or admin should be able to create a thumbnail.""" video = VideoFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.post( self._post_url(video), @@ -199,7 +199,7 @@ def test_api_thumbnail_create_instructor_or_admin(self): def test_api_thumbnail_create_instructor_file_too_large(self): """Instructor users should not be able to create a thumbnail if file is too large""" video = VideoFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.post( self._post_url(video), @@ -215,7 +215,7 @@ def test_api_thumbnail_create_instructor_file_too_large(self): def test_api_thumbnail_create_instructor_no_size_parameter_provided(self): """Instructor users shouldn't be able to create a thumbnail without a file size""" video = VideoFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.post( self._post_url(video), HTTP_AUTHORIZATION=f"Bearer {jwt_token}" @@ -232,7 +232,7 @@ def test_api_thumbnail_create_already_existing_instructor(self): video = VideoFactory() ThumbnailFactory(video=video) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.post( self._post_url(video), @@ -252,7 +252,7 @@ def test_api_thumbnail_instructor_create_in_read_only(self): thumbnail = ThumbnailFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=thumbnail.video, + resource=thumbnail.video.playlist, permissions__can_update=False, ) diff --git a/src/backend/marsha/core/tests/api/thumbnails/test_delete.py b/src/backend/marsha/core/tests/api/thumbnails/test_delete.py index a503006ebd..cb96f8d2ee 100644 --- a/src/backend/marsha/core/tests/api/thumbnails/test_delete.py +++ b/src/backend/marsha/core/tests/api/thumbnails/test_delete.py @@ -155,7 +155,7 @@ def test_api_thumbnail_delete_student(self): video = VideoFactory() thumbnail = ThumbnailFactory(video=video) - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) response = self.client.delete( self._delete_url(video, thumbnail), @@ -165,7 +165,7 @@ def test_api_thumbnail_delete_student(self): def test_api_thumbnail_delete_instructor(self): """Instructor should be able to delete a thumbnail for its video.""" - jwt_token = InstructorOrAdminLtiTokenFactory(resource=self.some_video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=self.some_video.playlist) self.assertEqual(Thumbnail.objects.count(), 1) @@ -199,7 +199,7 @@ def test_api_thumbnail_delete_instructor_in_read_only(self): thumbnail = ThumbnailFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=thumbnail.video, + resource=thumbnail.video.playlist, permissions__can_update=False, ) @@ -216,7 +216,7 @@ def test_api_thumbnail_delete_instructor_other_video(self): video_other = VideoFactory() thumbnail = ThumbnailFactory(video=video_other) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video_token) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video_token.playlist) response = self.client.delete( self._delete_url(video_other, thumbnail), diff --git a/src/backend/marsha/core/tests/api/thumbnails/test_initiate_upload.py b/src/backend/marsha/core/tests/api/thumbnails/test_initiate_upload.py index 7d5936359a..c826021cc0 100644 --- a/src/backend/marsha/core/tests/api/thumbnails/test_initiate_upload.py +++ b/src/backend/marsha/core/tests/api/thumbnails/test_initiate_upload.py @@ -31,7 +31,7 @@ def test_api_thumbnail_initiate_upload_anonymous(self): def test_api_thumbnail_initiate_upload_student(self): """Student users should not be allowed to initiate an upload.""" thumbnail = ThumbnailFactory() - jwt_token = StudentLtiTokenFactory(resource=thumbnail.video) + jwt_token = StudentLtiTokenFactory(resource=thumbnail.video.playlist) response = self.client.post( self._post_url(thumbnail.video, thumbnail), @@ -47,7 +47,7 @@ def test_api_thumbnail_initiate_upload_instructor(self): thumbnail = ThumbnailFactory( id="4ab8079e-ff4d-4d06-9922-4929e4f7a6eb", video=video, upload_state="ready" ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) # Get the upload policy for this thumbnail # It should generate a key file with the Unix timestamp of the present time @@ -101,7 +101,7 @@ def test_api_thumbnail_initiate_upload_instructor_read_only(self): thumbnail = ThumbnailFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=thumbnail.video, + resource=thumbnail.video.playlist, permissions__can_update=False, ) @@ -117,7 +117,7 @@ def test_api_thumbnail_initiate_upload_file_too_large(self): """It should not be possible to upload a thumbnail if its size is too large.""" video = VideoFactory(upload_state="ready") thumbnail = ThumbnailFactory(video=video, upload_state="ready") - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) # Get the upload policy for this thumbnail # It should generate a key file with the Unix timestamp of the present time diff --git a/src/backend/marsha/core/tests/api/thumbnails/test_options.py b/src/backend/marsha/core/tests/api/thumbnails/test_options.py index 2d89548043..3a10e0c2a6 100644 --- a/src/backend/marsha/core/tests/api/thumbnails/test_options.py +++ b/src/backend/marsha/core/tests/api/thumbnails/test_options.py @@ -61,7 +61,7 @@ def test_api_thumbnail_options_as_instructor(self): can query the thumbnail options' endpoint. """ video = VideoFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.options( self._options_url(video), HTTP_AUTHORIZATION=f"Bearer {jwt_token}" @@ -76,7 +76,7 @@ def test_api_thumbnail_options_as_student(self): can query the thumbnail options' endpoint. """ video = VideoFactory() - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) response = self.client.options( self._options_url(video), HTTP_AUTHORIZATION=f"Bearer {jwt_token}" diff --git a/src/backend/marsha/core/tests/api/thumbnails/test_retrieve.py b/src/backend/marsha/core/tests/api/thumbnails/test_retrieve.py index 0fc73a2cf9..1a33a14f56 100644 --- a/src/backend/marsha/core/tests/api/thumbnails/test_retrieve.py +++ b/src/backend/marsha/core/tests/api/thumbnails/test_retrieve.py @@ -172,7 +172,7 @@ def test_api_thumbnail_read_detail_student(self): video = VideoFactory() thumbnail = ThumbnailFactory(video=video) - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) response = self.client.get( self._get_url(video, thumbnail), @@ -186,7 +186,7 @@ def test_api_thumbnail_instructor_read_detail_in_read_only(self): thumbnail = ThumbnailFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=thumbnail.video, + resource=thumbnail.video.playlist, permissions__can_update=False, ) @@ -205,7 +205,7 @@ def test_api_thumbnail_read_detail_token_user(self): ) thumbnail = ThumbnailFactory(video=video, upload_state="pending") - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.get( self._get_url(thumbnail.video, thumbnail), @@ -232,7 +232,7 @@ def test_api_thumbnail_administrator_read_detail_in_read_only(self): thumbnail = ThumbnailFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=thumbnail.video, + resource=thumbnail.video.playlist, permissions__can_update=False, ) @@ -252,7 +252,7 @@ def test_api_thumbnail_read_detail_admin_user(self): thumbnail = ThumbnailFactory(video=video, upload_state="pending") jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, roles=["administrator"], ) @@ -289,7 +289,7 @@ def test_api_thumbnail_read_ready_thumbnail(self): upload_state="ready", ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) response = self.client.get( self._get_url(video, thumbnail), diff --git a/src/backend/marsha/core/tests/api/timed_text_tracks/test_create.py b/src/backend/marsha/core/tests/api/timed_text_tracks/test_create.py index 7d65356a67..c9b7f2977c 100644 --- a/src/backend/marsha/core/tests/api/timed_text_tracks/test_create.py +++ b/src/backend/marsha/core/tests/api/timed_text_tracks/test_create.py @@ -31,7 +31,7 @@ def test_api_timed_text_track_create_anonymous(self): def test_api_timed_text_track_create_token_user(self): """A token user should be able to create a timed text track for an existing video.""" video = VideoFactory(id="f8c30d0d-2bb4-440d-9e8d-f4b231511f1f") - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) data = {"language": "fr", "size": 10} response = self.client.post( @@ -60,7 +60,7 @@ def test_api_timed_text_track_create_token_user(self): def test_api_timed_text_create_token_user_track_no_size(self): """A token user shouldn't be able to create a track if size param is not specified""" video = VideoFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) data = {"language": "fr"} response = self.client.post( @@ -80,7 +80,7 @@ def test_api_timed_text_create_token_user_track_no_size(self): def test_api_timed_text_track_create_token_user_file_too_large(self): """A token user shouldn't be able to create a timed text track with a too large size""" video = VideoFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=video.playlist) data = {"language": "fr", "video": str(video.pk), "size": 100} response = self.client.post( @@ -101,7 +101,7 @@ def test_api_timed_text_track_create_instructor_in_read_only(self): timed_text_track = TimedTextTrackFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=timed_text_track.video, + resource=timed_text_track.video.playlist, permissions__can_update=False, ) diff --git a/src/backend/marsha/core/tests/api/timed_text_tracks/test_delete.py b/src/backend/marsha/core/tests/api/timed_text_tracks/test_delete.py index fd47ad3faa..141d92eb59 100644 --- a/src/backend/marsha/core/tests/api/timed_text_tracks/test_delete.py +++ b/src/backend/marsha/core/tests/api/timed_text_tracks/test_delete.py @@ -74,7 +74,7 @@ def test_api_timed_text_track_delete_detail_token_user(self): # Delete the timed text tracks using the JWT token for timed_text_track in timed_text_tracks: jwt_token = InstructorOrAdminLtiTokenFactory( - resource=timed_text_track.video + resource=timed_text_track.video.playlist, ) response = self.client.delete( self._delete_url(timed_text_track.video, timed_text_track), @@ -111,7 +111,7 @@ def test_api_timed_text_track_delete_instructor_in_read_only(self): timed_text_track = TimedTextTrackFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=timed_text_track.video, + resource=timed_text_track.video.playlist, permissions__can_update=False, ) diff --git a/src/backend/marsha/core/tests/api/timed_text_tracks/test_initiate_upload.py b/src/backend/marsha/core/tests/api/timed_text_tracks/test_initiate_upload.py index 7a3555efcd..413b4c7676 100644 --- a/src/backend/marsha/core/tests/api/timed_text_tracks/test_initiate_upload.py +++ b/src/backend/marsha/core/tests/api/timed_text_tracks/test_initiate_upload.py @@ -46,7 +46,9 @@ def test_api_timed_text_track_initiate_upload_token_user(self): upload_state=random.choice(["ready", "error"]), mode="cc", ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=timed_text_track.video) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=timed_text_track.video.playlist + ) # Create other timed text tracks to check that their upload state are unaffected # Make sure we avoid unicty constraints by setting a different language @@ -149,7 +151,7 @@ def test_api_timed_text_track_instructor_initiate_upload_in_read_only(self): timed_text_track = TimedTextTrackFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=timed_text_track.video, + resource=timed_text_track.video.playlist, permissions__can_update=False, ) @@ -465,7 +467,7 @@ def test_api_timed_text_track_initiate_upload_file_too_large(self): upload_state=random.choice(["ready", "error"]), mode="cc", ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=track.video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=track.video.playlist) # Get the upload policy for this timed text track # It should generate a key file with the Unix timestamp of the present time diff --git a/src/backend/marsha/core/tests/api/timed_text_tracks/test_list.py b/src/backend/marsha/core/tests/api/timed_text_tracks/test_list.py index 733ec4ebb0..714900072a 100644 --- a/src/backend/marsha/core/tests/api/timed_text_tracks/test_list.py +++ b/src/backend/marsha/core/tests/api/timed_text_tracks/test_list.py @@ -38,7 +38,7 @@ def test_api_timed_text_track_read_list_token_user(self): TimedTextTrackFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=timed_text_track_one.video + resource=timed_text_track_one.video.playlist ) response = self.client.get( diff --git a/src/backend/marsha/core/tests/api/timed_text_tracks/test_options.py b/src/backend/marsha/core/tests/api/timed_text_tracks/test_options.py index c0c846447e..8dc0948df5 100644 --- a/src/backend/marsha/core/tests/api/timed_text_tracks/test_options.py +++ b/src/backend/marsha/core/tests/api/timed_text_tracks/test_options.py @@ -66,7 +66,7 @@ def test_api_timed_text_track_options_as_instructor(self): """The details of choices fields should be available via http options for an instructor.""" timed_text_track = TimedTextTrackFactory(language="af") jwt_token = InstructorOrAdminLtiTokenFactory( - resource=timed_text_track.video, + resource=timed_text_track.video.playlist, permissions__can_update=False, ) @@ -75,7 +75,7 @@ def test_api_timed_text_track_options_as_instructor(self): def test_api_timed_text_track_options_as_student(self): """The details of choices fields should be available via http options for a student.""" timed_text_track = TimedTextTrackFactory(language="af") - jwt_token = StudentLtiTokenFactory(resource=timed_text_track.video) + jwt_token = StudentLtiTokenFactory(resource=timed_text_track.video.playlist) self.assert_jwt_can_query_options(jwt_token, timed_text_track) @@ -83,7 +83,7 @@ def test_api_timed_text_track_options_as_administrator(self): """The details of choices fields should be available via http options for an admin.""" timed_text_track = TimedTextTrackFactory(language="af") jwt_token = InstructorOrAdminLtiTokenFactory( - resource=timed_text_track.video, + resource=timed_text_track.video.playlist, permissions__can_update=False, roles=["administrator"], ) @@ -105,6 +105,8 @@ def test_api_timed_text_track_options_authenticated(self): upload_state=random.choice(["ready", "error"]), mode="cc", ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=timed_text_track.video) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=timed_text_track.video.playlist + ) self.assert_jwt_can_query_options(jwt_token, timed_text_track) diff --git a/src/backend/marsha/core/tests/api/timed_text_tracks/test_retrieve.py b/src/backend/marsha/core/tests/api/timed_text_tracks/test_retrieve.py index cac4b1a082..0aac2a9a76 100644 --- a/src/backend/marsha/core/tests/api/timed_text_tracks/test_retrieve.py +++ b/src/backend/marsha/core/tests/api/timed_text_tracks/test_retrieve.py @@ -41,7 +41,7 @@ def test_api_timed_text_track_read_detail_anonymous(self): def test_api_timed_text_track_read_detail_student(self): """Student users should not be allowed to read a timed text track detail.""" timed_text_track = TimedTextTrackFactory() - jwt_token = StudentLtiTokenFactory(resource=timed_text_track.video) + jwt_token = StudentLtiTokenFactory(resource=timed_text_track.video.playlist) # Get the timed text track using the JWT token response = self.client.get( self._get_url(timed_text_track.video, timed_text_track), @@ -66,7 +66,9 @@ def test_api_timed_text_track_read_detail_token_user(self): extension="srt", ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=timed_text_track.video) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=timed_text_track.video.playlist + ) # Get the timed text track using the JWT token response = self.client.get( @@ -123,7 +125,9 @@ def test_api_timed_text_track_without_extension_read_detail_token_user(self): upload_state="ready", ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=timed_text_track.video) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=timed_text_track.video.playlist + ) # Get the timed text track using the JWT token response = self.client.get( @@ -178,7 +182,7 @@ def test_api_timed_text_track_read_detail_admin_user(self): ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=timed_text_track.video, + resource=timed_text_track.video.playlist, roles=["administrator"], ) @@ -230,7 +234,7 @@ def test_api_timed_text_track_read_instructor_in_read_only(self): timed_text_track = TimedTextTrackFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=timed_text_track.video, + resource=timed_text_track.video.playlist, permissions__can_update=False, ) @@ -247,7 +251,9 @@ def test_api_timed_text_track_read_detail_token_user_no_active_stamp(self): Its "url" field should be set to None. """ timed_text_track = TimedTextTrackFactory(uploaded_on=None) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=timed_text_track.video) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=timed_text_track.video.playlist + ) # Get the timed text track using the JWT token response = self.client.get( @@ -265,7 +271,9 @@ def test_api_timed_text_track_read_detail_token_user_not_ready(self): timed_text_track = TimedTextTrackFactory( uploaded_on=None, upload_state=random.choice(["pending", "error", "ready"]) ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=timed_text_track.video) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=timed_text_track.video.playlist + ) # Get the timed_text_track linked to the JWT token response = self.client.get( @@ -293,7 +301,9 @@ def test_api_timed_text_track_read_detail_token_user_signed_urls(self, _mock_ope upload_state="ready", extension="srt", ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=timed_text_track.video) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=timed_text_track.video.playlist + ) # Get the timed_text_track via the API using the JWT token # fix the time so that the url signature is deterministic and can be checked diff --git a/src/backend/marsha/core/tests/api/timed_text_tracks/test_update.py b/src/backend/marsha/core/tests/api/timed_text_tracks/test_update.py index 55b4339c7a..9639ffa81a 100644 --- a/src/backend/marsha/core/tests/api/timed_text_tracks/test_update.py +++ b/src/backend/marsha/core/tests/api/timed_text_tracks/test_update.py @@ -36,7 +36,9 @@ def test_api_timed_text_track_update_detail_anonymous(self): def test_api_timed_text_track_update_detail_token_user_language(self): """Token users should be able to update the language of their timed_text_track.""" timed_text_track = TimedTextTrackFactory(language="fr") - jwt_token = InstructorOrAdminLtiTokenFactory(resource=timed_text_track.video) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=timed_text_track.video.playlist + ) response = self.client.get( self._update_url(timed_text_track.video, timed_text_track), @@ -57,7 +59,9 @@ def test_api_timed_text_track_update_detail_token_user_language(self): def test_api_timed_text_track_update_detail_token_user_closed_captioning(self): """Token users should be able to update the mode flag through the API.""" timed_text_track = TimedTextTrackFactory(mode="cc") - jwt_token = InstructorOrAdminLtiTokenFactory(resource=timed_text_track.video) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=timed_text_track.video.playlist + ) response = self.client.get( self._update_url(timed_text_track.video, timed_text_track), @@ -79,7 +83,9 @@ def test_api_timed_text_track_update_detail_token_user_closed_captioning(self): def test_api_timed_text_track_update_detail_token_user_active_stamp(self): """Token users trying to update "active_stamp" through the API should be ignored.""" timed_text_track = TimedTextTrackFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=timed_text_track.video) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=timed_text_track.video.playlist + ) response = self.client.get( self._update_url(timed_text_track.video, timed_text_track), @@ -102,7 +108,9 @@ def test_api_timed_text_track_update_detail_token_user_active_stamp(self): def test_api_timed_text_track_update_detail_token_user_upload_state(self): """Token users trying to update "upload_state" through the API should be ignored.""" timed_text_track = TimedTextTrackFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=timed_text_track.video) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=timed_text_track.video.playlist + ) response = self.client.get( self._update_url(timed_text_track.video, timed_text_track), @@ -127,7 +135,7 @@ def test_api_timed_text_track_update_instructor_in_read_only(self): timed_text_track = TimedTextTrackFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=timed_text_track.video, + resource=timed_text_track.video.playlist, permissions__can_update=False, ) @@ -313,7 +321,9 @@ def test_api_timed_text_track_patch_detail_token_user_stamp_and_state(self): These 2 fields can only be updated by AWS via the separate update-state API endpoint. """ timed_text_track = TimedTextTrackFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=timed_text_track.video) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=timed_text_track.video.playlist + ) self.assertEqual(timed_text_track.upload_state, "pending") self.assertIsNone(timed_text_track.uploaded_on) @@ -335,7 +345,9 @@ def test_api_timed_text_track_update_detail_token_id(self): """Token users trying to update the ID of a timed text track they own should be ignored.""" timed_text_track = TimedTextTrackFactory() original_id = timed_text_track.id - jwt_token = InstructorOrAdminLtiTokenFactory(resource=timed_text_track.video) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=timed_text_track.video.playlist + ) response = self.client.get( f"/api/videos/{timed_text_track.video.id}/timedtexttracks/{timed_text_track.id}/", @@ -358,7 +370,9 @@ def test_api_timed_text_track_update_detail_token_video(self): """Token users trying to update the video of a timed text track should be ignored.""" timed_text_track = TimedTextTrackFactory() original_video = timed_text_track.video - jwt_token = InstructorOrAdminLtiTokenFactory(resource=timed_text_track.video) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=timed_text_track.video.playlist + ) response = self.client.get( f"/api/videos/{timed_text_track.video.id}/timedtexttracks/{timed_text_track.id}/", @@ -381,7 +395,7 @@ def test_api_timed_text_track_update_detail_token_user_other_video(self): """Token users are not allowed to update a timed text track related to another video.""" other_video = VideoFactory() timed_text_track_update = TimedTextTrackFactory(language="en") - jwt_token = InstructorOrAdminLtiTokenFactory(resource=other_video) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=other_video.playlist) data = {"language": "fr", "size": 10} response = self.client.put( diff --git a/src/backend/marsha/core/tests/api/video/test_live_pairing.py b/src/backend/marsha/core/tests/api/video/test_live_pairing.py index 964ae687b8..ec00208085 100644 --- a/src/backend/marsha/core/tests/api/video/test_live_pairing.py +++ b/src/backend/marsha/core/tests/api/video/test_live_pairing.py @@ -269,7 +269,9 @@ def test_api_video_pairing_secret_post(self): """Post request is not allowed.""" live_pairing = LivePairingFactory() initial_secret = live_pairing.secret - jwt_token = InstructorOrAdminLtiTokenFactory(resource=live_pairing.video) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=live_pairing.video.playlist + ) response = self.client.post( f"/api/videos/{live_pairing.video.id}/pairing-secret/", @@ -286,7 +288,9 @@ def test_api_video_pairing_secret_patch(self): """Patch request is not allowed.""" live_pairing = LivePairingFactory() initial_secret = live_pairing.secret - jwt_token = InstructorOrAdminLtiTokenFactory(resource=live_pairing.video) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=live_pairing.video.playlist + ) response = self.client.patch( f"/api/videos/{live_pairing.video.id}/pairing-secret/", @@ -303,7 +307,9 @@ def test_api_video_pairing_secret_put(self): """Put request is not allowed.""" live_pairing = LivePairingFactory() initial_secret = live_pairing.secret - jwt_token = InstructorOrAdminLtiTokenFactory(resource=live_pairing.video) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=live_pairing.video.playlist + ) response = self.client.put( f"/api/videos/{live_pairing.video.id}/pairing-secret/", diff --git a/src/backend/marsha/core/tests/api/video/test_shared_live_media.py b/src/backend/marsha/core/tests/api/video/test_shared_live_media.py index e92283c357..b2e0f041f4 100644 --- a/src/backend/marsha/core/tests/api/video/test_shared_live_media.py +++ b/src/backend/marsha/core/tests/api/video/test_shared_live_media.py @@ -932,7 +932,7 @@ def test_api_video_shared_live_media_start_wrong_video_id(self): other_shared_live_media = SharedLiveMediaFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=other_shared_live_media.video, + resource=other_shared_live_media.video.playlist, ) response = self.client.patch( diff --git a/src/backend/marsha/core/tests/api/video/test_stats.py b/src/backend/marsha/core/tests/api/video/test_stats.py index 78de6c658f..74237f3076 100644 --- a/src/backend/marsha/core/tests/api/video/test_stats.py +++ b/src/backend/marsha/core/tests/api/video/test_stats.py @@ -173,7 +173,7 @@ def test_api_video_stats_student(self): video = VideoFactory() jwt_token = StudentLtiTokenFactory( - resource=video, + resource=video.playlist, context_id=str(video.playlist.lti_id), consumer_site=str(video.playlist.consumer_site.id), ) diff --git a/src/backend/marsha/core/tests/simple_jwt/test_tokens.py b/src/backend/marsha/core/tests/simple_jwt/test_tokens.py index a67e7e7742..aab360cd93 100644 --- a/src/backend/marsha/core/tests/simple_jwt/test_tokens.py +++ b/src/backend/marsha/core/tests/simple_jwt/test_tokens.py @@ -236,7 +236,9 @@ def test_for_live_session_anonymous(self): self.assertDictEqual(token.payload["permissions"], permissions) self.assertFalse(token.payload["maintenance"]) # settings.MAINTENANCE_MODE - self.assertEqual(token.payload["resource_id"], str(live_session.video.pk)) + self.assertEqual( + token.payload["resource_id"], str(live_session.video.playlist.pk) + ) self.assertDictEqual( token.payload["user"], { @@ -278,7 +280,7 @@ def test_for_live_session_lti(self): token.payload["consumer_site"], str(video.playlist.consumer_site.pk) ) self.assertEqual(token.payload["context_id"], str(video.playlist.lti_id)) - self.assertEqual(token.payload["resource_id"], str(video.pk)) + self.assertEqual(token.payload["resource_id"], str(video.playlist.pk)) self.assertDictEqual( token.payload["user"], { diff --git a/src/backend/marsha/core/tests/test_api_document.py b/src/backend/marsha/core/tests/test_api_document.py index dfeb1ecb18..8d014d9d72 100644 --- a/src/backend/marsha/core/tests/test_api_document.py +++ b/src/backend/marsha/core/tests/test_api_document.py @@ -42,7 +42,7 @@ def test_api_document_fetch_student(self): document = DocumentFactory() jwt_token = StudentLtiTokenFactory( - resource=document, + resource=document.playlist, permissions__can_update=True, ) @@ -69,7 +69,7 @@ def test_api_document_fetch_instructor(self): title="bar baz", ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=document) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=document.playlist) response = self.client.get( f"/api/documents/{document.id}/", @@ -104,7 +104,7 @@ def test_api_document_fetch_instructor_read_only(self): document = DocumentFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=document, + resource=document.playlist, permissions__can_update=False, ) @@ -128,7 +128,7 @@ def test_api_document_fetch_list_student(self): document = DocumentFactory() jwt_token = StudentLtiTokenFactory( - resource=document, + resource=document.playlist, permissions__can_update=True, ) @@ -141,7 +141,7 @@ def test_api_fetch_list_instructor(self): """An instrustor should not be able to fetch a document list.""" document = DocumentFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=document) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=document.playlist) response = self.client.get( "/api/documents/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}" @@ -158,7 +158,7 @@ def test_api_document_create_student(self): document = DocumentFactory() jwt_token = StudentLtiTokenFactory( - resource=document, + resource=document.playlist, permissions__can_update=True, ) @@ -183,7 +183,7 @@ def test_api_document_create_instructor(self): """An instrustor should not be able to create a document.""" document = DocumentFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=document) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=document.playlist) response = self.client.post( "/api/documents/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}" @@ -248,7 +248,7 @@ def test_api_document_delete_student(self): document = DocumentFactory() jwt_token = StudentLtiTokenFactory( - resource=document, + resource=document.playlist, permissions__can_update=True, ) @@ -262,7 +262,7 @@ def test_api_document_delete_instructor(self): """An instructor should be able to delete a document.""" document = DocumentFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=document) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=document.playlist) response = self.client.delete( f"/api/documents/{document.id}/", @@ -281,7 +281,7 @@ def test_api_document_update_student(self): document = DocumentFactory() jwt_token = StudentLtiTokenFactory( - resource=document, + resource=document.playlist, permissions__can_update=True, ) data = {"title": "new title"} @@ -299,7 +299,7 @@ def test_api_document_update_instructor_read_only(self): document = DocumentFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=document, + resource=document.playlist, permissions__can_update=False, ) data = {"title": "new title"} @@ -316,7 +316,7 @@ def test_api_document_update_instructor(self): """An instructor should be able to update a document.""" document = DocumentFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=document) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=document.playlist) data = {"title": "new title"} response = self.client.put( @@ -334,7 +334,7 @@ def test_api_document_update_title_with_extension(self): """Serializer should remove the extension from the document title (if any).""" document = DocumentFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=document) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=document.playlist) data = {"title": "new title.pdf"} response = self.client.put( @@ -359,7 +359,7 @@ def test_api_document_initiate_upload_student(self): document = DocumentFactory() jwt_token = StudentLtiTokenFactory( - resource=document, + resource=document.playlist, permissions__can_update=True, ) @@ -374,7 +374,7 @@ def test_api_document_initiate_upload_instructor_read_only(self): document = DocumentFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=document, + resource=document.playlist, permissions__can_update=False, ) @@ -391,7 +391,7 @@ def test_api_document_initiate_upload_instructor(self): upload_state=random.choice(["ready", "error"]), ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=document) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=document.playlist) now = datetime(2018, 8, 8, tzinfo=baseTimezone.utc) with mock.patch.object(timezone, "now", return_value=now), mock.patch( @@ -443,7 +443,7 @@ def test_api_document_initiate_upload_file_without_extension(self): upload_state=random.choice(["ready", "error"]), ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=document) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=document.playlist) now = datetime(2018, 8, 8, tzinfo=baseTimezone.utc) with mock.patch.object(timezone, "now", return_value=now), mock.patch( @@ -495,7 +495,7 @@ def test_api_document_initiate_upload_file_without_mimetype(self): upload_state=random.choice(["ready", "error"]), ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=document) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=document.playlist) now = datetime(2018, 8, 8, tzinfo=baseTimezone.utc) with mock.patch.object(timezone, "now", return_value=now), mock.patch( diff --git a/src/backend/marsha/core/tests/test_api_playlist_portability.py b/src/backend/marsha/core/tests/test_api_playlist_portability.py index 537f6d9583..5e608fd613 100644 --- a/src/backend/marsha/core/tests/test_api_playlist_portability.py +++ b/src/backend/marsha/core/tests/test_api_playlist_portability.py @@ -12,9 +12,9 @@ class PlaylistPortabilityAPITest(TestCase): """Test the API for playlist portability.""" - def _jwt_token(self, video): - """Build JWT token for a video with admin or instructor role.""" - jwt_token = InstructorOrAdminLtiTokenFactory(resource=video) + def _jwt_token(self, playlist): + """Build JWT token for a playlist with admin or instructor role.""" + jwt_token = InstructorOrAdminLtiTokenFactory(resource=playlist) return jwt_token def _patch_video(self, video, params): @@ -22,7 +22,7 @@ def _patch_video(self, video, params): return self.client.patch( f"/api/playlists/{video.playlist_id}/", json.dumps(params), - HTTP_AUTHORIZATION=f"Bearer {self._jwt_token(video)}", + HTTP_AUTHORIZATION=f"Bearer {self._jwt_token(video.playlist)}", content_type="application/json", ) diff --git a/src/backend/marsha/core/tests/test_api_xapi_statement.py b/src/backend/marsha/core/tests/test_api_xapi_statement.py index c8fc46c7fb..2cafae252f 100644 --- a/src/backend/marsha/core/tests/test_api_xapi_statement.py +++ b/src/backend/marsha/core/tests/test_api_xapi_statement.py @@ -29,7 +29,7 @@ def test_xapi_statement_api_with_anonymous_user(self): def test_xapi_statement_with_no_lrs_configured(self): """If no LRS configured a 200 status code should be returned.""" video = VideoFactory() - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) data = { "verb": { @@ -56,7 +56,7 @@ def test_xapi_statement_api_with_invalid_payload(self): playlist__consumer_site__lrs_url="http://lrs.com/data/xAPI", playlist__consumer_site__lrs_auth_token="Basic ThisIsABasicAuth", ) - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) data = {"foo": "bar"} @@ -107,7 +107,7 @@ def test_xapi_statement_with_request_error_to_lrs(self, xapi_send_mock): playlist__consumer_site__lrs_url="http://lrs.com/data/xAPI", playlist__consumer_site__lrs_auth_token="Basic ThisIsABasicAuth", ) - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) data = { "verb": { @@ -144,7 +144,7 @@ def test_xapi_statement_with_request_to_lrs_successful(self): playlist__consumer_site__lrs_url="http://lrs.com/data/xAPI", playlist__consumer_site__lrs_auth_token="Basic ThisIsABasicAuth", ) - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) data = { "verb": { @@ -172,7 +172,7 @@ def test_xapi_statement_with_missing_user(self): playlist__consumer_site__lrs_url="http://lrs.com/data/xAPI", playlist__consumer_site__lrs_auth_token="Basic ThisIsABasicAuth", ) - jwt_token = StudentLtiTokenFactory(resource=video) + jwt_token = StudentLtiTokenFactory(resource=video.playlist) del jwt_token.payload["user"] data = { @@ -202,7 +202,9 @@ def test_xapi_statement_document_resource(self): playlist__consumer_site__lrs_auth_token="Basic ThisIsABasicAuth", ) session_id = str(uuid.uuid4()) - jwt_token = StudentLtiTokenFactory(resource=document, session_id=session_id) + jwt_token = StudentLtiTokenFactory( + resource=document.playlist, session_id=session_id + ) data = { "verb": { diff --git a/src/backend/marsha/core/tests/views/test_lti_cache.py b/src/backend/marsha/core/tests/views/test_lti_cache.py index c65bca8667..593757f4d4 100644 --- a/src/backend/marsha/core/tests/views/test_lti_cache.py +++ b/src/backend/marsha/core/tests/views/test_lti_cache.py @@ -222,9 +222,9 @@ def test_views_direct_access_public_resource(self): elapsed, resource_origin = self._fetch_lti_request(url) self.assertEqual(resource_origin["id"], str(video.id)) - self.assertLess(elapsed, 0.1) + self.assertLess(elapsed, 0.2) - with self.assertNumQueries(2): + with self.assertNumQueries(3): elapsed, resource_origin = self._fetch_lti_request(url) self.assertEqual(resource_origin["id"], str(video.id)) - self.assertLess(elapsed, 0.1) + self.assertLess(elapsed, 0.2) diff --git a/src/backend/marsha/core/tests/views/test_public_video.py b/src/backend/marsha/core/tests/views/test_public_video.py index 3a52ec82a8..e467c4af31 100644 --- a/src/backend/marsha/core/tests/views/test_public_video.py +++ b/src/backend/marsha/core/tests/views/test_public_video.py @@ -405,7 +405,7 @@ def test_video_accessible_from_mail(self): "username": livesession.username, }, ) - self.assertEqual(jwt_token.payload["resource_id"], str(video.id)) + self.assertEqual(jwt_token.payload["resource_id"], str(video.playlist.id)) self.assertEqual(jwt_token.payload["locale"], "fr_FR") self.assertEqual(jwt_token.payload["context_id"], video.playlist.lti_id) self.assertEqual( @@ -455,7 +455,7 @@ def test_video_ressource_public_accessible_from_mail(self): }, ) self.assertEqual(jwt_token.payload["roles"], ["none"]) - self.assertEqual(jwt_token.payload["resource_id"], str(video.id)) + self.assertEqual(jwt_token.payload["resource_id"], str(video.playlist.id)) self.assertEqual(jwt_token.payload["locale"], "en_US") self.assertIsNone(jwt_token.payload.get("context_id")) self.assertIsNone(jwt_token.payload.get("consumer_site")) diff --git a/src/backend/marsha/websocket/consumers/video.py b/src/backend/marsha/websocket/consumers/video.py index 12f3aabe74..97379f9c79 100644 --- a/src/backend/marsha/websocket/consumers/video.py +++ b/src/backend/marsha/websocket/consumers/video.py @@ -45,7 +45,7 @@ async def _check_permissions(self): if isinstance(token, ResourceAccessToken): # With LTI: anyone with a valid token for the video can access - if token.payload.get("resource_id") != self.__get_video_id(): + if not await self._has_access_to_video(token): raise ConnectionRefusedError() elif isinstance(token, UserAccessToken): @@ -58,6 +58,13 @@ async def _check_permissions(self): else: raise RuntimeError("This should not happen") + @database_sync_to_async + def _has_access_to_video(self, token): + """Return if the user has access to the video.""" + return Video.objects.filter( + pk=self.__get_video_id(), playlist_id=token.payload.get("resource_id") + ).exists() + async def connect(self): """ Manage connection to this consumer. @@ -116,14 +123,16 @@ def retrieve_live_session(self): """Guess a live_session from the token and create it id not present.""" token = self.scope["token"] if LiveSessionServices.is_lti_token(token): - live_session, _ = LiveSessionServices.get_livesession_from_lti(token) + live_session, _ = LiveSessionServices.get_livesession_from_lti( + token, self.__get_video_id() + ) else: query_string = parse_qs(self.scope["query_string"]) if b"anonymous_id" not in query_string: raise ConnectionRefusedError() live_session, _ = LiveSessionServices.get_livesession_from_anonymous_id( anonymous_id=query_string[b"anonymous_id"][0].decode("utf-8"), - video_id=token.payload["resource_id"], + video_id=self.__get_video_id(), ) return live_session diff --git a/src/backend/marsha/websocket/tests/test_consumers_video.py b/src/backend/marsha/websocket/tests/test_consumers_video.py index 546764ac6c..d51193139d 100644 --- a/src/backend/marsha/websocket/tests/test_consumers_video.py +++ b/src/backend/marsha/websocket/tests/test_consumers_video.py @@ -300,7 +300,7 @@ async def test_connect_matching_video_anonymous(self): self.assertIsNone(live_session.channel_name) - jwt_token = ResourceAccessTokenFactory(resource=video) + jwt_token = ResourceAccessTokenFactory(resource=video.playlist) communicator = WebsocketCommunicator( base_application, @@ -331,7 +331,7 @@ async def test_connect_matching_video_admin(self): self.assertIsNone(live_session.channel_name) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.consumer_site.id), context_id=str(video.playlist.lti_id), user__id=live_session.lti_user_id, @@ -361,7 +361,7 @@ async def test_connect_no_matching_video(self): other_video = await self._get_video() jwt_token = StudentLtiTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.consumer_site.id), context_id=str(video.playlist.lti_id), ) @@ -432,7 +432,7 @@ async def test_video_update_channel_layer(self): ) jwt_token = StudentLtiTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.consumer_site.id), ) @@ -551,7 +551,7 @@ async def test_video_update_instructor_channel_layer(self): ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=video, + resource=video.playlist, consumer_site=str(video.consumer_site.id), ) @@ -684,7 +684,7 @@ async def test_thumbnail_update_channel_layer(self): thumbnail = await self._get_thumbnail() jwt_token = StudentLtiTokenFactory( - resource=thumbnail.video, + resource=thumbnail.video.playlist, consumer_site=str(thumbnail.video.consumer_site.id), ) @@ -728,7 +728,7 @@ async def test_timed_text_track_update_channel_layer(self): timed_text_track = await self._get_timed_text_track() jwt_token = StudentLtiTokenFactory( - resource=timed_text_track.video, + resource=timed_text_track.video.playlist, consumer_site=str(timed_text_track.video.consumer_site.id), ) From f32bb36053ce3132a78b75148f4c31cb1a55df52 Mon Sep 17 00:00:00 2001 From: Nicolas Clerc Date: Fri, 21 Jul 2023 14:12:17 +0200 Subject: [PATCH 4/7] =?UTF-8?q?=F0=9F=9B=82(backend)=20use=20playlist=20to?= =?UTF-8?q?ken=20in=20classroom=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. --- src/backend/marsha/bbb/api.py | 8 +- src/backend/marsha/bbb/permissions.py | 5 +- src/backend/marsha/bbb/serializers.py | 8 +- .../classroom/recordings/test_create_vod.py | 14 +- .../api/classroom/recordings/test_delete.py | 6 +- .../tests/api/classroom/test_bulk_destroy.py | 6 +- .../api/classroom/test_classroomdocuments.py | 6 +- .../bbb/tests/api/classroom/test_create.py | 4 +- .../bbb/tests/api/classroom/test_delete.py | 6 +- .../bbb/tests/api/classroom/test_list.py | 4 +- .../bbb/tests/api/classroom/test_retrieve.py | 122 +++++++++++++++++- .../api/classroom/test_service_create.py | 6 +- .../tests/api/classroom/test_service_end.py | 4 +- .../tests/api/classroom/test_service_join.py | 8 +- .../bbb/tests/api/classroom/test_update.py | 16 +-- .../api/classroomdocument/test_create.py | 10 +- .../api/classroomdocument/test_delete.py | 10 +- .../classroomdocument/test_initiate_upload.py | 10 +- .../api/classroomdocument/test_options.py | 6 +- .../api/classroomdocument/test_update.py | 8 +- 20 files changed, 194 insertions(+), 73 deletions(-) diff --git a/src/backend/marsha/bbb/api.py b/src/backend/marsha/bbb/api.py index 301656864a..2638cfc3cb 100644 --- a/src/backend/marsha/bbb/api.py +++ b/src/backend/marsha/bbb/api.py @@ -94,7 +94,7 @@ class ClassroomViewSet( permission_classes = [ ( - core_permissions.IsTokenResourceRouteObject + core_permissions.IsPlaylistToken & (core_permissions.IsTokenInstructor | core_permissions.IsTokenAdmin) ) | ( @@ -142,7 +142,8 @@ def get_permissions(self): ] elif self.action in ["retrieve", "service_join"]: permission_classes = [ - core_permissions.IsTokenResourceRouteObject + core_permissions.IsPlaylistToken + | core_permissions.IsTokenResourceRouteObject # needed for invite links | ( core_permissions.UserIsAuthenticated # asserts request.resource is None & ( @@ -188,7 +189,7 @@ def get_serializer_context(self): # For LTI | ( core_permissions.ResourceIsAuthenticated - & core_permissions.IsTokenResourceRouteObject + & core_permissions.IsPlaylistToken & ( core_permissions.IsTokenInstructor | core_permissions.IsTokenAdmin @@ -592,7 +593,6 @@ def initiate_upload(self, request, pk=None, classroom_id=None): serializer = serializers.ClassroomDocumentInitiateUploadSerializer( data=request.data ) - if serializer.is_valid() is not True: return Response(serializer.errors, status=400) diff --git a/src/backend/marsha/bbb/permissions.py b/src/backend/marsha/bbb/permissions.py index 4b8211526f..5e0f97dccf 100644 --- a/src/backend/marsha/bbb/permissions.py +++ b/src/backend/marsha/bbb/permissions.py @@ -2,6 +2,7 @@ from rest_framework import permissions +from marsha.bbb.models import Classroom from marsha.core import models, permissions as core_permissions @@ -33,7 +34,9 @@ def has_permission(self, request, view): if not request.resource: return False - return str(view.get_related_classroom_id()) == request.resource.id + return Classroom.objects.filter( + pk=view.get_related_classroom_id(), playlist_id=request.resource.id + ).exists() class BaseIsRelatedClassroomPlaylistRoleMixin: diff --git a/src/backend/marsha/bbb/serializers.py b/src/backend/marsha/bbb/serializers.py index 31b8212067..c6b80aa02f 100644 --- a/src/backend/marsha/bbb/serializers.py +++ b/src/backend/marsha/bbb/serializers.py @@ -296,13 +296,9 @@ def create(self, validated_data): The "validated_data" dictionary is returned after modification. """ - resource = self.context["request"].resource - classroom_id = self.context["request"].data.get("classroom_id") + classroom_id = self.context["view"].get_related_classroom_id() if not validated_data.get("classroom_id"): - if resource: - validated_data["classroom_id"] = resource.id - elif classroom_id: - validated_data["classroom_id"] = classroom_id + validated_data["classroom_id"] = classroom_id if not ClassroomDocument.objects.filter( classroom_id=validated_data["classroom_id"] diff --git a/src/backend/marsha/bbb/tests/api/classroom/recordings/test_create_vod.py b/src/backend/marsha/bbb/tests/api/classroom/recordings/test_create_vod.py index dc2d5ca9ed..b0f47ab6f4 100644 --- a/src/backend/marsha/bbb/tests/api/classroom/recordings/test_create_vod.py +++ b/src/backend/marsha/bbb/tests/api/classroom/recordings/test_create_vod.py @@ -78,7 +78,7 @@ def test_api_classroom_recording_create_anonymous_unknown_recording(self): def test_api_classroom_recording_create_vod_student(self): """Students should not be able to convert a recording to a VOD.""" recording = ClassroomRecordingFactory() - jwt_token = StudentLtiTokenFactory(resource=recording.classroom) + jwt_token = StudentLtiTokenFactory(resource=recording.classroom.playlist) with mock.patch("marsha.bbb.api.invoke_lambda_convert"): response = self.client.post( @@ -99,7 +99,9 @@ def test_api_classroom_recording_create_vod_instructor_or_admin(self): recording = ClassroomRecordingFactory( started_at="2019-08-21T15:00:02Z", ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=recording.classroom) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=recording.classroom.playlist + ) self.assertEqual(Video.objects.count(), 0) @@ -152,7 +154,9 @@ def test_api_classroom_recording_create_vod_instructor_or_admin_unknown_recordin recording = ClassroomRecordingFactory( started_at="2019-08-21T15:00:02Z", ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=recording.classroom) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=recording.classroom.playlist + ) self.assertEqual(Video.objects.count(), 0) @@ -367,7 +371,9 @@ def test_api_classroom_recording_create_vod_instructor_or_admin_inactive_convers started_at="2019-08-21T15:00:02Z", classroom__playlist__consumer_site__inactive_features=["vod_convert"], ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=recording.classroom) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=recording.classroom.playlist + ) self.assertEqual(Video.objects.count(), 0) diff --git a/src/backend/marsha/bbb/tests/api/classroom/recordings/test_delete.py b/src/backend/marsha/bbb/tests/api/classroom/recordings/test_delete.py index 10a2a55417..9cb5f2e775 100644 --- a/src/backend/marsha/bbb/tests/api/classroom/recordings/test_delete.py +++ b/src/backend/marsha/bbb/tests/api/classroom/recordings/test_delete.py @@ -68,7 +68,7 @@ def test_api_delete_classroom_recording_anonymous(self, delete_recording_mock): def test_api_delete_classroom_recording_student(self, delete_recording_mock): """Students should not be able to delete a recording.""" recording = ClassroomRecordingFactory() - jwt_token = StudentLtiTokenFactory(resource=recording.classroom) + jwt_token = StudentLtiTokenFactory(resource=recording.classroom.playlist) self.assertEqual(ClassroomRecording.objects.count(), 1) response = self.client.delete( @@ -93,7 +93,9 @@ def test_api_delete_classroom_recording_instructor_or_admin( recording = ClassroomRecordingFactory( started_at="2019-08-21T15:00:02Z", ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=recording.classroom) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=recording.classroom.playlist + ) self.assertEqual(ClassroomRecording.objects.count(), 1) response = self.client.delete( diff --git a/src/backend/marsha/bbb/tests/api/classroom/test_bulk_destroy.py b/src/backend/marsha/bbb/tests/api/classroom/test_bulk_destroy.py index e9aebbbf89..aaab0d437e 100644 --- a/src/backend/marsha/bbb/tests/api/classroom/test_bulk_destroy.py +++ b/src/backend/marsha/bbb/tests/api/classroom/test_bulk_destroy.py @@ -52,7 +52,7 @@ def test_api_classroom_bulk_delete_student(self): classroom2 = ClassroomFactory() jwt_token = StudentLtiTokenFactory( - resource=classroom1, + resource=classroom1.playlist, permissions__can_update=True, ) @@ -93,7 +93,7 @@ def test_api_classroom_bulk_delete_instructor(self): """LTI Token can't delete a list of classroom.""" classroom = ClassroomFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom.playlist) response = self.client.delete( self._api_url(), @@ -113,7 +113,7 @@ def test_api_classroom_bulk_delete_instructor_with_playlist_token(self): playlist = PlaylistFactory() classroom = ClassroomFactory(playlist=playlist) - jwt_token = PlaylistLtiTokenFactory(resource=classroom) + jwt_token = PlaylistLtiTokenFactory(resource=classroom.playlist) self.assertEqual(Classroom.objects.count(), 1) diff --git a/src/backend/marsha/bbb/tests/api/classroom/test_classroomdocuments.py b/src/backend/marsha/bbb/tests/api/classroom/test_classroomdocuments.py index da297bdc0f..e2f9686321 100644 --- a/src/backend/marsha/bbb/tests/api/classroom/test_classroomdocuments.py +++ b/src/backend/marsha/bbb/tests/api/classroom/test_classroomdocuments.py @@ -49,7 +49,7 @@ def test_api_list_classroom_documents_student(self): """A student should not be able to fetch a list of classroom documents.""" classroom = ClassroomFactory() ClassroomDocumentFactory.create_batch(3, classroom=classroom) - jwt_token = StudentLtiTokenFactory(resource=classroom) + jwt_token = StudentLtiTokenFactory(resource=classroom.playlist) response = self.client.get( f"/api/classrooms/{classroom.id}/classroomdocuments/", @@ -63,7 +63,7 @@ def test_api_list_classroom_documents_instructor(self): classroom_documents = ClassroomDocumentFactory.create_batch( 3, classroom=classroom ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom.playlist) response = self.client.get( f"/api/classrooms/{classroom.id}/classroomdocuments/?limit=2", @@ -118,7 +118,7 @@ def test_api_list_classroom_documents_instructor_urls(self): uploaded_on=now, ) ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom.playlist) response = self.client.get( f"/api/classrooms/{classroom.id}/classroomdocuments/", diff --git a/src/backend/marsha/bbb/tests/api/classroom/test_create.py b/src/backend/marsha/bbb/tests/api/classroom/test_create.py index fe9852004f..998333d922 100644 --- a/src/backend/marsha/bbb/tests/api/classroom/test_create.py +++ b/src/backend/marsha/bbb/tests/api/classroom/test_create.py @@ -51,7 +51,7 @@ def test_api_classroom_create_student(self): classroom = ClassroomFactory() jwt_token = StudentLtiTokenFactory( - resource=classroom, + resource=classroom.playlist, permissions__can_update=True, ) @@ -77,7 +77,7 @@ def test_api_classroom_create_instructor(self): """An instructor without playlist token should not be able to create a classroom.""" classroom = ClassroomFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom.playlist) response = self.client.post( "/api/classrooms/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}" diff --git a/src/backend/marsha/bbb/tests/api/classroom/test_delete.py b/src/backend/marsha/bbb/tests/api/classroom/test_delete.py index 02a9b19038..fb9e39e247 100644 --- a/src/backend/marsha/bbb/tests/api/classroom/test_delete.py +++ b/src/backend/marsha/bbb/tests/api/classroom/test_delete.py @@ -46,7 +46,7 @@ def test_api_classroom_delete_student(self): classroom = ClassroomFactory() jwt_token = StudentLtiTokenFactory( - resource=classroom, + resource=classroom.playlist, permissions__can_update=True, ) @@ -72,7 +72,7 @@ def test_api_classroom_delete_instructor(self): """An instructor without playlist token should not be able to delete a classroom.""" classroom = ClassroomFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom.playlist) response = self.client.delete( f"/api/classrooms/{classroom.id}/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}" @@ -86,7 +86,7 @@ def test_api_classroom_delete_instructor_with_playlist_token(self): playlist = PlaylistFactory() classroom = ClassroomFactory(playlist=playlist) - jwt_token = PlaylistLtiTokenFactory(resource=classroom) + jwt_token = PlaylistLtiTokenFactory(resource=classroom.playlist) self.assertEqual(Classroom.objects.count(), 1) diff --git a/src/backend/marsha/bbb/tests/api/classroom/test_list.py b/src/backend/marsha/bbb/tests/api/classroom/test_list.py index 6006db3262..bcd9916259 100644 --- a/src/backend/marsha/bbb/tests/api/classroom/test_list.py +++ b/src/backend/marsha/bbb/tests/api/classroom/test_list.py @@ -46,7 +46,7 @@ def test_api_classroom_fetch_list_student(self): classroom = ClassroomFactory() jwt_token = StudentLtiTokenFactory( - resource=classroom, + resource=classroom.playlist, permissions__can_update=True, ) @@ -59,7 +59,7 @@ def test_api_fetch_list_instructor(self): """An instructor should not be able to fetch a classroom list.""" classroom = ClassroomFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom.playlist) response = self.client.get( "/api/classrooms/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}" diff --git a/src/backend/marsha/bbb/tests/api/classroom/test_retrieve.py b/src/backend/marsha/bbb/tests/api/classroom/test_retrieve.py index ed5bd997c6..bef73f488f 100644 --- a/src/backend/marsha/bbb/tests/api/classroom/test_retrieve.py +++ b/src/backend/marsha/bbb/tests/api/classroom/test_retrieve.py @@ -8,6 +8,7 @@ from marsha.bbb import serializers from marsha.bbb.factories import ClassroomFactory, ClassroomRecordingFactory +from marsha.bbb.utils.tokens import create_classroom_stable_invite_jwt from marsha.core.factories import ( OrganizationAccessFactory, PlaylistAccessFactory, @@ -50,7 +51,7 @@ def test_api_classroom_fetch_student(self, mock_get_meeting_infos): "running": "true", } - jwt_token = StudentLtiTokenFactory(resource=classroom) + jwt_token = StudentLtiTokenFactory(resource=classroom.playlist) response = self.client.get( f"/api/classrooms/{classroom.id!s}/", @@ -108,7 +109,7 @@ def test_api_classroom_fetch_student_with_recordings(self, mock_get_meeting_info "running": "true", } - jwt_token = StudentLtiTokenFactory(resource=classroom) + jwt_token = StudentLtiTokenFactory(resource=classroom.playlist) response = self.client.get( f"/api/classrooms/{classroom.id!s}/", @@ -157,7 +158,7 @@ def test_api_classroom_fetch_from_other_classroom(self): classroom = ClassroomFactory() other_classroom = ClassroomFactory() - jwt_token = StudentLtiTokenFactory(resource=other_classroom) + jwt_token = StudentLtiTokenFactory(resource=other_classroom.playlist) response = self.client.get( f"/api/classrooms/{classroom.id!s}/", @@ -182,7 +183,7 @@ def test_api_classroom_fetch_student_scheduled(self, mock_get_meeting_infos): "running": "true", } - jwt_token = StudentLtiTokenFactory(resource=classroom) + jwt_token = StudentLtiTokenFactory(resource=classroom.playlist) response = self.client.get( f"/api/classrooms/{classroom.id!s}/", @@ -232,7 +233,7 @@ def test_api_classroom_fetch_instructor(self, mock_get_meeting_infos): "running": "true", } - jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom.playlist) response = self.client.get( f"/api/classrooms/{classroom.id!s}/", @@ -473,7 +474,7 @@ def test_api_classroom_fetch_with_recordings(self, mock_get_meeting_infos): "running": "true", } - jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom.playlist) response = self.client.get( f"/api/classrooms/{classroom.id!s}/", @@ -545,7 +546,7 @@ def test_api_classroom_fetch_from_LTI_inactive_conversion( "running": "true", } - jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom.playlist) response = self.client.get( f"/api/classrooms/{classroom.id!s}/", @@ -584,3 +585,110 @@ def test_api_classroom_fetch_from_standalone_site_inactive_conversion( self.assertEqual(response.status_code, 200) self.assertFalse(response.json()["vod_conversion_enabled"]) + + @mock.patch.object(serializers, "get_meeting_infos") + def test_api_classroom_fetch_public_invite(self, mock_get_meeting_infos): + """A public invited user should be allowed to fetch a classroom.""" + classroom = ClassroomFactory() + mock_get_meeting_infos.return_value = { + "returncode": "SUCCESS", + "running": "true", + } + + jwt_token = create_classroom_stable_invite_jwt(classroom) + + response = self.client.get( + f"/api/classrooms/{classroom.id!s}/", + HTTP_AUTHORIZATION=f"Bearer {jwt_token}", + ) + self.assertEqual(response.status_code, 200) + content = json.loads(response.content) + self.assertDictEqual( + { + "id": str(classroom.id), + "infos": {"returncode": "SUCCESS", "running": "true"}, + "lti_id": str(classroom.lti_id), + "title": classroom.title, + "description": classroom.description, + "started": False, + "ended": False, + "meeting_id": str(classroom.meeting_id), + "welcome_text": classroom.welcome_text, + "playlist": { + "id": str(classroom.playlist.id), + "title": classroom.playlist.title, + "lti_id": classroom.playlist.lti_id, + }, + "starting_at": None, + "estimated_duration": None, + "public_token": None, + "instructor_token": None, + "recordings": [], + "retention_date": None, + "enable_waiting_room": False, + "enable_chat": True, + "enable_presentation_supports": True, + "enable_recordings": True, + "recording_purpose": classroom.recording_purpose, + "enable_shared_notes": True, + "vod_conversion_enabled": True, + }, + content, + ) + + @mock.patch.object(serializers, "get_meeting_infos") + def test_api_classroom_fetch_moderator_invite(self, mock_get_meeting_infos): + """A moderator invited user should be allowed to fetch a classroom.""" + classroom = ClassroomFactory() + mock_get_meeting_infos.return_value = { + "returncode": "SUCCESS", + "running": "true", + } + + jwt_token = create_classroom_stable_invite_jwt( + classroom, + role=INSTRUCTOR, + permissions={ + "can_update": True, + "can_access_dashboard": True, + }, + ) + + response = self.client.get( + f"/api/classrooms/{classroom.id!s}/", + HTTP_AUTHORIZATION=f"Bearer {jwt_token}", + ) + self.assertEqual(response.status_code, 200) + content = json.loads(response.content) + self.assertDictEqual( + { + "id": str(classroom.id), + "infos": {"returncode": "SUCCESS", "running": "true"}, + "lti_id": str(classroom.lti_id), + "title": classroom.title, + "description": classroom.description, + "started": False, + "ended": False, + "meeting_id": str(classroom.meeting_id), + "welcome_text": classroom.welcome_text, + "playlist": { + "id": str(classroom.playlist.id), + "title": classroom.playlist.title, + "lti_id": classroom.playlist.lti_id, + }, + "starting_at": None, + "estimated_duration": None, + "public_token": None, + "instructor_token": None, + "recordings": [], + "retention_date": None, + "enable_waiting_room": False, + "enable_chat": True, + "enable_presentation_supports": True, + "enable_recordings": True, + "recording_purpose": classroom.recording_purpose, + "enable_shared_notes": True, + "vod_conversion_enabled": True, + }, + content, + ) diff --git a/src/backend/marsha/bbb/tests/api/classroom/test_service_create.py b/src/backend/marsha/bbb/tests/api/classroom/test_service_create.py index 4229f983a6..463f0143f5 100644 --- a/src/backend/marsha/bbb/tests/api/classroom/test_service_create.py +++ b/src/backend/marsha/bbb/tests/api/classroom/test_service_create.py @@ -67,7 +67,7 @@ def test_api_bbb_create_student(self, mock_create_request): """A student should not be able to create a classroom.""" classroom = ClassroomFactory() - jwt_token = StudentLtiTokenFactory(resource=classroom) + jwt_token = StudentLtiTokenFactory(resource=classroom.playlist) response = self.client.patch( f"/api/classrooms/{classroom.id}/create/", @@ -86,7 +86,7 @@ def test_api_bbb_create_new_classroom( mock_get_meeting_infos.return_value = {"returncode": "SUCCESS"} mock_create_request.return_value = {"returncode": "SUCCESS"} - jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom.playlist) data = {"title": "new title", "welcome_text": "Hello"} response = self.client.patch( @@ -126,7 +126,7 @@ def test_api_bbb_create_existing_classroom( {"message": "A classroom already exists with that classroom ID."} ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom.playlist) data = {"title": classroom.title, "welcome_text": classroom.welcome_text} response = self.client.patch( diff --git a/src/backend/marsha/bbb/tests/api/classroom/test_service_end.py b/src/backend/marsha/bbb/tests/api/classroom/test_service_end.py index 94761e8667..db175688e0 100644 --- a/src/backend/marsha/bbb/tests/api/classroom/test_service_end.py +++ b/src/backend/marsha/bbb/tests/api/classroom/test_service_end.py @@ -70,7 +70,7 @@ def test_api_bbb_end_classroom_student(self, mock_end_request): """A student should not be able to end a classroom.""" classroom = ClassroomFactory() - jwt_token = StudentLtiTokenFactory(resource=classroom) + jwt_token = StudentLtiTokenFactory(resource=classroom.playlist) response = self.client.patch( f"/api/classrooms/{classroom.id}/end/", @@ -92,7 +92,7 @@ def test_api_bbb_end_classroom_instructor(self, mock_end_request): "returncode": "SUCCESS", } - jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom.playlist) response = self.client.patch( f"/api/classrooms/{classroom.id}/end/", diff --git a/src/backend/marsha/bbb/tests/api/classroom/test_service_join.py b/src/backend/marsha/bbb/tests/api/classroom/test_service_join.py index dd27eb08aa..1fd3bfd79d 100644 --- a/src/backend/marsha/bbb/tests/api/classroom/test_service_join.py +++ b/src/backend/marsha/bbb/tests/api/classroom/test_service_join.py @@ -76,7 +76,7 @@ def test_api_bbb_join_student(self): ) jwt_token = StudentLtiTokenFactory( - resource=classroom, + resource=classroom.playlist, consumer_site="consumer_site", user__id="user_id", ) @@ -106,7 +106,7 @@ def test_api_bbb_join_from_other_classroom(self): other_classroom = ClassroomFactory() jwt_token = StudentLtiTokenFactory( - resource=other_classroom, + resource=other_classroom.playlist, consumer_site="consumer_site", user__id="user_id", ) @@ -127,7 +127,7 @@ def test_api_bbb_join_instructor(self): ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=classroom, + resource=classroom.playlist, consumer_site="consumer_site", user__id="user_id", ) @@ -153,7 +153,7 @@ def test_api_bbb_join_instructor_no_fullname(self): title="Classroom 1", ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom.playlist) response = self.client.patch( f"/api/classrooms/{classroom.id}/join/", diff --git a/src/backend/marsha/bbb/tests/api/classroom/test_update.py b/src/backend/marsha/bbb/tests/api/classroom/test_update.py index bf2c098fbe..4318f14420 100644 --- a/src/backend/marsha/bbb/tests/api/classroom/test_update.py +++ b/src/backend/marsha/bbb/tests/api/classroom/test_update.py @@ -64,7 +64,7 @@ def test_api_classroom_update_student(self): """A student user should not be able to update a classroom.""" classroom = ClassroomFactory() - jwt_token = StudentLtiTokenFactory(resource=classroom) + jwt_token = StudentLtiTokenFactory(resource=classroom.playlist) data = {"title": "new title"} @@ -81,7 +81,7 @@ def test_api_classroom_update_instructor_read_only(self): classroom = ClassroomFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=classroom, + resource=classroom.playlist, permissions__can_update=False, ) data = {"title": "new title"} @@ -104,7 +104,7 @@ def test_api_classroom_update_instructor(self, mock_get_meeting_infos): "running": "true", } - jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom.playlist) data = {"title": "new title", "welcome_text": "Hello"} response = self.client.patch( @@ -129,7 +129,7 @@ def test_api_classroom_update_instructor_scheduling(self, mock_get_meeting_infos "running": "true", } - jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom.playlist) now = datetime(2018, 8, 8, tzinfo=zoneinfo.ZoneInfo("Europe/Paris")) # set microseconds to 0 to compare date surely as serializer truncate them @@ -203,7 +203,7 @@ def test_api_classroom_update_instructor_scheduling_past_date( "running": "true", } - jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom.playlist) now = datetime(2018, 8, 8, tzinfo=zoneinfo.ZoneInfo("Europe/Paris")) # set microseconds to 0 to compare date surely as serializer truncate them @@ -250,7 +250,7 @@ def test_api_classroom_update_put_instructor_scheduling( "running": "true", } - jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom.playlist) now = datetime(2018, 8, 8, tzinfo=baseTimezone.utc) # set microseconds to 0 to compare date surely as serializer truncate them starting_at = (now + timedelta(hours=1)).replace(microsecond=0) @@ -321,7 +321,7 @@ def test_api_classroom_update_starting_at_ended(self, mock_get_meeting_infos): "running": "true", } - jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom.playlist) now = datetime(2018, 8, 8, tzinfo=baseTimezone.utc) # set microseconds to 0 to compare date surely as serializer truncate them starting_at = (now + timedelta(hours=1)).replace(microsecond=0) @@ -350,7 +350,7 @@ def test_api_classroom_update_estimated_duration_ended( "running": "true", } - jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom.playlist) estimated_duration = timedelta(seconds=60) data = {"estimated_duration": estimated_duration} diff --git a/src/backend/marsha/bbb/tests/api/classroomdocument/test_create.py b/src/backend/marsha/bbb/tests/api/classroomdocument/test_create.py index 6583e332ff..ac9cc952b9 100644 --- a/src/backend/marsha/bbb/tests/api/classroomdocument/test_create.py +++ b/src/backend/marsha/bbb/tests/api/classroomdocument/test_create.py @@ -43,7 +43,7 @@ def test_api_classroom_document_create_student(self): """ classroom = ClassroomFactory() - jwt_token = StudentLtiTokenFactory(resource=classroom) + jwt_token = StudentLtiTokenFactory(resource=classroom.playlist) response = self.client.post( f"/api/classrooms/{classroom.id}/classroomdocuments/", @@ -69,7 +69,7 @@ def test_api_classroom_document_create_instructor_first_document(self): First created document should be the default one. """ classroom = ClassroomFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom.playlist) response = self.client.post( f"/api/classrooms/{classroom.id}/classroomdocuments/", @@ -110,7 +110,7 @@ def test_api_classroom_document_create_instructor_second_document(self): classroom=classroom, is_default=True, ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom.playlist) response = self.client.post( f"/api/classrooms/{classroom.id}/classroomdocuments/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}", @@ -346,7 +346,7 @@ def test_api_classroom_document_create_user_access_token_admin_other_playlist(se def test_api_classroom_document_create_file_too_large(self): """With a file size too large the request should fail""" classroom = ClassroomFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom.playlist) response = self.client.post( f"/api/classrooms/{classroom.id}/classroomdocuments/", @@ -370,7 +370,7 @@ def test_api_classroom_document_create_file_too_large(self): def test_api_classroom_document_create_file_no_size(self): """Without file size the request should fail""" classroom = ClassroomFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom.playlist) response = self.client.post( f"/api/classrooms/{classroom.id}/classroomdocuments/", diff --git a/src/backend/marsha/bbb/tests/api/classroomdocument/test_delete.py b/src/backend/marsha/bbb/tests/api/classroomdocument/test_delete.py index a374f1180a..82139e4c82 100644 --- a/src/backend/marsha/bbb/tests/api/classroomdocument/test_delete.py +++ b/src/backend/marsha/bbb/tests/api/classroomdocument/test_delete.py @@ -41,7 +41,9 @@ def test_api_classroom_document_delete_student(self): """ classroom_document = ClassroomDocumentFactory() - jwt_token = StudentLtiTokenFactory(resource=classroom_document.classroom) + jwt_token = StudentLtiTokenFactory( + resource=classroom_document.classroom.playlist + ) self.assertEqual(ClassroomDocument.objects.count(), 1) response = self.client.delete( @@ -61,7 +63,7 @@ def test_api_classroom_document_delete_instructor_document(self): """ classroom_document = ClassroomDocumentFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=classroom_document.classroom + resource=classroom_document.classroom.playlist ) self.assertEqual(ClassroomDocument.objects.count(), 1) @@ -95,7 +97,7 @@ def test_api_classroom_document_delete_instructor_first_document(self): classroom=classroom, is_default=False, ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom.playlist) self.assertEqual(ClassroomDocument.objects.count(), 3) response = self.client.delete( @@ -125,7 +127,7 @@ def test_api_classroom_document_delete_instructor_second_document(self): classroom=classroom, is_default=False, ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom.playlist) self.assertEqual(ClassroomDocument.objects.count(), 2) response = self.client.delete( diff --git a/src/backend/marsha/bbb/tests/api/classroomdocument/test_initiate_upload.py b/src/backend/marsha/bbb/tests/api/classroomdocument/test_initiate_upload.py index 82adec8944..d925f1d6e7 100644 --- a/src/backend/marsha/bbb/tests/api/classroomdocument/test_initiate_upload.py +++ b/src/backend/marsha/bbb/tests/api/classroomdocument/test_initiate_upload.py @@ -48,7 +48,7 @@ def test_api_classroom_document_initiate_upload_instructor(self): classroom__id="ed08da34-7447-4141-96ff-5740315d7b99", ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=classroom_document.classroom + resource=classroom_document.classroom.playlist ) now = datetime(2018, 8, 8, tzinfo=baseTimezone.utc) @@ -107,7 +107,7 @@ def test_api_classroom_document_initiate_upload_instructor_without_extension(sel classroom__id="ed08da34-7447-4141-96ff-5740315d7b99", ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=classroom_document.classroom + resource=classroom_document.classroom.playlist ) now = datetime(2018, 8, 8, tzinfo=baseTimezone.utc) @@ -166,7 +166,7 @@ def test_api_classroom_document_initiate_upload_instructor_without_mimetype(self classroom__id="ed08da34-7447-4141-96ff-5740315d7b99", ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=classroom_document.classroom + resource=classroom_document.classroom.playlist ) now = datetime(2018, 8, 8, tzinfo=baseTimezone.utc) @@ -197,7 +197,7 @@ def test_api_classroom_document_initiate_upload_instructor_wrong_mimetype(self): classroom__id="ed08da34-7447-4141-96ff-5740315d7b99", ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=classroom_document.classroom + resource=classroom_document.classroom.playlist ) now = datetime(2018, 8, 8, tzinfo=baseTimezone.utc) @@ -476,7 +476,7 @@ def test_api_classroom_document_initiate_upload_file_too_large(self): classroom__id="ed08da34-7447-4141-96ff-5740315d7b99", ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=classroom_document.classroom + resource=classroom_document.classroom.playlist ) now = datetime(2018, 8, 8, tzinfo=baseTimezone.utc) diff --git a/src/backend/marsha/bbb/tests/api/classroomdocument/test_options.py b/src/backend/marsha/bbb/tests/api/classroomdocument/test_options.py index a4c0be5ba7..712172a153 100644 --- a/src/backend/marsha/bbb/tests/api/classroomdocument/test_options.py +++ b/src/backend/marsha/bbb/tests/api/classroomdocument/test_options.py @@ -46,7 +46,9 @@ def test_api_classroom_document_options_as_student(self): """A student can fetch the classroom document options endpoint""" classroom_document = ClassroomDocumentFactory() - jwt_token = StudentLtiTokenFactory(resource=classroom_document) + jwt_token = StudentLtiTokenFactory( + resource=classroom_document.classroom.playlist + ) response = self.client.options( f"/api/classrooms/{classroom_document.classroom.id}/classroomdocuments/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}", @@ -58,7 +60,7 @@ def test_api_classroom_document_options_instructor(self): """An instructor can fetch the classroom document options endpoint""" classroom = ClassroomFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom.playlist) response = self.client.options( f"/api/classrooms/{classroom.id}/classroomdocuments/", diff --git a/src/backend/marsha/bbb/tests/api/classroomdocument/test_update.py b/src/backend/marsha/bbb/tests/api/classroomdocument/test_update.py index ddd1d0a74f..66b2076114 100644 --- a/src/backend/marsha/bbb/tests/api/classroomdocument/test_update.py +++ b/src/backend/marsha/bbb/tests/api/classroomdocument/test_update.py @@ -39,7 +39,9 @@ def setUpClass(cls): def test_api_classroom_document_update_student(self): """A student user should not be able to update a classroom_document.""" classroom_document = ClassroomDocumentFactory() - jwt_token = StudentLtiTokenFactory(resource=classroom_document.classroom) + jwt_token = StudentLtiTokenFactory( + resource=classroom_document.classroom.playlist + ) data = {"filename": "updated_name.pdf", "size": 100} response = self.client.patch( @@ -55,7 +57,7 @@ def test_api_classroom_document_update_instructor(self): """An instructor should be able to update a classroom_document.""" classroom_document = ClassroomDocumentFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=classroom_document.classroom + resource=classroom_document.classroom.playlist ) data = {"filename": "updated_name.pdf", "size": 100} @@ -88,7 +90,7 @@ def test_api_classroom_document_update_instructor_default(self): is_default=True, ) second_document = ClassroomDocumentFactory(classroom=classroom) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=classroom.playlist) data = {"is_default": True} response = self.client.patch( From f7cd10290e41b896b095268ca59aa48eb50decd6 Mon Sep 17 00:00:00 2001 From: Nicolas Clerc Date: Fri, 21 Jul 2023 15:27:26 +0200 Subject: [PATCH 5/7] =?UTF-8?q?=F0=9F=9B=82(backend)=20use=20playlist=20to?= =?UTF-8?q?ken=20in=20deposit=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. --- src/backend/marsha/deposit/api.py | 6 +++--- src/backend/marsha/deposit/permissions.py | 13 +++++-------- src/backend/marsha/deposit/serializers.py | 7 ++----- .../deposit/tests/api/depositedfiles/test_create.py | 10 +++++++--- .../deposit/tests/api/depositedfiles/test_delete.py | 6 ++++-- .../api/depositedfiles/test_initiate_upload.py | 12 +++++++++--- .../deposit/tests/api/depositedfiles/test_update.py | 6 ++++-- .../tests/api/filedepositories/test_create.py | 2 +- .../tests/api/filedepositories/test_delete.py | 6 +++--- .../api/filedepositories/test_depositedfiles.py | 8 ++++---- .../deposit/tests/api/filedepositories/test_list.py | 4 ++-- .../tests/api/filedepositories/test_retrieve.py | 6 +++--- .../tests/api/filedepositories/test_update.py | 6 +++--- .../marsha/deposit/tests/api/test_options.py | 4 ++-- 14 files changed, 52 insertions(+), 44 deletions(-) diff --git a/src/backend/marsha/deposit/api.py b/src/backend/marsha/deposit/api.py index e9ff7dbbdf..461982d494 100644 --- a/src/backend/marsha/deposit/api.py +++ b/src/backend/marsha/deposit/api.py @@ -107,7 +107,7 @@ def get_permissions(self): ] elif self.action in ["retrieve"]: permission_classes = [ - core_permissions.IsTokenResourceRouteObject + core_permissions.IsPlaylistToken & ( core_permissions.IsTokenInstructor | core_permissions.IsTokenAdmin @@ -118,7 +118,7 @@ def get_permissions(self): elif self.action in ["update", "partial_update", "destroy"]: permission_classes = [ ( - core_permissions.IsTokenResourceRouteObject + core_permissions.IsPlaylistToken & ( core_permissions.IsTokenInstructor | core_permissions.IsTokenAdmin @@ -243,7 +243,7 @@ def get_permissions(self): | core_permissions.IsTokenStudent | core_permissions.UserIsAuthenticated ] - elif self.action in ["create", "list", "metadata"]: + elif self.action in ["list", "metadata"]: permission_classes = [ core_permissions.IsTokenInstructor | core_permissions.IsTokenAdmin diff --git a/src/backend/marsha/deposit/permissions.py b/src/backend/marsha/deposit/permissions.py index 70f4771c97..6b1f308802 100644 --- a/src/backend/marsha/deposit/permissions.py +++ b/src/backend/marsha/deposit/permissions.py @@ -1,9 +1,8 @@ """Custom permission classes for the Deposit app.""" -from django.core.exceptions import ObjectDoesNotExist - from rest_framework import permissions from marsha.core import models +from marsha.deposit.models import FileDepository def _is_organization_admin(user_id, file_depository_id): @@ -59,12 +58,10 @@ def has_permission(self, request, view): """ if not request.resource: return False - try: - return ( - str(view.get_related_object().file_depository.id) == request.resource.id - ) - except ObjectDoesNotExist: - return False + + return FileDepository.objects.filter( + pk=view.get_related_filedepository_id(), playlist_id=request.resource.id + ).exists() class IsFileDepositoryPlaylistOrOrganizationAdmin(permissions.BasePermission): diff --git a/src/backend/marsha/deposit/serializers.py b/src/backend/marsha/deposit/serializers.py index b41adb6d54..3f0f8b2773 100644 --- a/src/backend/marsha/deposit/serializers.py +++ b/src/backend/marsha/deposit/serializers.py @@ -83,13 +83,10 @@ def create(self, validated_data): """ resource = self.context["request"].resource user = self.context["request"].user - file_depository_id = self.context["request"].data.get("file_depository_id") + file_depository_id = self.context["view"].get_related_filedepository_id() if not validated_data.get("file_depository_id"): - if resource: - validated_data["file_depository_id"] = resource.id - elif file_depository_id: - validated_data["file_depository_id"] = file_depository_id + validated_data["file_depository_id"] = file_depository_id if resource: validated_data["author_id"] = resource.user.get("id") diff --git a/src/backend/marsha/deposit/tests/api/depositedfiles/test_create.py b/src/backend/marsha/deposit/tests/api/depositedfiles/test_create.py index 4357171d8f..ada6fad42b 100644 --- a/src/backend/marsha/deposit/tests/api/depositedfiles/test_create.py +++ b/src/backend/marsha/deposit/tests/api/depositedfiles/test_create.py @@ -42,7 +42,7 @@ def test_api_deposited_file_create_student_with_user_fullname(self): """ file_depository = FileDepositoryFactory() - jwt_token = StudentLtiTokenFactory(resource=file_depository) + jwt_token = StudentLtiTokenFactory(resource=file_depository.playlist) response = self.client.post( f"/api/filedepositories/{file_depository.id}/depositedfiles/", @@ -89,7 +89,9 @@ def test_api_deposited_file_create_student_with_username(self): file_depository = FileDepositoryFactory() jwt_token = StudentLtiTokenFactory( - resource=file_depository, user__user_fullname=None, user__username="student" + resource=file_depository.playlist, + user__user_fullname=None, + user__username="student", ) response = self.client.post( @@ -121,7 +123,9 @@ def test_api_deposited_file_create_student_without_username(self): file_depository = FileDepositoryFactory() jwt_token = StudentLtiTokenFactory( - resource=file_depository, user__user_fullname=None, user__username=None + resource=file_depository.playlist, + user__user_fullname=None, + user__username=None, ) response = self.client.post( diff --git a/src/backend/marsha/deposit/tests/api/depositedfiles/test_delete.py b/src/backend/marsha/deposit/tests/api/depositedfiles/test_delete.py index ebb0a2658f..5a2412a3e6 100644 --- a/src/backend/marsha/deposit/tests/api/depositedfiles/test_delete.py +++ b/src/backend/marsha/deposit/tests/api/depositedfiles/test_delete.py @@ -29,7 +29,9 @@ class DepositedFileDeleteAPITest(TestCase): def test_api_deposited_file_delete_student(self): """A student user should not be able to delete a deposited_file.""" deposited_file = DepositedFileFactory() - jwt_token = StudentLtiTokenFactory(resource=deposited_file.file_depository) + jwt_token = StudentLtiTokenFactory( + resource=deposited_file.file_depository.playlist + ) self.assertEqual(DepositedFile.objects.count(), 1) response = self.client.delete( @@ -45,7 +47,7 @@ def test_api_deposited_file_delete_instructor(self): """An instructor should be able to delete a deposited_file.""" deposited_file = DepositedFileFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=deposited_file.file_depository + resource=deposited_file.file_depository.playlist ) self.assertEqual(DepositedFile.objects.count(), 1) diff --git a/src/backend/marsha/deposit/tests/api/depositedfiles/test_initiate_upload.py b/src/backend/marsha/deposit/tests/api/depositedfiles/test_initiate_upload.py index 7f59592b0d..3368264df1 100644 --- a/src/backend/marsha/deposit/tests/api/depositedfiles/test_initiate_upload.py +++ b/src/backend/marsha/deposit/tests/api/depositedfiles/test_initiate_upload.py @@ -49,7 +49,9 @@ def test_api_deposited_file_initiate_upload_student(self): upload_state=random.choice(["ready", "error"]), file_depository__id="ed08da34-7447-4141-96ff-5740315d7b99", ) - jwt_token = StudentLtiTokenFactory(resource=deposited_file.file_depository) + jwt_token = StudentLtiTokenFactory( + resource=deposited_file.file_depository.playlist + ) now = datetime(2018, 8, 8, tzinfo=baseTimezone.utc) with mock.patch.object(timezone, "now", return_value=now), mock.patch( @@ -106,7 +108,9 @@ def test_api_deposited_file_initiate_upload_file_without_size(self): upload_state=random.choice(["ready", "error"]), file_depository__id="ed08da34-7447-4141-96ff-5740315d7b99", ) - jwt_token = StudentLtiTokenFactory(resource=deposited_file.file_depository) + jwt_token = StudentLtiTokenFactory( + resource=deposited_file.file_depository.playlist + ) now = datetime(2018, 8, 8, tzinfo=baseTimezone.utc) with mock.patch.object(timezone, "now", return_value=now), mock.patch( @@ -134,7 +138,9 @@ def test_api_deposited_file_initiate_upload_file_too_large(self): upload_state=random.choice(["ready", "error"]), file_depository__id="ed08da34-7447-4141-96ff-5740315d7b99", ) - jwt_token = StudentLtiTokenFactory(resource=deposited_file.file_depository) + jwt_token = StudentLtiTokenFactory( + resource=deposited_file.file_depository.playlist + ) now = datetime(2018, 8, 8, tzinfo=baseTimezone.utc) with mock.patch.object(timezone, "now", return_value=now), mock.patch( diff --git a/src/backend/marsha/deposit/tests/api/depositedfiles/test_update.py b/src/backend/marsha/deposit/tests/api/depositedfiles/test_update.py index 1571bb8e36..091bfb0abb 100644 --- a/src/backend/marsha/deposit/tests/api/depositedfiles/test_update.py +++ b/src/backend/marsha/deposit/tests/api/depositedfiles/test_update.py @@ -38,7 +38,9 @@ def setUpClass(cls): def test_api_deposited_file_update_student(self): """A student user should not be able to update a deposited_file.""" deposited_file = DepositedFileFactory() - jwt_token = StudentLtiTokenFactory(resource=deposited_file.file_depository) + jwt_token = StudentLtiTokenFactory( + resource=deposited_file.file_depository.playlist + ) data = {"read": True} response = self.client.patch( @@ -54,7 +56,7 @@ def test_api_deposited_file_update_instructor(self): """An instructor should be able to update a deposited_file.""" deposited_file = DepositedFileFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=deposited_file.file_depository + resource=deposited_file.file_depository.playlist ) data = {"read": True} diff --git a/src/backend/marsha/deposit/tests/api/filedepositories/test_create.py b/src/backend/marsha/deposit/tests/api/filedepositories/test_create.py index 972c559237..6d1a299402 100644 --- a/src/backend/marsha/deposit/tests/api/filedepositories/test_create.py +++ b/src/backend/marsha/deposit/tests/api/filedepositories/test_create.py @@ -66,7 +66,7 @@ def test_api_file_depository_create_student_with_playlist_token(self): def test_api_file_depository_create_instructor(self): """An instructor without playlist token should not be able to create a file_depository.""" file_depository = FileDepositoryFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=file_depository) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=file_depository.playlist) response = self.client.post( "/api/filedepositories/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}" diff --git a/src/backend/marsha/deposit/tests/api/filedepositories/test_delete.py b/src/backend/marsha/deposit/tests/api/filedepositories/test_delete.py index c2ba1ec5b4..da261bf98f 100644 --- a/src/backend/marsha/deposit/tests/api/filedepositories/test_delete.py +++ b/src/backend/marsha/deposit/tests/api/filedepositories/test_delete.py @@ -54,7 +54,7 @@ def test_api_file_depository_delete_user_logged_in(self): def test_api_file_depository_delete_student(self): """A student user should not be able to delete a file_depository.""" file_depository = FileDepositoryFactory() - jwt_token = StudentLtiTokenFactory(resource=file_depository) + jwt_token = StudentLtiTokenFactory(resource=file_depository.playlist) self.assertEqual(FileDepository.objects.count(), 1) response = self.client.delete( @@ -69,7 +69,7 @@ def test_api_file_depository_delete_instructor_read_only(self): """An instructor should not be able to delete a file_depository in read_only.""" file_depository = FileDepositoryFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=file_depository, + resource=file_depository.playlist, permissions__can_update=False, ) @@ -85,7 +85,7 @@ def test_api_file_depository_delete_instructor_read_only(self): def test_api_file_depository_delete_instructor(self): """An instructor should be able to delete a file_depository.""" file_depository = FileDepositoryFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=file_depository) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=file_depository.playlist) self.assertEqual(FileDepository.objects.count(), 1) response = self.client.delete( diff --git a/src/backend/marsha/deposit/tests/api/filedepositories/test_depositedfiles.py b/src/backend/marsha/deposit/tests/api/filedepositories/test_depositedfiles.py index 7669825c18..ccd9ddc59b 100644 --- a/src/backend/marsha/deposit/tests/api/filedepositories/test_depositedfiles.py +++ b/src/backend/marsha/deposit/tests/api/filedepositories/test_depositedfiles.py @@ -51,7 +51,7 @@ def test_api_file_depository_list_deposited_files_student(self): DepositedFileFactory.create_batch(3, file_depository=file_depository) owned_deposited_file = DepositedFileFactory(file_depository=file_depository) jwt_token = StudentLtiTokenFactory( - resource=file_depository, + resource=file_depository.playlist, permissions__can_update=True, user__id=owned_deposited_file.author_id, user__full_username=owned_deposited_file.author_name, @@ -90,7 +90,7 @@ def test_api_file_depository_list_deposited_files_instructor(self): deposited_files = DepositedFileFactory.create_batch( 3, file_depository=file_depository ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=file_depository) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=file_depository.playlist) response = self.client.get( f"/api/filedepositories/{file_depository.id}/depositedfiles/?limit=2", @@ -140,7 +140,7 @@ def test_api_file_depository_list_deposited_files_instructor_filtered(self): deposited_files_new = DepositedFileFactory.create_batch( 2, file_depository=file_depository ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=file_depository) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=file_depository.playlist) response = self.client.get( f"/api/filedepositories/{file_depository.id}/depositedfiles/?limit=10", @@ -291,7 +291,7 @@ def test_api_file_depository_list_deposited_files_instructor_signed_urls(self): deposited_files = DepositedFileFactory.create_batch( 3, file_depository=file_depository, uploaded_on=now ) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=file_depository) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=file_depository.playlist) now = datetime(2021, 11, 30, tzinfo=baseTimezone.utc) with mock.patch.object(timezone, "now", return_value=now), mock.patch( diff --git a/src/backend/marsha/deposit/tests/api/filedepositories/test_list.py b/src/backend/marsha/deposit/tests/api/filedepositories/test_list.py index f398edb8f2..5c49bf5d99 100644 --- a/src/backend/marsha/deposit/tests/api/filedepositories/test_list.py +++ b/src/backend/marsha/deposit/tests/api/filedepositories/test_list.py @@ -43,7 +43,7 @@ def test_api_file_depository_fetch_list_student(self): """A student should not be able to fetch a list of file_depository.""" file_depository = FileDepositoryFactory() jwt_token = StudentLtiTokenFactory( - resource=file_depository, + resource=file_depository.playlist, permissions__can_update=True, ) @@ -55,7 +55,7 @@ def test_api_file_depository_fetch_list_student(self): def test_api_file_depository_fetch_list_instructor(self): """An instructor should not be able to fetch a file_depository list.""" file_depository = FileDepositoryFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=file_depository) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=file_depository.playlist) response = self.client.get( "/api/filedepositories/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}" diff --git a/src/backend/marsha/deposit/tests/api/filedepositories/test_retrieve.py b/src/backend/marsha/deposit/tests/api/filedepositories/test_retrieve.py index b41e45a6b2..69f95ed228 100644 --- a/src/backend/marsha/deposit/tests/api/filedepositories/test_retrieve.py +++ b/src/backend/marsha/deposit/tests/api/filedepositories/test_retrieve.py @@ -38,7 +38,7 @@ def setUpClass(cls): def test_api_file_depository_fetch_student(self): """A student should be allowed to fetch a file_depository.""" file_depository = FileDepositoryFactory() - jwt_token = StudentLtiTokenFactory(resource=file_depository) + jwt_token = StudentLtiTokenFactory(resource=file_depository.playlist) response = self.client.get( f"/api/filedepositories/{file_depository.id!s}/", @@ -68,7 +68,7 @@ def test_api_file_depository_fetch_from_other_file_depository(self): """ file_depository = FileDepositoryFactory() other_file_depository = FileDepositoryFactory() - jwt_token = StudentLtiTokenFactory(resource=other_file_depository) + jwt_token = StudentLtiTokenFactory(resource=other_file_depository.playlist) response = self.client.get( f"/api/filedepositories/{file_depository.id!s}/", @@ -79,7 +79,7 @@ def test_api_file_depository_fetch_from_other_file_depository(self): def test_api_file_depository_fetch_instructor(self): """An instructor should be able to fetch a file_depository.""" file_depository = FileDepositoryFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=file_depository) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=file_depository.playlist) response = self.client.get( f"/api/filedepositories/{file_depository.id!s}/", diff --git a/src/backend/marsha/deposit/tests/api/filedepositories/test_update.py b/src/backend/marsha/deposit/tests/api/filedepositories/test_update.py index 3b181efdf0..15cf962617 100644 --- a/src/backend/marsha/deposit/tests/api/filedepositories/test_update.py +++ b/src/backend/marsha/deposit/tests/api/filedepositories/test_update.py @@ -55,7 +55,7 @@ def test_api_file_depository_update_user_logged_in(self): def test_api_file_depository_update_student(self): """A student user should not be able to update a file_depository.""" file_depository = FileDepositoryFactory() - jwt_token = StudentLtiTokenFactory(resource=file_depository) + jwt_token = StudentLtiTokenFactory(resource=file_depository.playlist) data = {"title": "new title"} response = self.client.patch( @@ -70,7 +70,7 @@ def test_api_file_depository_update_instructor_read_only(self): """An instructor should not be able to update a file_depository in read_only.""" file_depository = FileDepositoryFactory() jwt_token = InstructorOrAdminLtiTokenFactory( - resource=file_depository, + resource=file_depository.playlist, permissions__can_update=False, ) data = {"title": "new title"} @@ -86,7 +86,7 @@ def test_api_file_depository_update_instructor_read_only(self): def test_api_file_depository_update_instructor(self): """An instructor should be able to update a file_depository.""" file_depository = FileDepositoryFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=file_depository) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=file_depository.playlist) data = {"title": "new title", "description": "Hello"} response = self.client.patch( diff --git a/src/backend/marsha/deposit/tests/api/test_options.py b/src/backend/marsha/deposit/tests/api/test_options.py index 7a91a5e26f..e7e111dbe6 100644 --- a/src/backend/marsha/deposit/tests/api/test_options.py +++ b/src/backend/marsha/deposit/tests/api/test_options.py @@ -29,7 +29,7 @@ def test_api_deposited_files_options_as_student(self): """A student can fetch the deposited files options endpoint""" file_depository = FileDepositoryFactory() - jwt_token = StudentLtiTokenFactory(resource=file_depository) + jwt_token = StudentLtiTokenFactory(resource=file_depository.playlist) response = self.client.options( f"/api/filedepositories/{file_depository.id}/depositedfiles/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}", @@ -42,7 +42,7 @@ def test_api_deposited_files_options_instructor(self): """An instructor can fetch the deposited files options endpoint""" file_depository = FileDepositoryFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=file_depository) + jwt_token = InstructorOrAdminLtiTokenFactory(resource=file_depository.playlist) response = self.client.options( f"/api/filedepositories/{file_depository.id}/depositedfiles/", From d05a06eb20ffa67139107d0457c1971a86f2033e Mon Sep 17 00:00:00 2001 From: Nicolas Clerc Date: Fri, 21 Jul 2023 17:00:43 +0200 Subject: [PATCH 6/7] =?UTF-8?q?=F0=9F=9B=82(backend)=20use=20playlist=20to?= =?UTF-8?q?ken=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. --- src/backend/marsha/markdown/api.py | 26 ++++++++--- src/backend/marsha/markdown/permissions.py | 44 +++++-------------- 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 | 12 +++-- .../api/markdown_documents/test_update.py | 8 ++-- .../test_update_translations.py | 6 ++- .../tests/api/markdown_images/test_create.py | 12 +++-- .../tests/api/markdown_images/test_delete.py | 12 +++-- .../markdown_images/test_initiate_upload.py | 10 +++-- .../api/markdown_images/test_retrieve.py | 18 +++++--- 14 files changed, 103 insertions(+), 78 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..85f7fae803 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,24 +33,23 @@ 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] The API view for which permissions are being checked @@ -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..35d622f707 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}/", @@ -117,7 +119,9 @@ def test_api_document_fetch_from_other_document(self): ) other_markdown_document = MarkdownDocumentFactory() - jwt_token = InstructorOrAdminLtiTokenFactory(resource=other_markdown_document) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=other_markdown_document.playlist + ) response = self.client.get( f"/api/markdown-documents/{markdown_document.pk}/", @@ -130,7 +134,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 602ba6b8f1..0d71c1342f 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 @@ -35,7 +35,7 @@ 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( f"/api/markdown-documents/{markdown_document.id}/markdown-images/", @@ -48,7 +48,9 @@ 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( f"/api/markdown-documents/{markdown_document.id}/markdown-images/", @@ -80,7 +82,9 @@ 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( f"/api/markdown-documents/{markdown_document.id}/markdown-images/", @@ -109,7 +113,7 @@ def test_api_markdown_image_instructor_create_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, ) 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 b2832d9c80..3d77546273 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 @@ -35,7 +35,9 @@ 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-documents/{markdown_image.markdown_document.id}" @@ -49,7 +51,7 @@ 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) @@ -84,7 +86,7 @@ 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, ) @@ -105,7 +107,9 @@ def test_api_markdown_image_delete_instructor_other_markdown_document(self): markdown_document_other = MarkdownDocumentFactory() markdown_image = MarkdownImageFactory(markdown_document=markdown_document_other) - jwt_token = InstructorOrAdminLtiTokenFactory(resource=markdown_document_token) + jwt_token = InstructorOrAdminLtiTokenFactory( + resource=markdown_document_token.playlist + ) response = self.client.delete( f"/api/markdown-documents/{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 efb983e280..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 @@ -38,7 +38,9 @@ def test_api_markdown_image_initiate_upload_anonymous(self): 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-documents/{markdown_image.markdown_document.id}" @@ -57,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 @@ -114,7 +118,7 @@ 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, ) 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 eccfed2b03..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 @@ -40,7 +40,9 @@ 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-documents/{markdown_image.markdown_document.id}" @@ -55,7 +57,7 @@ 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, ) @@ -79,7 +81,9 @@ 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-documents/{markdown_image.markdown_document.id}" @@ -108,7 +112,7 @@ 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, ) @@ -133,7 +137,7 @@ def test_api_markdown_image_read_detail_admin_user(self): ) jwt_token = InstructorOrAdminLtiTokenFactory( - resource=markdown_document, + resource=markdown_document.playlist, roles=["administrator"], ) @@ -172,7 +176,9 @@ 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-documents/{markdown_image.markdown_document.id}" From 42fcb94d9b88ecc0db9d5a4ee9f17937f334ef5b Mon Sep 17 00:00:00 2001 From: Nicolas Clerc Date: Fri, 21 Jul 2023 17:01:18 +0200 Subject: [PATCH 7/7] =?UTF-8?q?=E2=9C=85(backend)=20fix=20template=20missi?= =?UTF-8?q?ng=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When run locally, if the front has been built, this test fails. Overriding the static directory ensures the behavior when it is missing. --- src/backend/marsha/core/tests/views/test_site.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/backend/marsha/core/tests/views/test_site.py b/src/backend/marsha/core/tests/views/test_site.py index 9890994882..c5a13c2edc 100644 --- a/src/backend/marsha/core/tests/views/test_site.py +++ b/src/backend/marsha/core/tests/views/test_site.py @@ -48,6 +48,7 @@ def test_site_publicly_accessible(self): ) @override_switch("site", active=True) + @override_settings(BASE_STATIC_DIR="missing") def test_site_site_template_missing(self): """Test site with missing index file should return a 501.""" response = self.client.get("/")