def test_views_lti_development_post_bypass_lti_instructor_no_video(self):
        """When bypassing LTI, the "example.com" consumer site is automatically created."""
        data = {
            "resource_link_id": "example.com-123",
            "context_id": "course-v1:ufr+mathematics+00001",
            "roles": "instructor",
            "tool_consumer_instance_guid": "example.com",
            "user_id": "56255f3807599c377bf0e5bf072359fd",
        }
        response = self.client.post(
            "/lti/videos/{!s}".format(uuid.uuid4()),
            data,
            HTTP_REFERER="https://example.com",
        )
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "<html>")
        content = response.content.decode("utf-8")
        match = re.search(
            '<div id="marsha-frontend-data" data-context="(.*)">', content
        )

        context = json.loads(unescape(match.group(1)))
        jwt_token = AccessToken(context.get("jwt"))
        video = Video.objects.get()
        self.assertEqual(jwt_token.payload["resource_id"], str(video.id))
        self.assertEqual(jwt_token.payload["user_id"], data["user_id"])
        self.assertEqual(jwt_token.payload["context_id"], data["context_id"])
        self.assertEqual(jwt_token.payload["roles"], [data["roles"]])
        self.assertEqual(jwt_token.payload["locale"], "en_US")
        self.assertDictEqual(
            jwt_token.payload["course"],
            {"school_name": "ufr", "course_name": "mathematics", "course_run": "00001"},
        )
        self.assertEqual(context.get("state"), "success")

        self.assertEqual(
            context.get("resource"),
            {
                "active_stamp": None,
                "is_ready_to_show": False,
                "show_download": True,
                "description": video.description,
                "id": str(video.id),
                "upload_state": "pending",
                "timed_text_tracks": [],
                "thumbnail": None,
                "title": video.title,
                "urls": None,
                "should_use_subtitle_as_transcript": False,
                "has_transcript": False,
                "playlist": {
                    "title": "course-v1:ufr+mathematics+00001",
                    "lti_id": "course-v1:ufr+mathematics+00001",
                },
                "live_state": None,
                "live_info": {},
            },
        )
        self.assertEqual(context.get("modelName"), "videos")
        # The consumer site was created with a name and a domain name
        ConsumerSite.objects.get(name="example.com", domain="example.com")
Beispiel #2
0
    def test_list_playlists_by_logged_in_user_with_organization_memberships(self):
        """Organization members get all playlists they have access to."""
        user = factories.UserFactory()

        org_1 = factories.OrganizationFactory()
        org_1.users.add(user)
        playlist_1 = factories.PlaylistFactory(
            lti_id="playlist#one", organization=org_1, title="First playlist"
        )

        org_2 = factories.OrganizationFactory()
        org_2.users.add(user)
        playlist_2 = factories.PlaylistFactory(
            lti_id="playlist#two", organization=org_2, title="Second playlist"
        )

        # User is not a member of this organization
        org_3 = factories.OrganizationFactory()
        factories.PlaylistFactory(organization=org_3)

        jwt_token = AccessToken()
        jwt_token.payload["resource_id"] = str(user.id)
        jwt_token.payload["user"] = {
            "id": str(user.id),
            "username": user.username,
        }

        response = self.client.get(
            "/api/playlists/",
            HTTP_AUTHORIZATION=f"Bearer {jwt_token}",
        )

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.json()["count"], 2)
        self.assertEqual(
            response.json()["results"],
            [
                {
                    "consumer_site": str(playlist_1.consumer_site.id),
                    "created_by": None,
                    "duplicated_from": None,
                    "id": str(playlist_1.id),
                    "is_portable_to_consumer_site": False,
                    "is_portable_to_playlist": True,
                    "is_public": False,
                    "lti_id": "playlist#one",
                    "organization": str(org_1.id),
                    "portable_to": [],
                    "title": "First playlist",
                    "users": [],
                },
                {
                    "consumer_site": str(playlist_2.consumer_site.id),
                    "created_by": None,
                    "duplicated_from": None,
                    "id": str(playlist_2.id),
                    "is_portable_to_consumer_site": False,
                    "is_portable_to_playlist": True,
                    "is_public": False,
                    "lti_id": "playlist#two",
                    "organization": str(org_2.id),
                    "portable_to": [],
                    "title": "Second playlist",
                    "users": [],
                },
            ],
        )
Beispiel #3
0
    def test_api_video_read_detail_token_user(self):
        """Instructors should be able to read the detail of their video."""
        video = VideoFactory(
            resource_id="a2f27fde-973a-4e89-8dca-cc59e01d255c",
            uploaded_on=datetime(2018, 8, 8, tzinfo=pytz.utc),
            upload_state="ready",
        )
        timed_text_track = TimedTextTrackFactory(
            video=video,
            mode="cc",
            language="fr",
            uploaded_on=datetime(2018, 8, 8, tzinfo=pytz.utc),
            upload_state="ready",
        )

        jwt_token = AccessToken()
        jwt_token.payload["video_id"] = str(video.id)
        jwt_token.payload["roles"] = ["instructor"]

        # Get the video linked to the JWT token
        response = self.client.get(
            "/api/videos/{!s}/".format(video.id),
            HTTP_AUTHORIZATION="Bearer {!s}".format(jwt_token),
        )
        self.assertEqual(response.status_code, 200)
        content = json.loads(response.content)

        thumbnails_template = (
            "https://abc.cloudfront.net/{!s}/thumbnails/1533686400_{!s}.0000000.jpg"
        )
        thumbnails_dict = {
            str(rate): thumbnails_template.format(video.resource_id, rate)
            for rate in [144, 240, 480, 720, 1080]
        }

        mp4_template = "https://abc.cloudfront.net/{!s}/mp4/1533686400_{!s}.mp4"
        mp4_dict = {
            str(rate): mp4_template.format(video.resource_id, rate)
            for rate in [144, 240, 480, 720, 1080]
        }

        self.assertEqual(
            content,
            {
                "description":
                video.description,
                "id":
                str(video.id),
                "title":
                video.title,
                "active_stamp":
                "1533686400",
                "is_ready_to_play":
                True,
                "upload_state":
                "ready",
                "timed_text_tracks": [{
                    "active_stamp":
                    "1533686400",
                    "is_ready_to_play":
                    True,
                    "mode":
                    "cc",
                    "id":
                    str(timed_text_track.id),
                    "language":
                    "fr",
                    "upload_state":
                    "ready",
                    "url":
                    ("https://abc.cloudfront.net/a2f27fde-973a-4e89-8dca-cc59e01d255c/"
                     "timedtext/1533686400_fr_cc.vtt"),
                    "video":
                    str(video.id),
                }],
                "urls": {
                    "mp4":
                    mp4_dict,
                    "thumbnails":
                    thumbnails_dict,
                    "manifests": {
                        "dash":
                        ("https://abc.cloudfront.net/a2f27fde-973a-4e89-8dca-cc59e01d255c/"
                         "cmaf/1533686400.mpd"),
                        "hls":
                        ("https://abc.cloudfront.net/a2f27fde-973a-4e89-8dca-cc59e01d255c/"
                         "cmaf/1533686400.m3u8"),
                    },
                    "previews":
                    ("https://abc.cloudfront.net/a2f27fde-973a-4e89-8dca-cc59e01d255c/"
                     "previews/1533686400_100.jpg"),
                },
            },
        )

        # Try getting another video
        other_video = VideoFactory()
        response = self.client.get(
            "/api/videos/{!s}/".format(other_video.id),
            HTTP_AUTHORIZATION="Bearer {!s}".format(jwt_token),
        )
        self.assertEqual(response.status_code, 403)
        content = json.loads(response.content)
        self.assertEqual(
            content,
            {"detail": "You do not have permission to perform this action."})
Beispiel #4
0
 def authorize(self, user, **additional_headers):
     token = AccessToken.for_user(user)
     self.client.credentials(
         HTTP_AUTHORIZATION=f'{api_settings.AUTH_HEADER_TYPES[0]} {token}',
         **additional_headers
     )
Beispiel #5
0
    def test_access_tokens_are_not_added_to_outstanding_list(self):
        AccessToken.for_user(self.user)

        qs = OutstandingToken.objects.all()

        self.assertFalse(qs.exists())
