Exemple #1
0
    def test_multiple_consumers(self):
        """
        Test to authenticate 2 users with the same username, coming from
        distinct consumer sites.
        """

        consumer1 = LTIConsumerFactory(slug="consumer1")
        passport_consumer1 = LTIPassportFactory(
            title="consumer1_passport", consumer=consumer1
        )

        consumer2 = LTIConsumerFactory(slug="consumer2")
        passport_consumer2 = LTIPassportFactory(
            title="consumer2_passport", consumer=consumer2
        )

        user1 = get_user_model().objects.create_user(
            "popular_username@test_auth_backend_consumer1",
            email="*****@*****.**",
            public_username="******",
            lti_consumer=consumer1,
            lti_remote_user_id="popular_username",
        )

        user2 = get_user_model().objects.create_user(
            "popular_username@test_auth_backend_consumer2",
            email="*****@*****.**",
            public_username="******",
            lti_consumer=consumer2,
            lti_remote_user_id="popular_username",
        )

        authenticated_user1 = self._authenticate(
            {
                "lti_message_type": "basic-lti-launch-request",
                "lti_version": "LTI-1p0",
                "resource_link_id": "aaa",
                "context_id": "course-v1:fooschool+authbackend+0001",
                "lis_person_contact_email_primary": "*****@*****.**",
                "lis_person_sourcedid": "popular_username",
            },
            passport_consumer1,
        )

        authenticated_user2 = self._authenticate(
            {
                "lti_message_type": "basic-lti-launch-request",
                "lti_version": "LTI-1p0",
                "resource_link_id": "aaa",
                "context_id": "course-v1:fooschool+authbackend+0001",
                "lis_person_contact_email_primary": "*****@*****.**",
                "lis_person_sourcedid": "popular_username",
            },
            passport_consumer2,
        )

        self.assertNotEqual(authenticated_user1, authenticated_user2)
        self.assertEqual(authenticated_user1, user1)
        self.assertEqual(authenticated_user2, user2)
Exemple #2
0
    def test_optional_email(self):
        """
        Ensure that we can authenticate with success if the user email is
        not found in the LTI request
        """

        consumer = LTIConsumerFactory(slug="consumer")
        passport = LTIPassportFactory(title="consumer1_passport1", consumer=consumer)

        user_count = get_user_model().objects.count()

        new_user = self._authenticate(
            {
                "user_id": "7275a984-1e77-4084-9fe6-e54d0deba0e7",
                "lti_message_type": "basic-lti-launch-request",
                "lti_version": "LTI-1p0",
                "resource_link_id": "aaa",
                "context_id": "course-v1:fooschool+authbackend+0001",
                "lis_person_sourcedid": "user_without_email",
            },
            passport,
        )

        self.assertEqual("user_without_email", new_user.public_username)
        self.assertEqual("", new_user.email)
        self.assertEqual(consumer, new_user.lti_consumer)
        self.assertEqual("user_without_email@consumer", new_user.username)
        self.assertEqual(user_count + 1, get_user_model().objects.count())
Exemple #3
0
    def test_known_user(self):
        """Test authentication with an already existing user."""

        consumer = LTIConsumerFactory(slug="consumer")
        passport = LTIPassportFactory(title="consumer1_passport1", consumer=consumer)

        known_user = get_user_model().objects.create_user(
            "test_auth_backend_user1",
            email="*****@*****.**",
            public_username="******",
            lti_consumer=consumer,
            lti_remote_user_id="ashley",
        )

        user_count = get_user_model().objects.count()

        auth_user = self._authenticate(
            {
                "user_id": "643f1625-f240-4a5a-b6eb-89b317807963",
                "lti_message_type": "basic-lti-launch-request",
                "lti_version": "LTI-1p0",
                "resource_link_id": "aaa",
                "context_id": "course-v1:fooschool+authbackend+0001",
                "lis_person_contact_email_primary": "*****@*****.**",
                "lis_person_sourcedid": "ashley",
            },
            passport,
        )
        self.assertEqual(known_user, auth_user)
        self.assertEqual(user_count, get_user_model().objects.count())
