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)
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())
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())
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)
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)
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})
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())
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"
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)
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)), )
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)
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
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)
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)
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, )
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)
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)
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}/")
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)
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)