Beispiel #6
0
    def test_views_lti_classroom_instructor_same_playlist(
            self, mock_get_consumer_site, mock_verify):
        """Validate the format of the response returned by the view for an instructor request."""
        passport = ConsumerSiteLTIPassportFactory()
        classroom = ClassroomFactory(
            playlist__lti_id="course-v1:ufr+mathematics+00001",
            playlist__consumer_site=passport.consumer_site,
            meeting_id="7a567d67-29d3-4547-96f3-035733a4dfaa",
        )
        data = {
            "resource_link_id": classroom.lti_id,
            "context_id": classroom.playlist.lti_id,
            "roles": random.choice(["instructor", "administrator"]),
            "oauth_consumer_key": passport.oauth_consumer_key,
            "user_id": "56255f3807599c377bf0e5bf072359fd",
            "lis_person_sourcedid": "jane_doe",
            "launch_presentation_locale": "fr",
        }

        mock_get_consumer_site.return_value = passport.consumer_site

        responses.add(
            responses.GET,
            "https://10.7.7.1/bigbluebutton/api/getMeetingInfo",
            match=[
                responses.matchers.query_param_matcher({
                    "meetingID":
                    "7a567d67-29d3-4547-96f3-035733a4dfaa",
                    "checksum":
                    "7f13332ec54e7df0a02d07904746cb5b8b330498",
                })
            ],
            body="""
            <response>
                <returncode>SUCCESS</returncode>
                <running>true</running>
            </response>
            """,
            status=200,
        )

        response = self.client.post(f"/lti/meetings/{classroom.pk}", data)
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "<html>")
        content = response.content.decode("utf-8")

        match = re.search(
            '<div id="marsha-frontend-data" data-context="(.*)">', content)

        context = json.loads(html.unescape(match.group(1)))
        jwt_token = AccessToken(context.get("jwt"))
        self.assertEqual(jwt_token.payload["resource_id"], str(classroom.id))
        self.assertEqual(
            jwt_token.payload["user"],
            {
                "email": None,
                "username": "******",
                "user_fullname": None,
                "id": "56255f3807599c377bf0e5bf072359fd",
            },
        )
        self.assertEqual(jwt_token.payload["context_id"], data["context_id"])
        self.assertEqual(jwt_token.payload["roles"], [data["roles"]])
        self.assertEqual(jwt_token.payload["locale"], "fr_FR")
        self.assertEqual(
            jwt_token.payload["permissions"],
            {
                "can_access_dashboard": True,
                "can_update": True
            },
        )
        self.assertEqual(context.get("state"), "success")
        self.assertIsNotNone(context.get("resource"))
        self.assertEqual(
            {
                "id": str(classroom.id),
                "infos": {
                    "returncode": "SUCCESS",
                    "running": "true"
                },
                "lti_id": str(classroom.lti_id),
                "meeting_id": str(classroom.meeting_id),
                "playlist": {
                    "id": str(classroom.playlist_id),
                    "lti_id": str(classroom.playlist.lti_id),
                    "title": classroom.playlist.title,
                },
                "started": False,
                "ended": False,
                "title": classroom.title,
                "description": classroom.description,
                "welcome_text": classroom.welcome_text,
                "starting_at": None,
                "estimated_duration": None,
            },
            context.get("resource"),
        )
        self.assertEqual(context.get("modelName"), "classrooms")
        self.assertEqual(context.get("appName"), "bbb")
        self.assertEqual(
            context.get("static"),
            {
                "img": {
                    "liveBackground": "/static/img/liveBackground.jpg",
                    "liveErrorBackground":
                    "/static/img/liveErrorBackground.jpg",
                    "bbbBackground": "/static/img/bbbBackground.png",
                    "bbbLogo": "/static/img/bbbLogo.png",
                },
                "svg": {
                    "icons": "/static/svg/icons.svg",
                },
            },
        )
        # Make sure we only go through LTI verification once as it is costly (getting passport +
        # signature)
        self.assertEqual(mock_verify.call_count, 1)
Beispiel #7
0
    def test_settings_json_is_uploaded___can_be_retrieved(self):
        with TemporaryDirectory() as d:
            with override_settings(MEDIA_ROOT=d):
                user = fake_user()
                models = fake_analysis_model()
                json_data = {
                    "model_settings": {
                        "event_set": {
                            "name":
                            "Event Set",
                            "desc":
                            "Either Probablistic or Historic",
                            "default":
                            "P",
                            "options": [{
                                "id": "P",
                                "desc": "Proabilistic"
                            }, {
                                "id": "H",
                                "desc": "Historic"
                            }]
                        },
                        "event_occurrence_id": {
                            "name": "Occurrence Set",
                            "desc": "PiWind Occurrence selection",
                            "default": "1",
                            "options": [{
                                "id": "1",
                                "desc": "Long Term"
                            }]
                        },
                        "boolean_parameters": [{
                            "name": "peril_wind",
                            "desc": "Boolean option",
                            "default": False
                        }, {
                            "name": "peril_surge",
                            "desc": "Boolean option",
                            "default": True
                        }],
                        "float_parameters": [{
                            "name": "float_1",
                            "desc": "Some float value",
                            "default": 0.5,
                            "max": 1.0,
                            "min": 0.0
                        }, {
                            "name": "float_2",
                            "desc": "Some float value",
                            "default": 0.3,
                            "max": 1.0,
                            "min": 0.0
                        }]
                    },
                    "lookup_settings": {
                        "supported_perils": [{
                            "id":
                            "WSS",
                            "desc":
                            "Single Peril: Storm Surge"
                        }, {
                            "id":
                            "WTC",
                            "desc":
                            "Single Peril: Tropical Cyclone"
                        }, {
                            "id":
                            "WW1",
                            "desc":
                            "Group Peril: Windstorm with storm surge"
                        }, {
                            "id":
                            "WW2",
                            "desc":
                            "Group Peril: Windstorm w/o storm surge"
                        }]
                    }
                }

                self.app.post(models.get_absolute_settings_url(),
                              headers={
                                  'Authorization':
                                  'Bearer {}'.format(
                                      AccessToken.for_user(user))
                              },
                              params=json.dumps(json_data),
                              content_type='application/json')

                response = self.app.get(
                    models.get_absolute_settings_url(),
                    headers={
                        'Authorization':
                        'Bearer {}'.format(AccessToken.for_user(user))
                    },
                )
                self.assertDictEqual.__self__.maxDiff = None
                self.assertDictEqual(json.loads(response.body), json_data)
                self.assertEqual(response.content_type, 'application/json')
Beispiel #8
0
    def test_api_video_with_a_thumbnail(self):
        """A video with a custom thumbnail should have it in its payload."""
        video = VideoFactory(
            pk="38a91911-9aee-41e2-94dd-573abda6f48f",
            uploaded_on=datetime(2018, 8, 8, tzinfo=pytz.utc),
            upload_state="ready",
        )
        thumbnail = ThumbnailFactory(
            video=video,
            uploaded_on=datetime(2018, 8, 8, tzinfo=pytz.utc),
            upload_state="ready",
        )

        jwt_token = AccessToken()
        jwt_token.payload["resource_id"] = str(video.id)
        jwt_token.payload["roles"] = [random.choice(["instructor", "administrator"])]
        jwt_token.payload["permissions"] = {"can_update": True}

        # Get the video linked to the JWT token
        response = self.client.get(
            "/api/videos/{!s}/".format(video.id),
            HTTP_AUTHORIZATION="Bearer {!s}".format(jwt_token),
        )
        self.assertEqual(response.status_code, 200)
        content = json.loads(response.content)

        self.assertEqual(
            content["thumbnail"],
            {
                "active_stamp": "1533686400",
                "id": str(thumbnail.id),
                "is_ready_to_show": True,
                "upload_state": "ready",
                "urls": {
                    "144": "https://abc.cloudfront.net/38a91911-9aee-41e2-94dd-573abda6f48f/"
                    "thumbnails/1533686400_144.jpg",
                    "240": "https://abc.cloudfront.net/38a91911-9aee-41e2-94dd-573abda6f48f/"
                    "thumbnails/1533686400_240.jpg",
                    "480": "https://abc.cloudfront.net/38a91911-9aee-41e2-94dd-573abda6f48f/"
                    "thumbnails/1533686400_480.jpg",
                    "720": "https://abc.cloudfront.net/38a91911-9aee-41e2-94dd-573abda6f48f/"
                    "thumbnails/1533686400_720.jpg",
                    "1080": "https://abc.cloudfront.net/38a91911-9aee-41e2-94dd-573abda6f48f/"
                    "thumbnails/1533686400_1080.jpg",
                },
                "video": str(video.id),
            },
        )

        self.assertEqual(
            content["urls"]["thumbnails"],
            {
                "144": "https://abc.cloudfront.net/38a91911-9aee-41e2-94dd-573abda6f48f/"
                "thumbnails/1533686400_144.jpg",
                "240": "https://abc.cloudfront.net/38a91911-9aee-41e2-94dd-573abda6f48f/"
                "thumbnails/1533686400_240.jpg",
                "480": "https://abc.cloudfront.net/38a91911-9aee-41e2-94dd-573abda6f48f/"
                "thumbnails/1533686400_480.jpg",
                "720": "https://abc.cloudfront.net/38a91911-9aee-41e2-94dd-573abda6f48f/"
                "thumbnails/1533686400_720.jpg",
                "1080": "https://abc.cloudfront.net/38a91911-9aee-41e2-94dd-573abda6f48f/"
                "thumbnails/1533686400_1080.jpg",
            },
        )