Exemple #4
0
    def test_automatic_default_public_username_role_administrator(self):
        """
        Ensure that we can authenticate with success if the public username is
        not found in the LTI request and check if the user has role administrator
        that a default public username is set.
        """

        consumer = LTIConsumerFactory(slug="consumer")
        passport = LTIPassportFactory(title="consumer1_passport1", consumer=consumer)

        user_count = get_user_model().objects.count()

        new_user = self._authenticate(
            {
                "user_id": "3fd0ff83-a62d-4a12-9716-4d48821ae24f",
                "lti_message_type": "basic-lti-launch-request",
                "lti_version": "LTI-1p0",
                "resource_link_id": "aaa",
                "context_id": "course-v1:fooschool+authbackend+0001",
                "lis_person_contact_email_primary": "*****@*****.**",
                "roles": "Administrator",
            },
            passport,
        )

        self.assertEqual(consumer, new_user.lti_consumer)
        self.assertEqual("*****@*****.**", new_user.email)
        self.assertEqual(
            "3fd0ff83-a62d-4a12-9716-4d48821ae24f@consumer", new_user.username
        )
        self.assertEqual(user_count + 1, get_user_model().objects.count())
        self.assertEqual("Administrator", new_user.public_username)
Exemple #5
0
    def test_delete(self):
        """The DELETE method is not allowed to sign in"""
        consumer = LTIConsumerFactory(slug="consumer")
        passport = LTIPassportFactory(title="consumer1_passport1",
                                      consumer=consumer)

        forum_uuid = "8bb319aa-f3cf-4509-952c-c4bd0fb42fd7"
        context_id = "course-v1:testschool+login+0001"

        # Build the LTI launch request
        lti_parameters = {
            "user_id": "643f1625-f240-4a5a-b6eb-89b317807963",
            "lti_message_type": "basic-lti-launch-request",
            "lti_version": "LTI-1p0",
            "resource_link_id": "aaa",
            "context_id": context_id,
            "lis_person_contact_email_primary": "*****@*****.**",
            "lis_person_sourcedid": "testuser",
            "launch_presentation_locale": "en",
            "roles": "Instructor",
        }
        url = f"http://testserver/lti/forum/{forum_uuid}"
        signed_parameters = sign_parameters(passport, lti_parameters, url)

        response = self.client.delete(
            f"/lti/forum/{forum_uuid}",
            data=urlencode(signed_parameters),
            content_type=CONTENT_TYPE,
        )
        self.assertEqual(405, response.status_code)
Exemple #6
0
def dev_consumer(request: HttpRequest) -> HttpResponse:
    """Display the standalone LTI consumer"""

    # Ensure that at least the demo consumer exists with a passport
    consumer = LTIConsumerFactory(slug="dev_consumer", title="Dev consumer")
    passport = LTIPassportFactory(title="Dev passport", consumer=consumer)

    if request.method == "POST":
        form = LTIConsumerForm(request.POST)
        if form.is_valid():
            launch_url = request.build_absolute_uri(
                reverse(
                    "forum.lti.view",
                    kwargs={"uuid": form.cleaned_data["forum_lti_id"]},
                ))
            lti_params = _generate_signed_parameters(form, launch_url,
                                                     passport)
            return render(
                request,
                "dev/consumer.html",
                {
                    "form": form,
                    "lti_params": lti_params,
                    "launch_url": launch_url
                },
            )
    else:
        form = LTIConsumerForm()

    return render(request, "dev/consumer.html", {"form": form})
Exemple #7
0
    def test_new_user(self):
        """Test authentication of a new user."""

        consumer = LTIConsumerFactory(slug="consumer")
        passport = LTIPassportFactory(title="consumer1_passport1", consumer=consumer)

        user_count = get_user_model().objects.count()

        new_user = self._authenticate(
            {
                "user_id": "1c6cd9c1-ca4c-41fe-b369-912075a5d3ce",
                "lti_message_type": "basic-lti-launch-request",
                "lti_version": "LTI-1p0",
                "resource_link_id": "aaa",
                "context_id": "course-v1:fooschool+authbackend+0001",
                "lis_person_contact_email_primary": "*****@*****.**",
                "lis_person_sourcedid": "new_user",
            },
            passport,
        )

        self.assertEqual("new_user", new_user.public_username)
        self.assertEqual(consumer, new_user.lti_consumer)
        self.assertEqual("*****@*****.**", new_user.email)
        self.assertEqual("new_user@consumer", new_user.username)
        self.assertEqual(user_count + 1, get_user_model().objects.count())
