예제 #1
0
파일: utils.py 프로젝트: openfun/marsha
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
예제 #2
0
    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)
예제 #3
0
    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)
예제 #4
0
    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)
예제 #5
0
    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)
예제 #6
0
    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)
예제 #7
0
    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)
예제 #8
0
    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)
예제 #9
0
# 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
예제 #10
0
    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)