Beispiel #9
0
    def test_api_video_initiate_upload_token_user(self):
        """A token user associated to a video should be able to retrieve an upload policy."""
        video = VideoFactory(
            id="27a23f52-3379-46a2-94fa-697b59cfe3c7",
            upload_state=random.choice(["ready", "error"]),
        )
        jwt_token = AccessToken()
        jwt_token.payload["resource_id"] = str(video.id)
        jwt_token.payload["roles"] = [random.choice(["instructor", "administrator"])]
        jwt_token.payload["permissions"] = {"can_update": True}

        # Create another video to check that its upload state is unaffected
        other_video = VideoFactory(upload_state=random.choice(["ready", "error"]))

        # Get the upload policy for this video
        # It should generate a key file with the Unix timestamp of the present time
        now = datetime(2018, 8, 8, tzinfo=pytz.utc)
        with mock.patch.object(timezone, "now", return_value=now), mock.patch(
            "datetime.datetime"
        ) as mock_dt:
            mock_dt.utcnow = mock.Mock(return_value=now)
            response = self.client.post(
                "/api/videos/{!s}/initiate-upload/".format(video.id),
                HTTP_AUTHORIZATION="Bearer {!s}".format(jwt_token),
            )
        self.assertEqual(response.status_code, 200)
        self.assertEqual(
            json.loads(response.content),
            {
                "url": "https://test-marsha-source.s3.amazonaws.com/",
                "fields": {
                    "acl": "private",
                    "key": (
                        "27a23f52-3379-46a2-94fa-697b59cfe3c7/video/27a23f52-3379-46a2-94fa-"
                        "697b59cfe3c7/1533686400"
                    ),
                    "x-amz-algorithm": "AWS4-HMAC-SHA256",
                    "x-amz-credential": "aws-access-key-id/20180808/eu-west-1/s3/aws4_request",
                    "x-amz-date": "20180808T000000Z",
                    "policy": (
                        "eyJleHBpcmF0aW9uIjogIjIwMTgtMDgtMDlUMDA6MDA6MDBaIiwgImNvbmRpdGlvbnMiOiBbe"
                        "yJhY2wiOiAicHJpdmF0ZSJ9LCBbInN0YXJ0cy13aXRoIiwgIiRDb250ZW50LVR5cGUiLCAidm"
                        "lkZW8vIl0sIFsiY29udGVudC1sZW5ndGgtcmFuZ2UiLCAwLCAxMDczNzQxODI0XSwgeyJidWN"
                        "rZXQiOiAidGVzdC1tYXJzaGEtc291cmNlIn0sIHsia2V5IjogIjI3YTIzZjUyLTMzNzktNDZh"
                        "Mi05NGZhLTY5N2I1OWNmZTNjNy92aWRlby8yN2EyM2Y1Mi0zMzc5LTQ2YTItOTRmYS02OTdiN"
                        "TljZmUzYzcvMTUzMzY4NjQwMCJ9LCB7IngtYW16LWFsZ29yaXRobSI6ICJBV1M0LUhNQUMtU0"
                        "hBMjU2In0sIHsieC1hbXotY3JlZGVudGlhbCI6ICJhd3MtYWNjZXNzLWtleS1pZC8yMDE4MDg"
                        "wOC9ldS13ZXN0LTEvczMvYXdzNF9yZXF1ZXN0In0sIHsieC1hbXotZGF0ZSI6ICIyMDE4MDgw"
                        "OFQwMDAwMDBaIn1dfQ=="
                    ),
                    "x-amz-signature": (
                        "8db66b80ad0afcaef57542df9da257976ab21bc3b8b0105f3bb6bdafe95964b9"
                    ),
                },
            },
        )

        # The upload state of the timed text track should should have been reset
        video.refresh_from_db()
        self.assertEqual(video.upload_state, "pending")

        # Check that the other timed text tracks are not reset
        other_video.refresh_from_db()
        self.assertNotEqual(other_video.upload_state, "pending")

        # Try initiating an upload for the other video
        response = self.client.post(
            "/api/videos/{!s}/initiate-upload/".format(other_video.id),
            HTTP_AUTHORIZATION="Bearer {!s}".format(jwt_token),
        )
        self.assertEqual(response.status_code, 403)
        content = json.loads(response.content)
        self.assertEqual(
            content, {"detail": "You do not have permission to perform this action."}
        )
Beispiel #10
0
    def test_api_document_initiate_upload_file_without_extension(self):
        """An extension should be guessed from the mimetype."""
        document = DocumentFactory(
            id="27a23f52-3379-46a2-94fa-697b59cfe3c7",
            upload_state=random.choice(["ready", "error"]),
        )

        jwt_token = AccessToken()
        jwt_token.payload["resource_id"] = str(document.id)
        jwt_token.payload["roles"] = [
            random.choice(["instructor", "administrator"])
        ]
        jwt_token.payload["permissions"] = {"can_update": True}

        now = datetime(2018, 8, 8, tzinfo=timezone.utc)
        with mock.patch.object(
                timezone, "now",
                return_value=now), mock.patch("datetime.datetime") as mock_dt:
            mock_dt.utcnow = mock.Mock(return_value=now)
            response = self.client.post(
                f"/api/documents/{document.id}/initiate-upload/",
                {
                    "filename": "foo",
                    "mimetype": "application/pdf"
                },
                HTTP_AUTHORIZATION=f"Bearer {jwt_token}",
                content_type="application/json",
            )

        self.assertEqual(response.status_code, 200)
        self.assertEqual(
            json.loads(response.content),
            {
                "url": "https://test-marsha-source.s3.amazonaws.com/",
                "fields": {
                    "acl":
                    "private",
                    "key":
                    ("27a23f52-3379-46a2-94fa-697b59cfe3c7/document/27a23f52-3379-46a2-94fa-"
                     "697b59cfe3c7/1533686400.pdf"),
                    "x-amz-algorithm":
                    "AWS4-HMAC-SHA256",
                    "x-amz-credential":
                    "aws-access-key-id/20180808/eu-west-1/s3/aws4_request",
                    "x-amz-date":
                    "20180808T000000Z",
                    "policy":
                    ("eyJleHBpcmF0aW9uIjogIjIwMTgtMDgtMDlUMDA6MDA6MDBaIiwgImNvbmRpdGlvbnMiOiBbe"
                     "yJhY2wiOiAicHJpdmF0ZSJ9LCBbImNvbnRlbnQtbGVuZ3RoLXJhbmdlIiwgMCwgMTA3Mzc0MT"
                     "gyNF0sIHsiYnVja2V0IjogInRlc3QtbWFyc2hhLXNvdXJjZSJ9LCB7ImtleSI6ICIyN2EyM2Y"
                     "1Mi0zMzc5LTQ2YTItOTRmYS02OTdiNTljZmUzYzcvZG9jdW1lbnQvMjdhMjNmNTItMzM3OS00"
                     "NmEyLTk0ZmEtNjk3YjU5Y2ZlM2M3LzE1MzM2ODY0MDAucGRmIn0sIHsieC1hbXotYWxnb3Jpd"
                     "GhtIjogIkFXUzQtSE1BQy1TSEEyNTYifSwgeyJ4LWFtei1jcmVkZW50aWFsIjogImF3cy1hY2"
                     "Nlc3Mta2V5LWlkLzIwMTgwODA4L2V1LXdlc3QtMS9zMy9hd3M0X3JlcXVlc3QifSwgeyJ4LWF"
                     "tei1kYXRlIjogIjIwMTgwODA4VDAwMDAwMFoifV19"),
                    "x-amz-signature":
                    ("9ee691c89e2061c5f631b093e01e7faee1ffe71de4c9684fb83d810a3fca799e"
                     ),
                },
            },
        )