Exemple #8
0
 def setUp(self):
     """Override the setUp method to instanciate and serve a request factory."""
     super().setUp()
     self.request_factory = RequestFactory()
     self._consumer = LTIConsumerFactory(slug="test_lti")
     self._passport = LTIPassportFactory(title="test passport",
                                         consumer=self._consumer)
     self._url = "http://testserver/lti/launch"
Exemple #9
0
    def test_moodle_launch_request(self):
        """
        Ensure that a launch request initiated by Moodle is
        accepted by the authentication backend.
        """

        consumer = LTIConsumerFactory(slug="consumer")
        passport = LTIPassportFactory(title="consumer1_passport1", consumer=consumer)

        new_user = self._authenticate(
            {
                "user_id": "2",
                "lis_person_sourcedid": "",
                "roles": (
                    "Instructor,urn:lti:sysrole:ims/lis/Administrator,"
                    + "urn:lti:instrole:ims/lis/Administrator"
                ),
                "context_id": "2",
                "context_label": "moodle101",
                "context_title": "Moodle 101",
                "resource_link_title": "Test forum 2",
                "resource_link_description": "",
                "resource_link_id": "2",
                "context_type": "CourseSection",
                "lis_course_section_sourcedid": "",
                "lis_result_sourcedid": (
                    '{"data":{"instanceid":"2","userid":"2","typeid":"1","launchid":1424835319}, '
                    + '"hash":"2d521baae5180acbc4ea200dfa3f4c75176010b16b0be666cba68a882c7caa82"}'
                ),
                "lis_outcome_service_url": "http://moodle-instance.example/mod/lti/service.php",
                "lis_person_name_given": "Admin",
                "lis_person_name_family": "User",
                "lis_person_name_full": "Admin User",
                "ext_user_username": "******",
                "lis_person_contact_email_primary": "*****@*****.**",
                "launch_presentation_locale": "en",
                "ext_lms": "moodle-2",
                "tool_consumer_info_product_family_code": "moodle",
                "tool_consumer_info_version": "2019111802",
                "lti_version": "LTI-1p0",
                "lti_message_type": "basic-lti-launch-request",
                "tool_consumer_instance_guid": "mooodle-instance.example",
                "tool_consumer_instance_name": "Test Site",
                "tool_consumer_instance_description": "Test Site",
                "launch_presentation_document_target": "iframe",
                "launch_presentation_return_url": (
                    "http://moodle-instance.example/mod/lti/return.php"
                    + "?course=2&launch_container=3&instanceid=2&sesskey=TEST"
                ),
            },
            passport,
        )

        self.assertEqual("moodle-user", new_user.public_username)
Exemple #10
0
    def test_groups_moderator_created_with_role_student(self):
        """
        Controls that group moderator is initialy created when a user login in the forum
        with the role student
        """
        consumer = LTIConsumerFactory(slug="consumer")
        passport = LTIPassportFactory(title="consumer1_passport1",
                                      consumer=consumer)

        forum_uuid = "8bb319aa-f3cf-4509-952c-c4bd0fb42fd7"
        context_id = "course-v1:testschool+login+0001"
        initial_group_count = Group.objects.count()

        # Build the LTI launch request
        lti_parameters = {
            "user_id": "643f1625-f240-4a5a-b6eb-89b317807963",
            "lti_message_type": "basic-lti-launch-request",
            "lti_version": "LTI-1p0",
            "resource_link_id": "aaa",
            "context_id": context_id,
            "lis_person_contact_email_primary": "*****@*****.**",
            "lis_person_sourcedid": "testuser",
            "launch_presentation_locale": "en",
            "roles": "Student",
        }
        url = f"http://testserver/lti/forum/{forum_uuid}"
        signed_parameters = sign_parameters(passport, lti_parameters, url)

        self.client.post(
            f"/lti/forum/{forum_uuid}",
            data=urlencode(signed_parameters),
            content_type=CONTENT_TYPE,
        )

        # A LTIContext and a Forum should have been created
        context = LTIContext.objects.get(lti_id=context_id)

        # Groups should have been created
        self.assertEqual(initial_group_count + 5, Group.objects.count())
        self.assertEqual(
            [
                f"cg:{context.id}",
                f"cg:{context.id}:role:administrator",
                f"cg:{context.id}:role:instructor",
                f"cg:{context.id}:role:moderator",
                f"cg:{context.id}:role:student",
            ],
            list(
                Group.objects.filter(name__startswith=f"cg:{context.id}").
                order_by("name").values_list("name", flat=True)),
        )
