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")
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": [], }, ], )
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."})
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 )
def test_access_tokens_are_not_added_to_outstanding_list(self): AccessToken.for_user(self.user) qs = OutstandingToken.objects.all() self.assertFalse(qs.exists())
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)
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')
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", }, )
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."} )
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" ), }, }, )
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" ), }, }, )
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")
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)
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)
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)
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
def get_token(self, obj): token = AccessToken.for_user(obj) return str(token)
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)
def get_token(cls, user): return AccessToken.for_user(user)
def create_token(user) -> str: token = AccessToken.for_user(user) return str(token)
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."})
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)
def setUp(self): self.user1 = mixer.blend(User, username='******', password='******') self.token = AccessToken()
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), }, )