Beispiel #11
0
    def test_api_document_initiate_upload_file_without_mimetype(self):
        """With no mimetype the extension should be ignored."""
        document = DocumentFactory(
            id="27a23f52-3379-46a2-94fa-697b59cfe3c7",
            upload_state=random.choice(["ready", "error"]),
        )

        jwt_token = AccessToken()
        jwt_token.payload["resource_id"] = str(document.id)
        jwt_token.payload["roles"] = [
            random.choice(["instructor", "administrator"])
        ]
        jwt_token.payload["permissions"] = {"can_update": True}

        now = datetime(2018, 8, 8, tzinfo=timezone.utc)
        with mock.patch.object(
                timezone, "now",
                return_value=now), mock.patch("datetime.datetime") as mock_dt:
            mock_dt.utcnow = mock.Mock(return_value=now)
            response = self.client.post(
                f"/api/documents/{document.id}/initiate-upload/",
                {
                    "filename": "foo",
                    "mimetype": ""
                },
                HTTP_AUTHORIZATION=f"Bearer {jwt_token}",
                content_type="application/json",
            )

        self.assertEqual(response.status_code, 200)
        self.assertEqual(
            json.loads(response.content),
            {
                "url": "https://test-marsha-source.s3.amazonaws.com/",
                "fields": {
                    "acl":
                    "private",
                    "key":
                    ("27a23f52-3379-46a2-94fa-697b59cfe3c7/document/27a23f52-3379-46a2-94fa-"
                     "697b59cfe3c7/1533686400"),
                    "x-amz-algorithm":
                    "AWS4-HMAC-SHA256",
                    "x-amz-credential":
                    "aws-access-key-id/20180808/eu-west-1/s3/aws4_request",
                    "x-amz-date":
                    "20180808T000000Z",
                    "policy":
                    ("eyJleHBpcmF0aW9uIjogIjIwMTgtMDgtMDlUMDA6MDA6MDBaIiwgImNvbmRpdGlvbnMiOiBbe"
                     "yJhY2wiOiAicHJpdmF0ZSJ9LCBbImNvbnRlbnQtbGVuZ3RoLXJhbmdlIiwgMCwgMTA3Mzc0MT"
                     "gyNF0sIHsiYnVja2V0IjogInRlc3QtbWFyc2hhLXNvdXJjZSJ9LCB7ImtleSI6ICIyN2EyM2Y"
                     "1Mi0zMzc5LTQ2YTItOTRmYS02OTdiNTljZmUzYzcvZG9jdW1lbnQvMjdhMjNmNTItMzM3OS00"
                     "NmEyLTk0ZmEtNjk3YjU5Y2ZlM2M3LzE1MzM2ODY0MDAifSwgeyJ4LWFtei1hbGdvcml0aG0iO"
                     "iAiQVdTNC1ITUFDLVNIQTI1NiJ9LCB7IngtYW16LWNyZWRlbnRpYWwiOiAiYXdzLWFjY2Vzcy"
                     "1rZXktaWQvMjAxODA4MDgvZXUtd2VzdC0xL3MzL2F3czRfcmVxdWVzdCJ9LCB7IngtYW16LWR"
                     "hdGUiOiAiMjAxODA4MDhUMDAwMDAwWiJ9XX0="),
                    "x-amz-signature":
                    ("b952d4bcdd88a082e0cae8e01ea7754ef0959475887fd732d79e3a04d672a166"
                     ),
                },
            },
        )
Beispiel #12
0
 def setUp(self):
     self.user, self.password = create_test_user()
     self.token = str(AccessToken.for_user(self.user))
     self.token_header = 'JWT %s' % self.token
 def test_init(self):
     # Should set token type claim
     token = AccessToken()
     self.assertEqual(token[api_settings.TOKEN_TYPE_CLAIM], 'access')
    def test_views_lti_development_post_bypass_lti_instructor(self):
        """In development, passport creation and LTI verif can be bypassed for a instructor."""
        video = VideoFactory(
            playlist__consumer_site__domain="example.com",
            playlist__title="foo bar",
            playlist__lti_id="course-v1:ufr+mathematics+00001",
        )
        data = {
            "resource_link_id": video.lti_id,
            "context_id": video.playlist.lti_id,
            "roles": "instructor",
            "tool_consumer_instance_guid": "example.com",
            "context_title": "mathematics",
            "tool_consumer_instance_name": "ufr",
            "user_id": "56255f3807599c377bf0e5bf072359fd",
        }
        response = self.client.post(
            "/lti/videos/{!s}".format(video.pk),
            data,
            HTTP_REFERER="https://example.com",
        )
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "<html>")
        content = response.content.decode("utf-8")

        match = re.search(
            '<div id="marsha-frontend-data" data-context="(.*)">', content
        )

        context = json.loads(unescape(match.group(1)))
        jwt_token = AccessToken(context.get("jwt"))
        self.assertEqual(jwt_token.payload["resource_id"], str(video.id))
        self.assertEqual(jwt_token.payload["user_id"], data["user_id"])
        self.assertEqual(jwt_token.payload["context_id"], data["context_id"])
        self.assertEqual(jwt_token.payload["roles"], [data["roles"]])
        self.assertEqual(jwt_token.payload["locale"], "en_US")
        self.assertEqual(
            jwt_token.payload["permissions"],
            {"can_access_dashboard": True, "can_update": True},
        )
        self.assertDictEqual(
            jwt_token.payload["course"],
            {"school_name": "ufr", "course_name": "mathematics", "course_run": "00001"},
        )
        self.assertEqual(context.get("state"), "success")
        self.assertEqual(
            context.get("resource"),
            {
                "active_stamp": None,
                "is_ready_to_show": False,
                "show_download": True,
                "description": video.description,
                "id": str(video.id),
                "upload_state": "pending",
                "timed_text_tracks": [],
                "thumbnail": None,
                "title": video.title,
                "urls": None,
                "should_use_subtitle_as_transcript": False,
                "has_transcript": False,
                "playlist": {
                    "title": "foo bar",
                    "lti_id": "course-v1:ufr+mathematics+00001",
                },
                "live_state": None,
                "live_info": {},
            },
        )
        self.assertEqual(context.get("modelName"), "videos")