Exemple #11
0
    def test_post_with_valid_lti_launch_request(self):
        """A user should be able to authenticate via a LTI launch request signed by
        a trusted consumer and passport."""

        consumer = LTIConsumerFactory(slug="consumer")
        passport = LTIPassportFactory(title="consumer1_passport1",
                                      consumer=consumer)

        forum_uuid = "8bb319aa-f3cf-4509-952c-c4bd0fb42fd7"
        context_id = "course-v1:testschool+login+0001"

        # Build the LTI launch request
        lti_parameters = {
            "user_id": "643f1625-f240-4a5a-b6eb-89b317807963",
            "lti_message_type": "basic-lti-launch-request",
            "lti_version": "LTI-1p0",
            "resource_link_id": "aaa",
            "context_id": context_id,
            "lis_person_contact_email_primary": "*****@*****.**",
            "lis_person_sourcedid": "testuser",
            "launch_presentation_locale": "en",
            "roles": "Instructor",
        }
        url = f"http://testserver/lti/forum/{forum_uuid}"
        signed_parameters = sign_parameters(passport, lti_parameters, url)

        response = self.client.post(
            f"/lti/forum/{forum_uuid}",
            data=urlencode(signed_parameters),
            content_type=CONTENT_TYPE,
        )

        # A LTIContext and a Forum should have been created
        context = LTIContext.objects.get(lti_id=context_id)
        forum = Forum.objects.get(lti_id=forum_uuid)

        # The response should be a redirection to the forum URL
        self.assertEqual(302, response.status_code)
        self.assertEqual(f"/forum/forum/{forum.slug}-{forum.id}/",
                         response.url)

        # Our current LTIContext id should have been injected in the user session
        self.assertEqual(context.id,
                         self.client.session.get(SESSION_LTI_CONTEXT_ID))

        # The launch_presentation_locale should be set in the language cookie
        self.assertEqual(
            "en", response.client.cookies[settings.LANGUAGE_COOKIE_NAME].value)
Exemple #12
0
    def _connects(self,
                  role,
                  forum_uuid=None,
                  uuid=None,
                  lis_person_sourcedid=None):
        """
        Utils not to repeat the connection of an instructor or
        a student
        """
        consumer = LTIConsumerFactory(slug="consumer")
        passport = LTIPassportFactory(title="consumer1_passport1",
                                      consumer=consumer)

        if not forum_uuid:
            forum_uuid = self.forum_uuid

        # Build the LTI launch request
        lti_parameters = {
            "user_id":
            "643f1625-f240-4a5a-b6eb-89b317807963" if not uuid else uuid,
            "lti_message_type": "basic-lti-launch-request",
            "lti_version": "LTI-1p0",
            "resource_link_id": "aaa",
            "context_id": self.context_id,
            "lis_person_contact_email_primary": "*****@*****.**",
            "lis_person_sourcedid":
            "testuser" if not lis_person_sourcedid else lis_person_sourcedid,
            "launch_presentation_locale": "en",
            "roles": role.capitalize(),
        }

        url = f"http://testserver/lti/forum/{forum_uuid}"
        signed_parameters = sign_parameters(passport, lti_parameters, url)

        self.client.post(
            f"/lti/forum/{forum_uuid}",
            data=urlencode(signed_parameters),
            content_type=CONTENT_TYPE,
        )

        forum = Forum.objects.get(
            lti_id=forum_uuid,
            lti_contexts__id=LTIContext.objects.get(
                lti_id=self.context_id, lti_consumer_id=passport.consumer).id,
        )

        return forum
