def generate_passport_and_signed_lti_parameters(url, lti_parameters, passport_attributes=None): """Generate signed LTI parameters.""" url = urlparse(url) if passport_attributes is None: passport_attributes = {} passport = ConsumerSiteLTIPassportFactory( consumer_site__domain=url.hostname, **passport_attributes) client = oauth1.Client(client_key=passport.oauth_consumer_key, client_secret=passport.shared_secret) # Compute Authorization header which looks like: # Authorization: OAuth oauth_nonce="80966668944732164491378916897", # oauth_timestamp="1378916897", oauth_version="1.0", oauth_signature_method="HMAC-SHA1", # oauth_consumer_key="", oauth_signature="frVp4JuvT1mVXlxktiAUjQ7%2F1cw%3D" _uri, headers, _body = client.sign( url.geturl(), http_method="POST", body=lti_parameters, headers={"Content-Type": "application/x-www-form-urlencoded"}, ) oauth_dict = dict(param.strip().replace('"', "").split("=") for param in headers["Authorization"].split(",")) signature = oauth_dict["oauth_signature"] oauth_dict["oauth_signature"] = unquote(signature) oauth_dict["oauth_nonce"] = oauth_dict.pop("OAuth oauth_nonce") lti_parameters.update(oauth_dict) return lti_parameters, passport
def test_views_lti_meeting_connection_error(self, mock_get_consumer_site, mock_verify): """Validate the response returned for an instructor request when there is no file.""" passport = ConsumerSiteLTIPassportFactory() data = { "resource_link_id": "example.com-123", "context_id": "course-v1:ufr+mathematics+00001", "roles": "instructor", "oauth_consumer_key": passport.oauth_consumer_key, "user_id": "56255f3807599c377bf0e5bf072359fd", "lis_person_sourcedid": "jane_doe", } mock_get_consumer_site.return_value = passport.consumer_site # mock_create_request.side_effect = ConnectionError responses.add( responses.GET, "https://10.7.7.1/bigbluebutton/api/getMeetingInfo", body=ConnectionError(), status=200, ) with self.assertRaises(ConnectionError): self.client.post(f"/lti/meetings/{uuid.uuid4()}", data) self.assertEqual(mock_verify.call_count, 1)
def test_views_lti_markdown_document_instructor_no_mmarkdown_document( self, mock_get_consumer_site, mock_verify): """Validate the response returned for an instructor request when there is no file.""" passport = ConsumerSiteLTIPassportFactory() data = { "resource_link_id": "example.com-123", "context_id": "course-v1:ufr+mathematics+00001", "roles": "instructor", "oauth_consumer_key": passport.oauth_consumer_key, "user_id": "56255f3807599c377bf0e5bf072359fd", "lis_person_sourcedid": "jane_doe", } mock_get_consumer_site.return_value = passport.consumer_site response = self.client.post(f"/lti/markdown-documents/{uuid.uuid4()}", 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))) self.assertIsNotNone(context.get("jwt")) self.assertEqual(context.get("state"), "success") self.assertIsNotNone(context.get("resource")) self.assertEqual(context.get("modelName"), "markdown-documents") # 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_instructor_no_classroom( self, mock_get_consumer_site, mock_verify): """Validate the response returned for an instructor request when there is no file.""" passport = ConsumerSiteLTIPassportFactory() data = { "resource_link_id": "example.com-123", "context_id": "course-v1:ufr+mathematics+00001", "roles": "instructor", "oauth_consumer_key": passport.oauth_consumer_key, "user_id": "56255f3807599c377bf0e5bf072359fd", "lis_person_sourcedid": "jane_doe", } mock_get_consumer_site.return_value = passport.consumer_site responses.add( responses.GET, "https://10.7.7.1/bigbluebutton/api/getMeetingInfo", body=""" <response> <returncode>SUCCESS</returncode> <running>true</running> </response> """, status=200, ) response = self.client.post(f"/lti/meetings/{uuid.uuid4()}", 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))) self.assertIsNotNone(context.get("jwt")) self.assertEqual(context.get("state"), "success") self.assertIsNotNone(context.get("resource")) self.assertEqual(context.get("modelName"), "classrooms") 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_classroom_get_request(self, ): """LTI GET request should not be allowed.""" 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", ) response = self.client.get(f"/lti/meetings/{classroom.id}") self.assertEqual(response.status_code, 405)
def test_views_lti_markdown_document_student(self, mock_get_consumer_site, mock_verify): """Validate the response returned for a student request.""" passport = ConsumerSiteLTIPassportFactory() markdown_document = MarkdownDocumentFactory( playlist__lti_id="course-v1:ufr+mathematics+00001", playlist__consumer_site=passport.consumer_site, is_draft=False, ) data = { "resource_link_id": markdown_document.lti_id, "context_id": markdown_document.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 response = self.client.post( f"/lti/markdown-documents/{markdown_document.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"), "markdown-documents") self.assertEqual( jwt_token.payload["user"], { "email": None, "username": "******", "user_fullname": None, "id": "56255f3807599c377bf0e5bf072359fd", }, ) self.assertTrue(context.get("flags").get("markdown")) # 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_meeting_student(self, mock_get_consumer_site, mock_verify): """Validate the response returned for a student request.""" passport = ConsumerSiteLTIPassportFactory() meeting = MeetingFactory( 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": meeting.lti_id, "context_id": meeting.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/meetings/{meeting.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"), "meetings") self.assertEqual( jwt_token.payload["user"], { "email": None, "username": "******", "user_fullname": None, "id": "56255f3807599c377bf0e5bf072359fd", }, ) self.assertEqual( context.get("flags"), { "BBB": True, "live_raw": False, "sentry": False }, ) # 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_meeting_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() meeting = MeetingFactory( 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": meeting.lti_id, "context_id": meeting.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/{meeting.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(meeting.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(meeting.id), "infos": { "returncode": "SUCCESS", "running": "true" }, "lti_id": str(meeting.lti_id), "meeting_id": str(meeting.meeting_id), "playlist": { "id": str(meeting.playlist_id), "lti_id": str(meeting.playlist.lti_id), "title": meeting.playlist.title, }, "started": False, "ended": False, "title": meeting.title, "welcome_text": meeting.welcome_text, }, context.get("resource"), ) self.assertEqual(context.get("modelName"), "meetings") self.assertEqual(context.get("appName"), "bbb") # Make sure we only go through LTI verification once as it is costly (getting passport + # signature) self.assertEqual(mock_verify.call_count, 1)
# We don't enforce arguments documentation in tests # pylint: disable=unused-argument @override_settings({{cookiecutter.setting_name}}=True) class {{cookiecutter.model}}LTIViewTestCase(TestCase): """Test case for the file LTI view.""" maxDiff = None @mock.patch.object(LTI, "verify") @mock.patch.object(LTI, "get_consumer_site") def test_views_lti_{{cookiecutter.model_lower}}_student(self, mock_get_consumer_site, mock_verify): """Validate the response returned for a student request.""" passport = ConsumerSiteLTIPassportFactory() {{cookiecutter.model_lower}} = {{cookiecutter.model}}Factory( playlist__lti_id="course-v1:ufr+mathematics+00001", playlist__consumer_site=passport.consumer_site, ) data = { "resource_link_id": {{cookiecutter.model_lower}}.lti_id, "context_id": {{cookiecutter.model_lower}}.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
def test_views_lti_markdown_document_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() markdown_document = MarkdownDocumentFactory( playlist__lti_id="course-v1:ufr+mathematics+00001", playlist__consumer_site=passport.consumer_site, ) data = { "resource_link_id": markdown_document.lti_id, "context_id": markdown_document.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 response = self.client.post( f"/lti/markdown-documents/{markdown_document.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(markdown_document.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(markdown_document.id), "is_draft": True, "rendering_options": {}, "playlist": { "id": str(markdown_document.playlist_id), "lti_id": str(markdown_document.playlist.lti_id), "title": markdown_document.playlist.title, }, "position": 0, "translations": [ { "content": markdown_document.content, "language_code": "en", "rendered_content": markdown_document.rendered_content, "title": markdown_document.title, }, ], }, context.get("resource"), ) self.assertEqual(context.get("modelName"), "markdown-documents") self.assertEqual(context.get("appName"), "markdown") # Make sure we only go through LTI verification once as it is costly (getting passport + # signature) self.assertEqual(mock_verify.call_count, 1)