Beispiel #15
0
 def post(self, request):
     result = super(TokenAuthenticationView, self).post(request)
     user_id = AccessToken(result.data['access'])['user_id']
     User.objects.filter(pk=user_id).update(last_login=now())
     return result
    def test_views_lti_video_read_other_playlist(
        self, mock_get_consumer_site, mock_verify
    ):
        """A video from another portable playlist should have "can_update" set to False."""
        passport = ConsumerSiteLTIPassportFactory(consumer_site__domain="example.com")
        video = VideoFactory(
            id="301b5f4f-b9f1-4a5f-897d-f8f1bf22c396",
            playlist__is_portable_to_playlist=True,
            playlist__is_portable_to_consumer_site=True,
            playlist__title="playlist-003",
            upload_state=random.choice([s[0] for s in STATE_CHOICES]),
            uploaded_on="2019-09-24 07:24:40+00",
            resolutions=[144, 240, 480, 720, 1080],
        )
        data = {
            "resource_link_id": video.lti_id,
            "context_id": "another-playlist",
            "roles": "instructor",
            "oauth_consumer_key": passport.oauth_consumer_key,
            "user_id": "56255f3807599c377bf0e5bf072359fd",
            "launch_presentation_locale": "fr",
        }

        mock_get_consumer_site.return_value = passport.consumer_site
        response = self.client.post("/lti/videos/{!s}".format(video.pk), data)
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "<html>")
        content = response.content.decode("utf-8")

        match = re.search(
            '<div id="marsha-frontend-data" data-context="(.*)">', content
        )

        context = json.loads(unescape(match.group(1)))
        jwt_token = AccessToken(context.get("jwt"))
        self.assertEqual(
            jwt_token.payload["permissions"],
            {"can_access_dashboard": True, "can_update": False},
        )
        self.assertEqual(context.get("state"), "success")
        self.assertEqual(
            context.get("resource"),
            {
                "active_stamp": "1569309880",
                "is_ready_to_show": True,
                "show_download": True,
                "description": video.description,
                "id": str(video.id),
                "upload_state": video.upload_state,
                "timed_text_tracks": [],
                "thumbnail": None,
                "title": video.title,
                "urls": {
                    "mp4": {
                        "144": "https://abc.cloudfront.net/301b5f4f-b9f1-4a5f-897d-f8f1bf22c396/"
                        "mp4/1569309880_144.mp4?response-content-disposition=attachment%3B+"
                        "filename%3Dplaylist-003_1569309880.mp4",
                        "240": "https://abc.cloudfront.net/301b5f4f-b9f1-4a5f-897d-f8f1bf22c396/"
                        "mp4/1569309880_240.mp4?response-content-disposition=attachment%3B+"
                        "filename%3Dplaylist-003_1569309880.mp4",
                        "480": "https://abc.cloudfront.net/301b5f4f-b9f1-4a5f-897d-f8f1bf22c396/"
                        "mp4/1569309880_480.mp4?response-content-disposition=attachment%3B+"
                        "filename%3Dplaylist-003_1569309880.mp4",
                        "720": "https://abc.cloudfront.net/301b5f4f-b9f1-4a5f-897d-f8f1bf22c396/"
                        "mp4/1569309880_720.mp4?response-content-disposition=attachment%3B+"
                        "filename%3Dplaylist-003_1569309880.mp4",
                        "1080": "https://abc.cloudfront.net/301b5f4f-b9f1-4a5f-897d-f8f1bf22c396/"
                        "mp4/1569309880_1080.mp4?response-content-disposition=attachment%3B+"
                        "filename%3Dplaylist-003_1569309880.mp4",
                    },
                    "thumbnails": {
                        "144": "https://abc.cloudfront.net/301b5f4f-b9f1-4a5f-897d-f8f1bf22c396/"
                        "thumbnails/1569309880_144.0000000.jpg",
                        "240": "https://abc.cloudfront.net/301b5f4f-b9f1-4a5f-897d-f8f1bf22c396/"
                        "thumbnails/1569309880_240.0000000.jpg",
                        "480": "https://abc.cloudfront.net/301b5f4f-b9f1-4a5f-897d-f8f1bf22c396/"
                        "thumbnails/1569309880_480.0000000.jpg",
                        "720": "https://abc.cloudfront.net/301b5f4f-b9f1-4a5f-897d-f8f1bf22c396/"
                        "thumbnails/1569309880_720.0000000.jpg",
                        "1080": "https://abc.cloudfront.net/301b5f4f-b9f1-4a5f-897d-f8f1bf22c396/"
                        "thumbnails/1569309880_1080.0000000.jpg",
                    },
                    "manifests": {
                        "dash": "https://abc.cloudfront.net/301b5f4f-b9f1-4a5f-897d-f8f1bf22c396/"
                        "cmaf/1569309880.mpd",
                        "hls": "https://abc.cloudfront.net/301b5f4f-b9f1-4a5f-897d-f8f1bf22c396/"
                        "cmaf/1569309880.m3u8",
                    },
                    "previews": "https://abc.cloudfront.net/301b5f4f-b9f1-4a5f-897d-f8f1bf22c396/"
                    "previews/1569309880_100.jpg",
                },
                "should_use_subtitle_as_transcript": False,
                "has_transcript": False,
            },
        )
        self.assertEqual(context.get("modelName"), "videos")
        # Make sure we only go through LTI verification once as it is costly (getting passport +
        # signature)
        self.assertEqual(mock_verify.call_count, 1)
Beispiel #17
0
    def test_views_lti_classroom_student(self, mock_get_consumer_site,
                                         mock_verify):
        """Validate the response returned for a student request."""
        passport = ConsumerSiteLTIPassportFactory()
        classroom = ClassroomFactory(
            playlist__lti_id="course-v1:ufr+mathematics+00001",
            playlist__consumer_site=passport.consumer_site,
            meeting_id="7a567d67-29d3-4547-96f3-035733a4dfaa",
        )
        data = {
            "resource_link_id": classroom.lti_id,
            "context_id": classroom.playlist.lti_id,
            "roles": ["student"],
            "oauth_consumer_key": passport.oauth_consumer_key,
            "user_id": "56255f3807599c377bf0e5bf072359fd",
            "lis_person_sourcedid": "jane_doe",
            "launch_presentation_locale": "fr",
        }

        mock_get_consumer_site.return_value = passport.consumer_site

        responses.add(
            responses.GET,
            "https://10.7.7.1/bigbluebutton/api/getMeetingInfo",
            match=[
                responses.matchers.query_param_matcher({
                    "meetingID":
                    "7a567d67-29d3-4547-96f3-035733a4dfaa",
                    "checksum":
                    "7f13332ec54e7df0a02d07904746cb5b8b330498",
                })
            ],
            body="""
            <response>
                <returncode>SUCCESS</returncode>
                <running>true</running>
            </response>
            """,
            status=200,
        )

        response = self.client.post(f"/lti/classrooms/{classroom.id}", data)
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "<html>")
        content = response.content.decode("utf-8")

        match = re.search(
            '<div id="marsha-frontend-data" data-context="(.*)">', content)

        context = json.loads(html.unescape(match.group(1)))
        jwt_token = AccessToken(context.get("jwt"))
        self.assertEqual(context.get("state"), "success")
        self.assertIsNotNone(context.get("resource"))
        self.assertEqual(context.get("modelName"), "classrooms")
        self.assertEqual(
            jwt_token.payload["user"],
            {
                "email": None,
                "username": "******",
                "user_fullname": None,
                "id": "56255f3807599c377bf0e5bf072359fd",
            },
        )
        self.assertEqual(
            context.get("flags"),
            {
                "BBB": True,
                "live_raw": False,
                "markdown": True,
                "sentry": False
            },
        )
        self.assertEqual(
            context.get("static"),
            {
                "img": {
                    "liveBackground": "/static/img/liveBackground.jpg",
                    "liveErrorBackground":
                    "/static/img/liveErrorBackground.jpg",
                    "bbbBackground": "/static/img/bbbBackground.png",
                    "bbbLogo": "/static/img/bbbLogo.png",
                },
                "svg": {
                    "icons": "/static/svg/icons.svg",
                },
            },
        )

        # Make sure we only go through LTI verification once as it is costly (getting passport +
        # signature)
        self.assertEqual(mock_verify.call_count, 1)
    def test_views_lti_video_post_instructor(self, mock_get_consumer_site, mock_verify):
        """Validate the format of the response returned by the view for an instructor request."""
        passport = ConsumerSiteLTIPassportFactory()
        video = VideoFactory(
            playlist__lti_id="course-v1:ufr+mathematics+00001",
            playlist__consumer_site=passport.consumer_site,
        )
        data = {
            "resource_link_id": video.lti_id,
            "context_id": video.playlist.lti_id,
            "roles": "instructor",
            "oauth_consumer_key": passport.oauth_consumer_key,
            "user_id": "56255f3807599c377bf0e5bf072359fd",
            "launch_presentation_locale": "fr",
        }

        mock_get_consumer_site.return_value = passport.consumer_site
        response = self.client.post("/lti/videos/{!s}".format(video.pk), data)
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "<html>")
        content = response.content.decode("utf-8")

        match = re.search(
            '<div id="marsha-frontend-data" data-context="(.*)">', content
        )

        context = json.loads(unescape(match.group(1)))
        jwt_token = AccessToken(context.get("jwt"))
        self.assertEqual(jwt_token.payload["resource_id"], str(video.id))
        self.assertEqual(jwt_token.payload["user_id"], data["user_id"])
        self.assertEqual(jwt_token.payload["context_id"], data["context_id"])
        self.assertEqual(jwt_token.payload["roles"], [data["roles"]])
        self.assertEqual(jwt_token.payload["locale"], "fr_FR")
        self.assertEqual(
            jwt_token.payload["permissions"],
            {"can_access_dashboard": True, "can_update": True},
        )
        self.assertDictEqual(
            jwt_token.payload["course"],
            {"school_name": "ufr", "course_name": "mathematics", "course_run": "00001"},
        )

        self.assertEqual(context.get("state"), "success")
        self.assertEqual(
            context.get("static"), {"svg": {"plyr": "/static/svg/plyr.svg"}}
        )
        self.assertEqual(
            context.get("resource"),
            {
                "active_stamp": None,
                "is_ready_to_show": False,
                "show_download": True,
                "description": video.description,
                "id": str(video.id),
                "upload_state": "pending",
                "timed_text_tracks": [],
                "thumbnail": None,
                "title": video.title,
                "urls": None,
                "should_use_subtitle_as_transcript": False,
                "has_transcript": False,
            },
        )
        self.assertEqual(context.get("modelName"), "videos")
        self.assertEqual(context.get("sentry_dsn"), "https://sentry.dsn")
        self.assertEqual(context.get("environment"), "test")
        self.assertEqual(context.get("release"), "1.2.3")
        # Make sure we only go through LTI verification once as it is costly (getting passport +
        # signature)
        self.assertEqual(mock_verify.call_count, 1)