Exemple #13
0
    def test_openedx_studio_launch_request_existing_user_instructor_admin_empty_username(
        self,
    ):
        """
        Ensure that users that have a public_username empty will have their public_username
        reset to a default value.
        """

        consumer = LTIConsumerFactory(slug="consumer")
        passport = LTIPassportFactory(title="consumer1_passport1", consumer=consumer)
        role = random.choice(["Instructor", "Administrator"])
        public_username = (
            "Educational team" if role == "Instructor" else "Administrator"
        )
        params = {
            "context_id": "course-v1:TEST1+0001+2020_T1",
            "context_label": "TEST1",
            "context_title": "test course 1",
            "custom_component_display_name": "Forum",
            "launch_presentation_return_url": "",
            "lis_result_sourcedid": "course-v1%3ATEST1%2B0001%2B2020_T1:-c7b2c44b1d",
            "lti_message_type": "basic-lti-launch-request",
            "lti_version": "LTI-1p0",
            "resource_link_id": "-c7b2c44b1d",
            "roles": role,
            "user_id": "student",
        }
        # User 1 is using ashley from openedx studio in the course "TEST1"
        user1 = self._authenticate(params, passport)

        # A new ashley user should have been created
        self.assertEqual(1, get_user_model().objects.count())
        self.assertEqual(public_username, user1.public_username)

        # We set public_username to an empty value for the test
        user1.public_username = ""
        user1.save()
        self.assertEqual("", user1.public_username)
        # Authenticate with the same params
        user1 = self._authenticate(params, passport)

        # No new user have been created
        self.assertEqual(1, get_user_model().objects.count())
        # Confirm that public_username is reset to the default value
        self.assertEqual(public_username, user1.public_username)
Exemple #14
0
    def test_openedx_studio_launch_request_existing_user_all_roles_empty_username(self):
        """
        Check that users previously created that have a public_username defined don't get
        their public_username reset with the next connection.
        """

        consumer = LTIConsumerFactory(slug="consumer")
        passport = LTIPassportFactory(title="consumer1_passport1", consumer=consumer)

        params = {
            "context_id": "course-v1:TEST1+0001+2020_T1",
            "context_label": "TEST1",
            "context_title": "test course 1",
            "custom_component_display_name": "Forum",
            "launch_presentation_return_url": "",
            "lis_result_sourcedid": "course-v1%3ATEST1%2B0001%2B2020_T1:-c7b2c44b1d",
            "lti_message_type": "basic-lti-launch-request",
            "lti_version": "LTI-1p0",
            "resource_link_id": "-c7b2c44b1d",
            "roles": random.choice(["Instructor", "Administrator", "Student"]),
            "user_id": "student",
        }
        # User 1 is using ashley from openedx studio in the course "TEST1"
        user1 = self._authenticate(params, passport)

        # We set public_username to a define value for the test
        user1.public_username = "******"
        user1.save()

        # A new ashley user should have been created
        self.assertEqual(1, get_user_model().objects.count())
        self.assertEqual("Test", user1.public_username)

        # Authenticate with the same params
        user1 = self._authenticate(
            params,
            passport,
        )

        # No new user have been created
        self.assertEqual(1, get_user_model().objects.count())
        # Confirm that public_username is still define
        self.assertEqual("Test", user1.public_username)
Exemple #15
0
    def test_required_parameters(self):
        """
        Ensure that authentication fails if parameters are missing in the LTI
        request.
        """

        consumer = LTIConsumerFactory(slug="consumer")
        passport = LTIPassportFactory(title="consumer1_passport1", consumer=consumer)

        # Missing param : lis_person_sourcedid or ext_user_username or user_id
        with self.assertRaises(PermissionDenied):
            self._authenticate(
                {
                    "lti_message_type": "basic-lti-launch-request",
                    "lti_version": "LTI-1p0",
                    "resource_link_id": "aaa",
                    "context_id": "course-v1:fooschool+authbackend+0001",
                    "lis_person_contact_email_primary": "*****@*****.**",
                },
                passport,
            )
Exemple #16
0
    def test_automatic_default_public_username_role_administrator_instructor(self):
        """
        Ensure that we set 'Educational team' as a default public username if the user doesn't
        have a public username set and if he has the role instructor. If he has both the
        instructor and administrator roles, the instructor role has precedence over the
        adminstrator role.
        """

        consumer = LTIConsumerFactory(slug="consumer")
        passport = LTIPassportFactory(title="consumer1_passport1", consumer=consumer)

        user_count = get_user_model().objects.count()

        new_user = self._authenticate(
            {
                "user_id": "3fd0ff83-a62d-4a12-9716-4d48821ae24f",
                "lti_message_type": "basic-lti-launch-request",
                "lti_version": "LTI-1p0",
                "resource_link_id": "aaa",
                "context_id": "course-v1:fooschool+authbackend+0001",
                "lis_person_contact_email_primary": "*****@*****.**",
                "roles": (
                    "Administrator,Instructor,urn:lti:sysrole:ims/lis/Administrator,"
                    "urn:lti:instrole:ims/lis/Administrator"
                ),
            },
            passport,
        )

        self.assertEqual(consumer, new_user.lti_consumer)
        self.assertEqual("*****@*****.**", new_user.email)
        self.assertEqual(
            "3fd0ff83-a62d-4a12-9716-4d48821ae24f@consumer", new_user.username
        )
        self.assertEqual(user_count + 1, get_user_model().objects.count())
        self.assertEqual("Educational team", new_user.public_username)