Beispiel #19
0
    def test_settings_json_is_not_valid___response_is_400(self):
        with TemporaryDirectory() as d:
            with override_settings(MEDIA_ROOT=d):
                user = fake_user()
                models = fake_analysis_model()
                json_data = {
                    "model_settings": {
                        "event_set": {
                            "name":
                            "Event Set",
                            "default":
                            "P",
                            "options": [{
                                "id": "P",
                                "desc": "Proabilistic"
                            }, {
                                "id": "H",
                                "desc": "Historic"
                            }]
                        },
                        "event_occurrence_id": {
                            "name": "Occurrence Set",
                            "desc": "PiWind Occurrence selection",
                            "default": 1,
                            "options": [{
                                "id": "1",
                                "desc": "Long Term"
                            }]
                        },
                        "boolean_parameters": [{
                            "name": "peril_wind",
                            "desc": "Boolean option",
                            "default": 1.1
                        }, {
                            "name": "peril_surge",
                            "desc": "Boolean option",
                            "default": True
                        }],
                        "float_parameter": [{
                            "name": "float_1",
                            "desc": "Some float value",
                            "default": False,
                            "max": 1.0,
                            "min": 0.0
                        }, {
                            "name": "float_2",
                            "desc": "Some float value",
                            "default": 0.3,
                            "max": 1.0,
                            "min": 0.0
                        }]
                    },
                    "lookup_settings": {
                        "supported_perils": [{
                            "i":
                            "WSS",
                            "desc":
                            "Single Peril: Storm Surge"
                        }, {
                            "id":
                            "WTC",
                            "des":
                            "Single Peril: Tropical Cyclone"
                        }, {
                            "id":
                            "WW11",
                            "desc":
                            "Group Peril: Windstorm with storm surge"
                        }, {
                            "id":
                            "WW2",
                            "desc":
                            "Group Peril: Windstorm w/o storm surge"
                        }]
                    }
                }

                response = self.app.post(
                    models.get_absolute_settings_url(),
                    headers={
                        'Authorization':
                        'Bearer {}'.format(AccessToken.for_user(user))
                    },
                    params=json.dumps(json_data),
                    content_type='application/json',
                    expect_errors=True,
                )

                validation_error = {
                    'model_settings': [
                        "Additional properties are not allowed ('float_parameter' was unexpected)"
                    ],
                    'model_settings-event_set':
                    ["'desc' is a required property"],
                    'model_settings-event_occurrence_id-default':
                    ["1 is not of type 'string'"],
                    'model_settings-boolean_parameters-0-default':
                    ["1.1 is not of type 'boolean'"],
                    'lookup_settings-supported_perils-0': [
                        "Additional properties are not allowed ('i' was unexpected)",
                        "'id' is a required property"
                    ],
                    'lookup_settings-supported_perils-1': [
                        "Additional properties are not allowed ('des' was unexpected)",
                        "'desc' is a required property"
                    ],
                    'lookup_settings-supported_perils-2-id':
                    ["'WW11' is too long"]
                }

                self.assertEqual(400, response.status_code)
                self.assertDictEqual.__self__.maxDiff = None
                self.assertDictEqual(json.loads(response.body),
                                     validation_error)
    def test_views_lti_video_post_student_with_video(
        self, mock_get_consumer_site, mock_verify
    ):
        """Validate the format of the response returned by the view for a student request."""
        passport = ConsumerSiteLTIPassportFactory()
        video = VideoFactory(
            id="59c0fc7a-0f64-46c0-993f-bdf47ecd837f",
            playlist__lti_id="course-v1:ufr+mathematics+00001",
            playlist__consumer_site=passport.consumer_site,
            playlist__title="playlist-002",
            upload_state=random.choice([s[0] for s in STATE_CHOICES]),
            uploaded_on="2019-09-24 07:24:40+00",
            resolutions=[144, 240, 480, 720, 1080],
        )
        data = {
            "resource_link_id": video.lti_id,
            "context_id": video.playlist.lti_id,
            "roles": "student",
            "oauth_consumer_key": passport.oauth_consumer_key,
            "user_id": "56255f3807599c377bf0e5bf072359fd",
        }
        mock_get_consumer_site.return_value = passport.consumer_site

        response = self.client.post("/lti/videos/{!s}".format(video.pk), data)
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "<html>")
        content = response.content.decode("utf-8")

        match = re.search(
            '<div id="marsha-frontend-data" data-context="(.*)">', content
        )

        context = json.loads(unescape(match.group(1)))
        jwt_token = AccessToken(context.get("jwt"))
        self.assertEqual(jwt_token.payload["resource_id"], str(video.id))
        self.assertEqual(jwt_token.payload["user_id"], data["user_id"])
        self.assertEqual(jwt_token.payload["context_id"], data["context_id"])
        self.assertEqual(jwt_token.payload["roles"], [data["roles"]])
        self.assertEqual(jwt_token.payload["locale"], "en_US")
        self.assertEqual(
            jwt_token.payload["permissions"],
            {"can_access_dashboard": False, "can_update": False},
        )
        self.assertDictEqual(
            jwt_token.payload["course"],
            {"school_name": "ufr", "course_name": "mathematics", "course_run": "00001"},
        )

        self.assertEqual(context.get("state"), "success")

        self.assertEqual(
            context.get("resource"),
            {
                "active_stamp": "1569309880",
                "is_ready_to_show": True,
                "show_download": True,
                "description": video.description,
                "id": str(video.id),
                "upload_state": video.upload_state,
                "timed_text_tracks": [],
                "thumbnail": None,
                "title": video.title,
                "urls": {
                    "mp4": {
                        "144": "https://abc.cloudfront.net/59c0fc7a-0f64-46c0-993f-bdf47ecd837f/"
                        "mp4/1569309880_144.mp4?response-content-disposition=attachment%3B+"
                        "filename%3Dplaylist-002_1569309880.mp4",
                        "240": "https://abc.cloudfront.net/59c0fc7a-0f64-46c0-993f-bdf47ecd837f/"
                        "mp4/1569309880_240.mp4?response-content-disposition=attachment%3B+"
                        "filename%3Dplaylist-002_1569309880.mp4",
                        "480": "https://abc.cloudfront.net/59c0fc7a-0f64-46c0-993f-bdf47ecd837f/"
                        "mp4/1569309880_480.mp4?response-content-disposition=attachment%3B+"
                        "filename%3Dplaylist-002_1569309880.mp4",
                        "720": "https://abc.cloudfront.net/59c0fc7a-0f64-46c0-993f-bdf47ecd837f/"
                        "mp4/1569309880_720.mp4?response-content-disposition=attachment%3B+"
                        "filename%3Dplaylist-002_1569309880.mp4",
                        "1080": "https://abc.cloudfront.net/59c0fc7a-0f64-46c0-993f-bdf47ecd837f/"
                        "mp4/1569309880_1080.mp4?response-content-disposition=attachment%3B+"
                        "filename%3Dplaylist-002_1569309880.mp4",
                    },
                    "thumbnails": {
                        "144": "https://abc.cloudfront.net/59c0fc7a-0f64-46c0-993f-bdf47ecd837f/"
                        "thumbnails/1569309880_144.0000000.jpg",
                        "240": "https://abc.cloudfront.net/59c0fc7a-0f64-46c0-993f-bdf47ecd837f/"
                        "thumbnails/1569309880_240.0000000.jpg",
                        "480": "https://abc.cloudfront.net/59c0fc7a-0f64-46c0-993f-bdf47ecd837f/"
                        "thumbnails/1569309880_480.0000000.jpg",
                        "720": "https://abc.cloudfront.net/59c0fc7a-0f64-46c0-993f-bdf47ecd837f/"
                        "thumbnails/1569309880_720.0000000.jpg",
                        "1080": "https://abc.cloudfront.net/59c0fc7a-0f64-46c0-993f-bdf47ecd837f/"
                        "thumbnails/1569309880_1080.0000000.jpg",
                    },
                    "manifests": {
                        "dash": "https://abc.cloudfront.net/59c0fc7a-0f64-46c0-993f-bdf47ecd837f/"
                        "cmaf/1569309880.mpd",
                        "hls": "https://abc.cloudfront.net/59c0fc7a-0f64-46c0-993f-bdf47ecd837f/"
                        "cmaf/1569309880.m3u8",
                    },
                    "previews": "https://abc.cloudfront.net/59c0fc7a-0f64-46c0-993f-bdf47ecd837f/"
                    "previews/1569309880_100.jpg",
                },
                "should_use_subtitle_as_transcript": False,
                "has_transcript": False,
            },
        )
        self.assertEqual(context.get("modelName"), "videos")
        # Make sure we only go through LTI verification once as it is costly (getting passport +
        # signature)
        self.assertEqual(mock_verify.call_count, 1)
Beispiel #21
0
    def _get_app_data(self):
        """Build app data for the frontend with information retrieved from the LTI launch request.

        Returns
        -------
        dictionary
            Configuration data to bootstrap the frontend:

            For all roles
            +++++++++++++

            - state: state of the LTI launch request. Can be one of `success` or `error`.
            - modelName: the type of resource (video, document,...)
            - resource: representation of the targetted resource including urls for the resource
                file (e.g. for a video: all resolutions, thumbnails and timed text tracks).

            For instructors only
            ++++++++++++++++++++

            - jwt_token: a short-lived JWT token linked to the resource ID that will be
                used for authentication and authorization on the API.

        """
        lti = LTI(self.request, self.kwargs["uuid"])
        lti.verify()

        app_data = None
        if lti.is_student:
            cache_key = "app_data|{model:s}|{domain:s}|{context:s}|{resource!s}".format(
                model=self.model.__name__,
                domain=lti.get_consumer_site().domain,
                context=lti.context_id,
                resource=lti.resource_id,
            )

            app_data = cache.get(cache_key)
            permissions = {"can_access_dashboard": False, "can_update": False}

        if not app_data:
            resource = get_or_create_resource(self.model, lti)
            permissions = {
                "can_access_dashboard":
                lti.is_instructor or lti.is_admin,
                "can_update": (lti.is_instructor or lti.is_admin)
                and resource.playlist.lti_id == lti.context_id,
            }
            app_data = {
                "modelName": self.model.RESOURCE_NAME,
                "resource":
                self.serializer_class(resource).data if resource else None,
                "state": "success",
                "sentry_dsn": settings.SENTRY_DSN,
                "environment": settings.ENVIRONMENT,
                "release": settings.RELEASE,
                "static": {
                    "svg": {
                        "plyr": static("svg/plyr.svg")
                    }
                },
            }
            if lti.is_student:
                cache.set(cache_key, app_data,
                          settings.APP_DATA_CACHE_DURATION)

        if app_data["resource"] is not None:
            try:
                locale = react_locale(lti.launch_presentation_locale)
            except ImproperlyConfigured:
                locale = "en_US"

            # Create a short-lived JWT token for the video
            jwt_token = AccessToken()
            jwt_token.payload.update({
                "session_id": str(uuid.uuid4()),
                "context_id": lti.context_id,
                "resource_id": str(lti.resource_id),
                "roles": lti.roles,
                "course": lti.get_course_info(),
                "locale": locale,
                "permissions": permissions,
                "maintenance": settings.MAINTENANCE_MODE,
            })
            try:
                jwt_token.payload["user_id"] = lti.user_id
            except AttributeError:
                pass

            app_data["jwt"] = str(jwt_token)

        return app_data
Beispiel #22
0
 def get_token(self, obj):
     token = AccessToken.for_user(obj)
     return str(token)
Beispiel #23
0
    async def test_video_update_instructor_channel_layer(self):
        """Admin user should receive message only from the admin channel."""
        video = await self._get_video(
            live_state=RUNNING,
            live_type=JITSI,
            live_info={
                "medialive": {
                    "input": {
                        "id":
                        "medialive_input_1",
                        "endpoints": [
                            "https://live_endpoint1",
                            "https://live_endpoint2",
                        ],
                    },
                    "channel": {
                        "id": "medialive_channel_1"
                    },
                },
                "mediapackage": {
                    "id": "mediapackage_channel_1",
                    "endpoints": {
                        "hls": {
                            "id": "endpoint1",
                            "url": "https://channel_endpoint1/live.m3u8",
                        },
                    },
                },
            },
        )

        jwt_token = AccessToken()
        jwt_token.payload["resource_id"] = str(video.id)
        jwt_token.payload["consumer_site"] = str(video.consumer_site.id)
        jwt_token.payload["context_id"] = "Maths"
        jwt_token.payload["roles"] = [
            random.choice(["instructor", "administrator"])
        ]
        jwt_token.payload["permissions"] = {"can_update": True}
        jwt_token.payload["user"] = {
            "id": "444444",
        }

        communicator = WebsocketCommunicator(
            base_application,
            f"ws/video/{video.id}/?jwt={jwt_token}",
        )

        connected, _ = await communicator.connect()

        self.assertTrue(connected)

        channel_layer = get_channel_layer()

        await channel_layer.group_send(
            VIDEO_ROOM_NAME.format(video_id=str(video.id)),
            {
                "type":
                "video_updated",
                "video":
                await self._get_serializer_data(video, {"is_admin": False}),
            },
        )

        self.assertTrue(await communicator.receive_nothing())

        await channel_layer.group_send(
            VIDEO_ADMIN_ROOM_NAME.format(video_id=str(video.id)),
            {
                "type":
                "video_updated",
                "video":
                await self._get_serializer_data(video, {"is_admin": True}),
            },
        )

        response = await communicator.receive_from()
        self.assertEqual(
            json.loads(response),
            {
                "type": "videos",
                "resource": {
                    "active_shared_live_media": None,
                    "active_shared_live_media_page": None,
                    "active_stamp": None,
                    "allow_recording": True,
                    "description": video.description,
                    "estimated_duration": None,
                    "has_chat": True,
                    "has_live_media": True,
                    "has_transcript": False,
                    "id": str(video.id),
                    "is_public": False,
                    "is_ready_to_show": True,
                    "is_recording": False,
                    "is_scheduled": False,
                    "join_mode": "approval",
                    "timed_text_tracks": [],
                    "thumbnail": None,
                    "title": video.title,
                    "upload_state": "pending",
                    "urls": {
                        "manifests": {
                            "hls": "https://channel_endpoint1/live.m3u8"
                        },
                        "mp4": {},
                        "thumbnails": {},
                    },
                    "show_download": True,
                    "should_use_subtitle_as_transcript": False,
                    "starting_at": None,
                    "participants_asking_to_join": [],
                    "participants_in_discussion": [],
                    "playlist": {
                        "id": str(video.playlist.id),
                        "title": video.playlist.title,
                        "lti_id": video.playlist.lti_id,
                    },
                    "recording_time": 0,
                    "live_info": {
                        "jitsi": {
                            "config_overwrite": {},
                            "domain": "meet.jit.si",
                            "external_api_url":
                            "https://meet.jit.si/external_api.js",
                            "interface_config_overwrite": {},
                            "room_name": str(video.pk),
                        },
                        "medialive": {
                            "input": {
                                "endpoints": [
                                    "https://live_endpoint1",
                                    "https://live_endpoint2",
                                ],
                            },
                        },
                    },
                    "live_state": RUNNING,
                    "live_type": JITSI,
                    "xmpp": None,
                    "shared_live_medias": [],
                },
            },
        )

        await communicator.disconnect()