Exemple #17
0
    def test_post_with_lti_uuid_multiple_forum_existing_in_another_context(
            self):
        """
        Forums can have an identical `lti_id` and be accessed from the same LTI
        launch URL but from different LTI contexts. A new forum is created for
        each LTI context. The title for the forum created is set by default to the
        class name of the forum created last having the same `lti_id` (this enables
        quick copy and paste). We check that the default name used is from the last
        forum created.
        """

        passport = LTIPassportFactory(consumer=LTIConsumerFactory())
        forum_uuid = "8bb319aa-f3cf-4509-952c-c4bd0fb42fd7"
        context1_id = "course-v1:testschool+login+0001"

        # Build the LTI launch request
        lti_parameters = {
            "user_id": "643f1625-f240-4a5a-b6eb-89b317807963",
            "lti_message_type": "basic-lti-launch-request",
            "lti_version": "LTI-1p0",
            "resource_link_id": "aaa",
            "context_id": context1_id,
            "lis_person_contact_email_primary": "*****@*****.**",
            "lis_person_sourcedid": "testuser",
            "launch_presentation_locale": "en",
            "roles": "Instructor",
        }
        url = f"http://testserver/lti/forum/{forum_uuid}"
        self.client.post(
            f"/lti/forum/{forum_uuid}",
            data=urlencode(sign_parameters(passport, lti_parameters, url)),
            content_type=CONTENT_TYPE,
            follow=True,
        )
        # A new forum has been created
        forum1 = Forum.objects.get(
            lti_id=forum_uuid,
            lti_contexts__id=LTIContext.objects.get(
                lti_id=context1_id, lti_consumer_id=passport.consumer).id,
        )
        forum1.name = "An original title"
        forum1.save()
        forum1.refresh_from_db()

        # We use the same lti_parameters except the context
        context2_id = "course-v1:testschool+login+0002"
        lti_parameters2 = {
            "user_id": "643f1625-f240-4a5a-b6eb-89b317807963",
            "lti_message_type": "basic-lti-launch-request",
            "lti_version": "LTI-1p0",
            "resource_link_id": "aaa",
            "context_id": context2_id,
            "lis_person_contact_email_primary": "*****@*****.**",
            "lis_person_sourcedid": "testuser",
            "launch_presentation_locale": "en",
            "roles": "Instructor",
        }
        # We request the same LTI launch source url
        self.client.post(
            f"/lti/forum/{forum_uuid}",
            data=urlencode(sign_parameters(passport, lti_parameters2, url)),
            content_type=CONTENT_TYPE,
            follow=True,
        )
        # A new forum has been created
        forum2 = Forum.objects.get(
            lti_id=forum_uuid,
            lti_contexts__id=LTIContext.objects.get(
                lti_id=context2_id, lti_consumer_id=passport.consumer).id,
        )
        # Forum name should be the same as the previous forum
        self.assertEqual(forum2.name, forum1.name)
        forum2.name = "A new original title"
        forum2.save()
        forum2.refresh_from_db()

        # We use the same lti_parameters except the context
        context3_id = "course-v3:testschool+login+0002"
        lti_parameters3 = {
            "user_id": "643f1625-f240-4a5a-b6eb-89b317807963",
            "lti_message_type": "basic-lti-launch-request",
            "lti_version": "LTI-1p0",
            "resource_link_id": "aaa",
            "context_id": context3_id,
            "lis_person_contact_email_primary": "*****@*****.**",
            "lis_person_sourcedid": "testuser",
            "launch_presentation_locale": "en",
            "roles": "Instructor",
        }
        # We request the same LTI launch source url
        self.client.post(
            f"/lti/forum/{forum_uuid}",
            data=urlencode(sign_parameters(passport, lti_parameters3, url)),
            content_type=CONTENT_TYPE,
            follow=True,
        )
        # A new forum has been created
        forum3 = Forum.objects.get(
            lti_id=forum_uuid,
            lti_contexts__id=LTIContext.objects.get(
                lti_id=context3_id, lti_consumer_id=passport.consumer).id,
        )
        # Forum name should be the same as the previous forum, the last created
        self.assertEqual(forum3.name, forum2.name)
Exemple #18
0
    def test_post_with_lti_uuid_forum_existing_in_another_context(self):
        """
        Two forums can have an identical `lti_id` and be accessed from the same LTI
        launch URL but from two different LTI contexts. A new forum is created for
        each LTI context. The title for the forum created is set by default to the
        class name of the last forum having the same `lti_id` (this enables quick
        copy and paste)
        """

        passport = LTIPassportFactory(consumer=LTIConsumerFactory())

        forum_uuid = "8bb319aa-f3cf-4509-952c-c4bd0fb42fd7"
        context1_id = "course-v1:testschool+login+0001"

        # Build the LTI launch request
        lti_parameters = {
            "user_id": "643f1625-f240-4a5a-b6eb-89b317807963",
            "lti_message_type": "basic-lti-launch-request",
            "lti_version": "LTI-1p0",
            "resource_link_id": "aaa",
            "context_id": context1_id,
            "lis_person_contact_email_primary": "*****@*****.**",
            "lis_person_sourcedid": "testuser",
            "launch_presentation_locale": "en",
            "roles": "Instructor",
        }
        url = f"http://testserver/lti/forum/{forum_uuid}"
        # we sign the request
        initial_forum_count = Forum.objects.count()
        initial_lticontext_count = LTIContext.objects.count()
        response = self.client.post(
            f"/lti/forum/{forum_uuid}",
            data=urlencode(sign_parameters(passport, lti_parameters, url)),
            content_type=CONTENT_TYPE,
            follow=True,
        )

        # A LTIContext and a Forum should have been created
        context1 = LTIContext.objects.get(lti_id=context1_id,
                                          lti_consumer_id=passport.consumer)
        forum1 = Forum.objects.get(lti_id=forum_uuid,
                                   lti_contexts__id=context1.id)
        forum1.name = "An original title"
        self.assertEqual(LTIContext.objects.count(),
                         initial_lticontext_count + 1)
        self.assertEqual(Forum.objects.count(), initial_forum_count + 1)

        # The response should be a redirection to the forum URL
        self.assertRedirects(response,
                             f"/forum/forum/{forum1.slug}-{forum1.id}/")
        forum1.save()
        forum1.refresh_from_db()

        # We use the same lti_parameters except the context
        context2_id = "course-v1:testschool+login+0002"
        lti_parameters2 = {
            "user_id": "643f1625-f240-4a5a-b6eb-89b317807963",
            "lti_message_type": "basic-lti-launch-request",
            "lti_version": "LTI-1p0",
            "resource_link_id": "aaa",
            "context_id": context2_id,
            "lis_person_contact_email_primary": "*****@*****.**",
            "lis_person_sourcedid": "testuser",
            "launch_presentation_locale": "en",
            "roles": "Instructor",
        }
        # We request the same LTI launch source url
        response = self.client.post(
            f"/lti/forum/{forum_uuid}",
            data=urlencode(sign_parameters(passport, lti_parameters2, url)),
            content_type=CONTENT_TYPE,
            follow=True,
        )
        # A new lti context should have been created
        self.assertEqual(LTIContext.objects.count(),
                         initial_lticontext_count + 2)
        # A new forum should have been created
        self.assertEqual(Forum.objects.count(), initial_forum_count + 2)
        # The response should be a redirection to the forum URL
        context2 = LTIContext.objects.get(lti_id=context2_id,
                                          lti_consumer_id=passport.consumer)
        forum2 = Forum.objects.get(lti_id=forum_uuid,
                                   lti_contexts__id=context2.id)
        # Forum name should be the same as the previous forum
        self.assertEqual(forum2.name, forum1.name)

        self.assertRedirects(response,
                             f"/forum/forum/{forum2.slug}-{forum2.id}/")

        # we resign the request for context1
        response = self.client.post(
            f"/lti/forum/{forum_uuid}",
            data=urlencode(sign_parameters(passport, lti_parameters, url)),
            content_type=CONTENT_TYPE,
            follow=True,
        )
        # No new lti context should have been created
        self.assertEqual(LTIContext.objects.count(),
                         initial_lticontext_count + 2)
        # No new forum should have been created
        self.assertEqual(Forum.objects.count(), initial_forum_count + 2)
        # The response should be a redirection to the forum URL
        self.assertRedirects(response,
                             f"/forum/forum/{forum1.slug}-{forum1.id}/")