def get_token_for_user(user):
    access_token = AccessToken.for_user(user)
    return str(access_token)
Beispiel #25
0
 def get_token(cls, user):
     return AccessToken.for_user(user)
Beispiel #26
0
def create_token(user) -> str:
    token = AccessToken.for_user(user)
    return str(token)
Beispiel #27
0
    def test_api_video_initiate_upload_token_user(self):
        """A token user associated to a video should be able to retrieve an upload policy."""
        video = VideoFactory(
            id="27a23f52-3379-46a2-94fa-697b59cfe3c7",
            resource_id="a2f27fde-973a-4e89-8dca-cc59e01d255c",
            upload_state=random.choice(["ready", "error"]),
        )
        jwt_token = AccessToken()
        jwt_token.payload["video_id"] = str(video.id)
        jwt_token.payload["roles"] = ["instructor"]

        # Create another video to check that its upload state is unaffected
        other_video = VideoFactory(
            upload_state=random.choice(["ready", "error"]))

        # Get the upload policy for this video
        # It should generate a key file with the Unix timestamp of the present time
        now = datetime(2018, 8, 8, tzinfo=pytz.utc)
        with mock.patch.object(timezone, "now", return_value=now):
            response = self.client.post(
                "/api/videos/{!s}/initiate-upload/".format(video.id),
                HTTP_AUTHORIZATION="Bearer {!s}".format(jwt_token),
            )
        self.assertEqual(response.status_code, 200)
        content = json.loads(response.content)

        policy = content.pop("policy")
        self.assertEqual(
            json.loads(b64decode(policy)),
            {
                "expiration":
                "2018-08-09T00:00:00.000Z",
                "conditions": [
                    {
                        "acl": "private"
                    },
                    {
                        "bucket": "test-marsha-source"
                    },
                    {
                        "x-amz-credential":
                        "aws-access-key-id/20180808/eu-west-1/s3/aws4_request"
                    },
                    {
                        "x-amz-algorithm": "AWS4-HMAC-SHA256"
                    },
                    {
                        "x-amz-date": "20180808T000000Z"
                    },
                    {
                        "key":
                        ("a2f27fde-973a-4e89-8dca-cc59e01d255c/video/"
                         "27a23f52-3379-46a2-94fa-697b59cfe3c7/1533686400")
                    },
                    ["starts-with", "$Content-Type", "video/"],
                    ["content-length-range", 0, 1073741824],
                ],
            },
        )
        self.assertEqual(
            content,
            {
                "acl":
                "private",
                "bucket":
                "test-marsha-source",
                "stamp":
                "1533686400",
                "key":
                "{!s}/video/{!s}/1533686400".format(video.resource_id,
                                                    video.id),
                "max_file_size":
                1073741824,
                "s3_endpoint":
                "s3.eu-west-1.amazonaws.com",
                "x_amz_algorithm":
                "AWS4-HMAC-SHA256",
                "x_amz_credential":
                "aws-access-key-id/20180808/eu-west-1/s3/aws4_request",
                "x_amz_date":
                "20180808T000000Z",
                "x_amz_expires":
                86400,
                "x_amz_signature":
                ("7b4bb2a1d0620d1bcf5adeec87173cdfa048cdc45705f77370af017dd7772a6f"
                 ),
            },
        )

        # The upload state of the timed text track should should have been reset
        video.refresh_from_db()
        self.assertEqual(video.upload_state, "pending")

        # Check that the other timed text tracks are not reset
        other_video.refresh_from_db()
        self.assertNotEqual(other_video.upload_state, "pending")

        # Try initiating an upload for the other video
        response = self.client.post(
            "/api/videos/{!s}/initiate-upload/".format(other_video.id),
            HTTP_AUTHORIZATION="Bearer {!s}".format(jwt_token),
        )
        self.assertEqual(response.status_code, 403)
        content = json.loads(response.content)
        self.assertEqual(
            content,
            {"detail": "You do not have permission to perform this action."})
Beispiel #28
0
    def setUpClass(cls):
        super().setUpClass()

        # Force URLs reload to use {{cookiecutter.setting_name}}
        reload_urlconf()

    def test_api_{{cookiecutter.model_lower}}_fetch_list_anonymous(self):
        """An anonymous should not be able to fetch a list of {{cookiecutter.model_lower}}."""
        response = self.client.get("/api/{{cookiecutter.model_url_part}}/")
        self.assertEqual(response.status_code, 401)

    def test_api_{{cookiecutter.model_lower}}_fetch_list_student(self):
        """A student should not be able to fetch a list of {{cookiecutter.model_lower}}."""
        {{cookiecutter.model_lower}} = {{cookiecutter.model}}Factory()

        jwt_token = AccessToken()
        jwt_token.payload["resource_id"] = str({{cookiecutter.model_lower}}.id)
        jwt_token.payload["roles"] = ["student"]
        jwt_token.payload["permissions"] = {"can_update": True}

        response = self.client.get(
            "/api/{{cookiecutter.model_url_part}}/", HTTP_AUTHORIZATION=f"Bearer {jwt_token}"
        )
        self.assertEqual(response.status_code, 403)

    def test_api_{{cookiecutter.model_lower}}_fetch_list_instructor(self):
        """An instructor should not be able to fetch a {{cookiecutter.model_lower}} list."""
        {{cookiecutter.model_lower}} = {{cookiecutter.model}}Factory()

        jwt_token = AccessToken()
        jwt_token.payload["resource_id"] = str({{cookiecutter.model_lower}}.id)
Beispiel #29
0
 def setUp(self):
     self.user1 = mixer.blend(User, username='******', password='******')
     self.token = AccessToken()
Beispiel #30
0
    def test_whoami_by_logged_in_user(self):
        """
        Logged-in users can make `whoami` requests.

        They receive their own user object.
        """
        user = factories.UserFactory(first_name="Jane",
                                     last_name="Doe",
                                     email="*****@*****.**")
        org_1 = factories.OrganizationFactory()
        org_access_1 = factories.OrganizationAccessFactory(user=user,
                                                           organization=org_1)
        org_2 = factories.OrganizationFactory()
        org_access_2 = factories.OrganizationAccessFactory(user=user,
                                                           organization=org_2)

        jwt_token = AccessToken()
        jwt_token.payload["resource_id"] = str(user.id)
        jwt_token.payload["user_id"] = str(user.id)

        with self.assertNumQueries(3):
            response = self.client.get(
                "/api/users/whoami/",
                HTTP_AUTHORIZATION=f"Bearer {jwt_token}",
            )

        self.assertEqual(response.status_code, 200)
        self.assertEqual(
            response.json()["date_joined"],
            user.date_joined.isoformat()[:-6] +
            "Z",  # NB: DRF literally does this
        )
        self.assertEqual(response.json()["email"], "*****@*****.**")
        self.assertEqual(response.json()["first_name"], "Jane")
        self.assertEqual(response.json()["id"], str(user.id))
        self.assertEqual(response.json()["is_staff"], False)
        self.assertEqual(response.json()["is_superuser"], False)
        self.assertEqual(response.json()["last_name"], "Doe")

        resp_accesses = response.json()["organization_accesses"]
        resp_org_access_1 = (resp_accesses.pop(0)
                             if resp_accesses[0]["organization"] == str(
                                 org_1.id) else resp_accesses.pop(1))
        self.assertEqual(
            resp_org_access_1,
            {
                "organization": str(org_1.id),
                "organization_name": org_1.name,
                "role": org_access_1.role,
                "user": str(user.id),
            },
        )
        resp_org_access_2 = resp_accesses.pop(0)
        self.assertEqual(
            resp_org_access_2,
            {
                "organization": str(org_2.id),
                "organization_name": org_2.name,
                "role": org_access_2.role,
                "user": str(user.id),
            },
        )