Exemple #19
0
    def test_openedx_studio_launch_request(self):
        """
        Ensure that a launch request initiated by OpenedX studio is accepted by the
        authentication backend AND that a user_id specific to the context_id is
        generated.
        """

        consumer = LTIConsumerFactory(slug="consumer")
        passport = LTIPassportFactory(title="consumer1_passport1", consumer=consumer)

        user_count = get_user_model().objects.count()

        # User 1 is using ashley from openedx studio in the course "TEST1"
        user1 = self._authenticate(
            {
                "context_id": "course-v1:TEST1+0001+2020_T1",
                "context_label": "TEST1",
                "context_title": "test course 1",
                "custom_component_display_name": "Forum",
                "launch_presentation_return_url": "",
                "lis_result_sourcedid": "course-v1%3ATEST1%2B0001%2B2020_T1:-c7b2c44b1d",
                "lti_message_type": "basic-lti-launch-request",
                "lti_version": "LTI-1p0",
                "resource_link_id": "-c7b2c44b1d",
                "roles": "Instructor",
                "user_id": "student",
            },
            passport,
        )

        # A new ashley user should have been created
        self.assertEqual(user_count + 1, get_user_model().objects.count())
        self.assertEqual("Educational team", user1.public_username)
        self.assertEqual("", user1.email)
        self.assertEqual(consumer, user1.lti_consumer)
        self.assertNotEqual("student@consumer", user1.username)

        # User 2 is using ashley from openedx studio in the course "TEST1"
        # (it is basically the same LTI launch request than user 1)
        user2 = self._authenticate(
            {
                "context_id": "course-v1:TEST1+0001+2020_T1",
                "context_label": "TEST1",
                "context_title": "test course 1",
                "custom_component_display_name": "Forum",
                "launch_presentation_return_url": "",
                "lis_result_sourcedid": "course-v1%3ATEST1%2B0001%2B2020_T1:-c7b2c44b1d",
                "lti_message_type": "basic-lti-launch-request",
                "lti_version": "LTI-1p0",
                "resource_link_id": "-c7b2c44b1d",
                "roles": "Instructor",
                "user_id": "student",
            },
            passport,
        )

        # user1 and user2 should be the same. No new user should have been created, since they
        # came from the same LTI context_id.
        self.assertEqual(user_count + 1, get_user_model().objects.count())
        self.assertEqual(user1, user2)

        # User 3 is using ashley from openedx studio in the course "TEST2"
        user3 = self._authenticate(
            {
                "context_id": "course-v1:TEST2+0001+2020_T1",
                "context_label": "TEST2",
                "context_title": "test course 2",
                "custom_component_display_name": "Forum",
                "launch_presentation_return_url": "",
                "lis_result_sourcedid": "course-v1%3ATEST2%2B0001%2B2020_T1:-a2a2a2a2a2",
                "lti_message_type": "basic-lti-launch-request",
                "lti_version": "LTI-1p0",
                "resource_link_id": "-a2a2a2a2a2",
                "roles": "Instructor",
                "user_id": "student",
            },
            passport,
        )
        # A new ashley user should have been created for user 3
        self.assertEqual(user_count + 2, get_user_model().objects.count())
        self.assertEqual("Educational team", user3.public_username)
        self.assertEqual("", user3.email)
        self.assertEqual(consumer, user3.lti_consumer)
        self.assertNotEqual("student@consumer", user3.username)
        self.assertNotEqual(user1, user3)
Exemple #20
0
 def _launch_params(lti_parameters):
     consumer = LTIConsumerFactory(slug="test_launch_params")
     passport = LTIPassportFactory(title="test passport", consumer=consumer)
     url = "http://testserver/lti/launch"
     signed_parameters = sign_parameters(passport, lti_parameters, url)
     return LaunchParams(signed_parameters)