Пример #1
0
class CreateLtiUserTest(TestCase):
    """
    Tests for the create_lti_user function in users.py
    """

    def setUp(self):
        super(CreateLtiUserTest, self).setUp()
        self.lti_consumer = LtiConsumer(
            consumer_name='TestConsumer',
            consumer_key='TestKey',
            consumer_secret='TestSecret'
        )
        self.lti_consumer.save()

    def test_create_lti_user_creates_auth_user_model(self):
        users.create_lti_user('lti_user_id', self.lti_consumer)
        self.assertEqual(User.objects.count(), 1)

    @patch('uuid.uuid4', return_value='random_uuid')
    @patch('lti_provider.users.generate_random_edx_username', return_value='edx_id')
    def test_create_lti_user_creates_correct_user(self, uuid_mock, _username_mock):
        users.create_lti_user('lti_user_id', self.lti_consumer)
        self.assertEqual(User.objects.count(), 1)
        user = User.objects.get(username='******')
        self.assertEqual(user.email, '*****@*****.**')
        uuid_mock.assert_called_with()

    @patch('lti_provider.users.generate_random_edx_username', side_effect=['edx_id', 'new_edx_id'])
    def test_unique_username_created(self, username_mock):
        User(username='******').save()
        users.create_lti_user('lti_user_id', self.lti_consumer)
        self.assertEqual(username_mock.call_count, 2)
        self.assertEqual(User.objects.count(), 2)
        user = User.objects.get(username='******')
        self.assertEqual(user.email, '*****@*****.**')
Пример #2
0
 def setUp(self):
     super(SignAndSendReplaceResultTest, self).setUp()
     self.course_key = CourseLocator(
         org='some_org',
         course='some_course',
         run='some_run'
     )
     self.usage_key = BlockUsageLocator(
         course_key=self.course_key,
         block_type='problem',
         block_id='block_id'
     )
     self.user = UserFactory.create()
     consumer = LtiConsumer(
         consumer_name='consumer',
         consumer_key='consumer_key',
         consumer_secret='secret'
     )
     consumer.save()
     outcome = OutcomeService(
         lis_outcome_service_url='http://example.com/service_url',
         lti_consumer=consumer,
     )
     outcome.save()
     self.assignment = GradedAssignment(
         user=self.user,
         course_key=self.course_key,
         usage_key=self.usage_key,
         outcome_service=outcome,
         lis_result_sourcedid='sourcedid',
     )
     self.assignment.save()
Пример #3
0
class UserManagementHelperTest(TestCase):
    """
    Tests for the helper functions in users.py
    """
    shard = 4

    def setUp(self):
        super(UserManagementHelperTest, self).setUp()
        self.request = RequestFactory().post('/')
        self.old_user = UserFactory.create()
        self.new_user = UserFactory.create()
        self.new_user.save()
        self.request.user = self.old_user
        self.lti_consumer = LtiConsumer(
            consumer_name='TestConsumer',
            consumer_key='TestKey',
            consumer_secret='TestSecret'
        )
        self.lti_consumer.save()
        self.lti_user = LtiUser(
            lti_user_id='lti_user_id',
            edx_user=self.new_user
        )

    @patch('django.contrib.auth.authenticate', return_value=None)
    def test_permission_denied_for_unknown_user(self, _authenticate_mock):
        with self.assertRaises(PermissionDenied):
            users.switch_user(self.request, self.lti_user, self.lti_consumer)

    @patch('lti_provider.users.login')
    def test_authenticate_called(self, _login_mock):
        with patch('lti_provider.users.authenticate', return_value=self.new_user) as authenticate:
            users.switch_user(self.request, self.lti_user, self.lti_consumer)
            authenticate.assert_called_with(
                username=self.new_user.username,
                lti_user_id=self.lti_user.lti_user_id,
                lti_consumer=self.lti_consumer
            )

    @patch('lti_provider.users.login')
    def test_login_called(self, login_mock):
        with patch('lti_provider.users.authenticate', return_value=self.new_user):
            users.switch_user(self.request, self.lti_user, self.lti_consumer)
            login_mock.assert_called_with(self.request, self.new_user)

    def test_random_username_generator(self):
        for _idx in range(1000):
            username = users.generate_random_edx_username()
            self.assertLessEqual(len(username), 30, 'Username too long')
            # Check that the username contains only allowable characters
            for char in range(len(username)):
                self.assertIn(
                    username[char], string.ascii_letters + string.digits,
                    "Username has forbidden character '{}'".format(username[char])
                )
Пример #4
0
class BaseOutcomeTest(TestCase):
    """
    Super type for tests of both the leaf and composite outcome celery tasks.
    """
    def setUp(self):
        super(BaseOutcomeTest, self).setUp()
        self.course_key = CourseLocator(
            org='some_org',
            course='some_course',
            run='some_run'
        )
        self.usage_key = BlockUsageLocator(
            course_key=self.course_key,
            block_type='problem',
            block_id='block_id'
        )
        self.user = UserFactory.create()
        self.consumer = LtiConsumer(
            consumer_name='Lti Consumer Name',
            consumer_key='consumer_key',
            consumer_secret='consumer_secret',
            instance_guid='tool_instance_guid'
        )
        self.consumer.save()
        outcome = OutcomeService(
            lis_outcome_service_url='http://example.com/service_url',
            lti_consumer=self.consumer
        )
        outcome.save()
        self.assignment = GradedAssignment(
            user=self.user,
            course_key=self.course_key,
            usage_key=self.usage_key,
            outcome_service=outcome,
            lis_result_sourcedid='sourcedid',
            version_number=1,
        )
        self.assignment.save()

        self.send_score_update_mock = self.setup_patch(
            'lti_provider.outcomes.send_score_update', None
        )

    def setup_patch(self, function_name, return_value):
        """
        Patch a method with a given return value, and return the mock
        """
        mock = MagicMock(return_value=return_value)
        new_patch = patch(function_name, new=mock)
        new_patch.start()
        self.addCleanup(new_patch.stop)
        return mock
Пример #5
0
def lti_launch(request, course_id, usage_id):
    """
    Endpoint for all requests to embed edX content via the LTI protocol. This
    endpoint will be called by a POST message that contains the parameters for
    an LTI launch (we support version 1.2 of the LTI specification):
        http://www.imsglobal.org/lti/ltiv1p2/ltiIMGv1p2.html

    An LTI launch is successful if:
        - The launch contains all the required parameters
        - The launch data is correctly signed using a known client key/secret
          pair
    """
    if not settings.FEATURES['ENABLE_LTI_PROVIDER']:
        return HttpResponseForbidden()

    # Check the LTI parameters, and return 400 if any required parameters are
    # missing
    params = get_required_parameters(request.POST)
    if not params:
        return HttpResponseBadRequest()
    params.update(get_optional_parameters(request.POST))

    # Get the consumer information from either the instance GUID or the consumer
    # key
    try:
        lti_consumer = LtiConsumer.get_or_supplement(
            params.get('tool_consumer_instance_guid', None),
            params['oauth_consumer_key']
        )
    except LtiConsumer.DoesNotExist:
        return HttpResponseForbidden()

    # Check the OAuth signature on the message
    if not SignatureValidator(lti_consumer).verify(request):
        return HttpResponseForbidden()

    # Add the course and usage keys to the parameters array
    try:
        course_key, usage_key = parse_course_and_usage_keys(course_id, usage_id)
    except InvalidKeyError:
        log.error(
            'Invalid course key %s or usage key %s from request %s',
            course_id,
            usage_id,
            request
        )
        raise Http404()
    params['course_key'] = course_key
    params['usage_key'] = usage_key

    # Create an edX account if the user identifed by the LTI launch doesn't have
    # one already, and log the edX account into the platform.
    authenticate_lti_user(request, params['user_id'], lti_consumer)

    # Store any parameters required by the outcome service in order to report
    # scores back later. We know that the consumer exists, since the record was
    # used earlier to verify the oauth signature.
    store_outcome_parameters(params, request.user, lti_consumer)

    return render_courseware(request, params['usage_key'])
Пример #6
0
    def setUp(self):
        super(BaseOutcomeTest, self).setUp()
        self.course_key = CourseLocator(org="some_org", course="some_course", run="some_run")
        self.usage_key = BlockUsageLocator(course_key=self.course_key, block_type="problem", block_id="block_id")
        self.user = UserFactory.create()
        self.consumer = LtiConsumer(
            consumer_name="Lti Consumer Name",
            consumer_key="consumer_key",
            consumer_secret="consumer_secret",
            instance_guid="tool_instance_guid",
        )
        self.consumer.save()
        outcome = OutcomeService(lis_outcome_service_url="http://example.com/service_url", lti_consumer=self.consumer)
        outcome.save()
        self.assignment = GradedAssignment(
            user=self.user,
            course_key=self.course_key,
            usage_key=self.usage_key,
            outcome_service=outcome,
            lis_result_sourcedid="sourcedid",
            version_number=1,
        )
        self.assignment.save()

        self.send_score_update_mock = self.setup_patch("lti_provider.outcomes.send_score_update", None)
Пример #7
0
 def setUp(self):
     super(SendOutcomeTest, self).setUp()
     self.course_key = CourseLocator(
         org='some_org',
         course='some_course',
         run='some_run'
     )
     self.usage_key = BlockUsageLocator(
         course_key=self.course_key,
         block_type='problem',
         block_id='block_id'
     )
     self.user = UserFactory.create()
     self.points_possible = 10
     self.points_earned = 3
     self.generate_xml_mock = self.setup_patch(
         'lti_provider.outcomes.generate_replace_result_xml',
         'replace result XML'
     )
     self.replace_result_mock = self.setup_patch(
         'lti_provider.outcomes.sign_and_send_replace_result',
         'replace result response'
     )
     self.check_result_mock = self.setup_patch(
         'lti_provider.outcomes.check_replace_result_response',
         True
     )
     consumer = LtiConsumer(
         consumer_name='Lti Consumer Name',
         consumer_key='consumer_key',
         consumer_secret='consumer_secret',
         instance_guid='tool_instance_guid'
     )
     consumer.save()
     outcome = OutcomeService(
         lis_outcome_service_url='http://example.com/service_url',
         lti_consumer=consumer
     )
     outcome.save()
     self.assignment = GradedAssignment(
         user=self.user,
         course_key=self.course_key,
         usage_key=self.usage_key,
         outcome_service=outcome,
         lis_result_sourcedid='sourcedid',
     )
     self.assignment.save()
Пример #8
0
 def setUp(self):
     super(CreateLtiUserTest, self).setUp()
     self.lti_consumer = LtiConsumer(
         consumer_name='TestConsumer',
         consumer_key='TestKey',
         consumer_secret='TestSecret'
     )
     self.lti_consumer.save()
Пример #9
0
class BaseOutcomeTest(TestCase):
    """
    Super type for tests of both the leaf and composite outcome celery tasks.
    """
    def setUp(self):
        super(BaseOutcomeTest, self).setUp()
        self.course_key = CourseLocator(org='some_org',
                                        course='some_course',
                                        run='some_run')
        self.usage_key = BlockUsageLocator(course_key=self.course_key,
                                           block_type='problem',
                                           block_id='block_id')
        self.user = UserFactory.create()
        self.consumer = LtiConsumer(consumer_name='Lti Consumer Name',
                                    consumer_key='consumer_key',
                                    consumer_secret='consumer_secret',
                                    instance_guid='tool_instance_guid')
        self.consumer.save()
        outcome = OutcomeService(
            lis_outcome_service_url='http://example.com/service_url',
            lti_consumer=self.consumer)
        outcome.save()
        self.assignment = GradedAssignment(
            user=self.user,
            course_key=self.course_key,
            usage_key=self.usage_key,
            outcome_service=outcome,
            lis_result_sourcedid='sourcedid',
            version_number=1,
        )
        self.assignment.save()

        self.send_score_update_mock = self.setup_patch(
            'lti_provider.outcomes.send_score_update', None)

    def setup_patch(self, function_name, return_value):
        """
        Patch a method with a given return value, and return the mock
        """
        mock = MagicMock(return_value=return_value)
        new_patch = patch(function_name, new=mock)
        new_patch.start()
        self.addCleanup(new_patch.stop)
        return mock
Пример #10
0
def lti_launch(request, course_id, usage_id):
    """
    Endpoint for all requests to embed edX content via the LTI protocol. This
    endpoint will be called by a POST message that contains the parameters for
    an LTI launch (we support version 1.2 of the LTI specification):
        http://www.imsglobal.org/lti/ltiv1p2/ltiIMGv1p2.html

    An LTI launch is successful if:
        - The launch contains all the required parameters
        - The launch data is correctly signed using a known client key/secret
          pair
    """
    if not settings.FEATURES['ENABLE_LTI_PROVIDER']:
        return HttpResponseForbidden()

    # Check the LTI parameters, and return 400 if any required parameters are
    # missing
    params = get_required_parameters(request.POST)
    if not params:
        return HttpResponseBadRequest()
    params.update(get_optional_parameters(request.POST))

    # Get the consumer information from either the instance GUID or the consumer
    # key
    try:
        lti_consumer = LtiConsumer.get_or_supplement(
            params.get('tool_consumer_instance_guid', None),
            params['oauth_consumer_key'])
    except LtiConsumer.DoesNotExist:
        return HttpResponseForbidden()

    # Check the OAuth signature on the message
    if not SignatureValidator(lti_consumer).verify(request):
        return HttpResponseForbidden()

    # Add the course and usage keys to the parameters array
    try:
        course_key, usage_key = parse_course_and_usage_keys(
            course_id, usage_id)
    except InvalidKeyError:
        log.error('Invalid course key %s or usage key %s from request %s',
                  course_id, usage_id, request)
        raise Http404()
    params['course_key'] = course_key
    params['usage_key'] = usage_key

    # Create an edX account if the user identifed by the LTI launch doesn't have
    # one already, and log the edX account into the platform.
    authenticate_lti_user(request, params['user_id'], lti_consumer)

    # Store any parameters required by the outcome service in order to report
    # scores back later. We know that the consumer exists, since the record was
    # used earlier to verify the oauth signature.
    store_outcome_parameters(params, request.user, lti_consumer)

    return render_courseware(request, params['usage_key'])
Пример #11
0
 def create_outcome_service(self, id_suffix):
     """
     Create and save a new OutcomeService model in the test database. The
     OutcomeService model requires an LtiConsumer model, so we create one of
     those as well. The method takes an ID string that is used to ensure that
     unique fields do not conflict.
     """
     lti_consumer = LtiConsumer(
         consumer_name='lti_consumer_name' + id_suffix,
         consumer_key='lti_consumer_key' + id_suffix,
         consumer_secret='lti_consumer_secret' + id_suffix,
         instance_guid='lti_instance_guid' + id_suffix)
     lti_consumer.save()
     outcome_service = OutcomeService(
         lis_outcome_service_url='https://example.com/outcomes/' +
         id_suffix,
         lti_consumer=lti_consumer)
     outcome_service.save()
     return outcome_service
Пример #12
0
 def setUp(self):
     super(StoreOutcomeParametersTest, self).setUp()
     self.user = UserFactory.create()
     self.course_key = CourseLocator(
         org='some_org',
         course='some_course',
         run='some_run'
     )
     self.usage_key = BlockUsageLocator(
         course_key=self.course_key,
         block_type='problem',
         block_id='block_id'
     )
     self.consumer = LtiConsumer(
         consumer_name='consumer',
         consumer_key='consumer_key',
         consumer_secret='secret'
     )
     self.consumer.save()
Пример #13
0
 def create_outcome_service(self, id_suffix):
     """
     Create and save a new OutcomeService model in the test database. The
     OutcomeService model requires an LtiConsumer model, so we create one of
     those as well. The method takes an ID string that is used to ensure that
     unique fields do not conflict.
     """
     lti_consumer = LtiConsumer(
         consumer_name='lti_consumer_name' + id_suffix,
         consumer_key='lti_consumer_key' + id_suffix,
         consumer_secret='lti_consumer_secret' + id_suffix,
         instance_guid='lti_instance_guid' + id_suffix
     )
     lti_consumer.save()
     outcome_service = OutcomeService(
         lis_outcome_service_url='https://example.com/outcomes/' + id_suffix,
         lti_consumer=lti_consumer
     )
     outcome_service.save()
     return outcome_service
Пример #14
0
 def setUp(self):
     super(AuthenticateLtiUserTest, self).setUp()
     self.lti_consumer = LtiConsumer(
         consumer_name='TestConsumer',
         consumer_key='TestKey',
         consumer_secret='TestSecret'
     )
     self.lti_consumer.save()
     self.lti_user_id = 'lti_user_id'
     self.edx_user_id = 'edx_user_id'
     self.old_user = UserFactory.create()
     self.request = RequestFactory().post('/')
     self.request.user = self.old_user
Пример #15
0
    def setUp(self):
        super(BaseOutcomeTest, self).setUp()
        self.course_key = CourseLocator(
            org='some_org',
            course='some_course',
            run='some_run'
        )
        self.usage_key = BlockUsageLocator(
            course_key=self.course_key,
            block_type='problem',
            block_id='block_id'
        )
        self.user = UserFactory.create()
        self.consumer = LtiConsumer(
            consumer_name='Lti Consumer Name',
            consumer_key='consumer_key',
            consumer_secret='consumer_secret',
            instance_guid='tool_instance_guid'
        )
        self.consumer.save()
        outcome = OutcomeService(
            lis_outcome_service_url='http://example.com/service_url',
            lti_consumer=self.consumer
        )
        outcome.save()
        self.assignment = GradedAssignment(
            user=self.user,
            course_key=self.course_key,
            usage_key=self.usage_key,
            outcome_service=outcome,
            lis_result_sourcedid='sourcedid',
            version_number=1,
        )
        self.assignment.save()

        self.send_score_update_mock = self.setup_patch(
            'lti_provider.outcomes.send_score_update', None
        )
Пример #16
0
class CreateLtiUserTest(TestCase):
    """
    Tests for the create_lti_user function in users.py
    """
    shard = 4

    def setUp(self):
        super(CreateLtiUserTest, self).setUp()
        self.lti_consumer = LtiConsumer(consumer_name='TestConsumer',
                                        consumer_key='TestKey',
                                        consumer_secret='TestSecret')
        self.lti_consumer.save()

    def test_create_lti_user_creates_auth_user_model(self):
        users.create_lti_user('lti_user_id', self.lti_consumer)
        self.assertEqual(User.objects.count(), 1)

    @patch('uuid.uuid4', return_value='random_uuid')
    @patch('lti_provider.users.generate_random_edx_username',
           return_value='edx_id')
    def test_create_lti_user_creates_correct_user(self, uuid_mock,
                                                  _username_mock):
        users.create_lti_user('lti_user_id', self.lti_consumer)
        self.assertEqual(User.objects.count(), 1)
        user = User.objects.get(username='******')
        self.assertEqual(user.email, '*****@*****.**')
        uuid_mock.assert_called_with()

    @patch('lti_provider.users.generate_random_edx_username',
           side_effect=['edx_id', 'new_edx_id'])
    def test_unique_username_created(self, username_mock):
        User(username='******').save()
        users.create_lti_user('lti_user_id', self.lti_consumer)
        self.assertEqual(username_mock.call_count, 2)
        self.assertEqual(User.objects.count(), 2)
        user = User.objects.get(username='******')
        self.assertEqual(user.email, '*****@*****.**')
Пример #17
0
 def setUp(self):
     super(LtiBackendTest, self).setUp()
     self.edx_user = UserFactory.create()
     self.edx_user.save()
     self.lti_consumer = LtiConsumer(
         consumer_key="Consumer Key",
         consumer_secret="Consumer Secret"
     )
     self.lti_consumer.save()
     self.lti_user_id = 'LTI User ID'
     LtiUser(
         lti_consumer=self.lti_consumer,
         lti_user_id=self.lti_user_id,
         edx_user=self.edx_user
     ).save()
Пример #18
0
 def setUp(self):
     super(SendOutcomeTest, self).setUp()
     self.course_key = CourseLocator(org='some_org',
                                     course='some_course',
                                     run='some_run')
     self.usage_key = BlockUsageLocator(course_key=self.course_key,
                                        block_type='problem',
                                        block_id='block_id')
     self.user = UserFactory.create()
     self.points_possible = 10
     self.points_earned = 3
     self.generate_xml_mock = self.setup_patch(
         'lti_provider.outcomes.generate_replace_result_xml',
         'replace result XML')
     self.replace_result_mock = self.setup_patch(
         'lti_provider.outcomes.sign_and_send_replace_result',
         'replace result response')
     self.check_result_mock = self.setup_patch(
         'lti_provider.outcomes.check_replace_result_response', True)
     consumer = LtiConsumer(consumer_name='Lti Consumer Name',
                            consumer_key='consumer_key',
                            consumer_secret='consumer_secret',
                            instance_guid='tool_instance_guid')
     consumer.save()
     outcome = OutcomeService(
         lis_outcome_service_url='http://example.com/service_url',
         lti_consumer=consumer)
     outcome.save()
     self.assignment = GradedAssignment(
         user=self.user,
         course_key=self.course_key,
         usage_key=self.usage_key,
         outcome_service=outcome,
         lis_result_sourcedid='sourcedid',
     )
     self.assignment.save()
Пример #19
0
 def setUp(self):
     super(UserManagementHelperTest, self).setUp()
     self.request = RequestFactory().post('/')
     self.old_user = UserFactory.create()
     self.new_user = UserFactory.create()
     self.new_user.save()
     self.request.user = self.old_user
     self.lti_consumer = LtiConsumer(
         consumer_name='TestConsumer',
         consumer_key='TestKey',
         consumer_secret='TestSecret'
     )
     self.lti_consumer.save()
     self.lti_user = LtiUser(
         lti_user_id='lti_user_id',
         edx_user=self.new_user
     )
Пример #20
0
 def test_create_two_lti_consumers_with_empty_instance_guid(self):
     """
     Test ability to create two or more LTI consumers through the Django admin
     with empty instance_guid field.
     A blank guid field is required when a customer enables a new secret/key combination for
     LTI integration with their LMS.
     """
     lti_consumer_first = LtiConsumer(
         consumer_name='lti_consumer_name_second',
         consumer_key='lti_consumer_key_second',
         consumer_secret='lti_consumer_secret_second',
         instance_guid='')
     lti_consumer_first.save()
     lti_consumer_second = LtiConsumer(
         consumer_name='lti_consumer_name_third',
         consumer_key='lti_consumer_key_third',
         consumer_secret='lti_consumer_secret_third',
         instance_guid='')
     lti_consumer_second.save()
     count = LtiConsumer.objects.count()
     self.assertEqual(count, 3)
Пример #21
0
 def setUp(self):
     super(StoreOutcomeParametersTest, self).setUp()
     self.user = UserFactory.create()
     self.course_key = CourseLocator(
         org='some_org',
         course='some_course',
         run='some_run'
     )
     self.usage_key = BlockUsageLocator(
         course_key=self.course_key,
         block_type='problem',
         block_id='block_id'
     )
     self.consumer = LtiConsumer(
         consumer_name='consumer',
         consumer_key='consumer_key',
         consumer_secret='secret'
     )
     self.consumer.save()
Пример #22
0
def lti_run(request):
    """
    This method can be reached in two ways, and must always follow a POST to
    lti_launch:
     - The user was logged in, so this method was called by lti_launch
     - The user was not logged in, so the login process redirected them back here.

    In either case, the session was populated by lti_launch, so all the required
    LTI parameters will be stored there. Note that the request passed here may
    or may not contain the LTI parameters (depending on how the user got here),
    and so we should only use LTI parameters from the session.

    Users should never call this view directly; if a user attempts to call it
    without having first gone through lti_launch (and had the LTI parameters
    stored in the session) they will get a 403 response.
    """

    # Check the parameters to make sure that the session is associated with a
    # valid LTI launch
    params = restore_params_from_session(request)
    if not params:
        # This view has been called without first setting the session
        return HttpResponseForbidden()
    # Remove the parameters from the session to prevent replay
    del request.session[LTI_SESSION_KEY]

    # Store any parameters required by the outcome service in order to report
    # scores back later. We know that the consumer exists, since the record was
    # used earlier to verify the oauth signature.
    lti_consumer = LtiConsumer.get_or_supplement(
        params.get('tool_consumer_instance_guid', None),
        params['oauth_consumer_key']
    )
    store_outcome_parameters(params, request.user, lti_consumer)

    return render_courseware(request, params['usage_key'])
Пример #23
0
 def test_create_two_lti_consumers_with_empty_instance_guid(self):
     """
     Test ability to create two or more LTI consumers through the Django admin
     with empty instance_guid field.
     A blank guid field is required when a customer enables a new secret/key combination for
     LTI integration with their LMS.
     """
     lti_consumer_first = LtiConsumer(
         consumer_name='lti_consumer_name_second',
         consumer_key='lti_consumer_key_second',
         consumer_secret='lti_consumer_secret_second',
         instance_guid=''
     )
     lti_consumer_first.save()
     lti_consumer_second = LtiConsumer(
         consumer_name='lti_consumer_name_third',
         consumer_key='lti_consumer_key_third',
         consumer_secret='lti_consumer_secret_third',
         instance_guid=''
     )
     lti_consumer_second.save()
     count = LtiConsumer.objects.count()
     self.assertEqual(count, 3)
Пример #24
0
 def setUp(self):
     super(CreateLtiUserTest, self).setUp()
     self.lti_consumer = LtiConsumer(consumer_name='TestConsumer',
                                     consumer_key='TestKey',
                                     consumer_secret='TestSecret')
     self.lti_consumer.save()
Пример #25
0
class StoreOutcomeParametersTest(TestCase):
    """
    Tests for the store_outcome_parameters method in outcomes.py
    """
    def setUp(self):
        super(StoreOutcomeParametersTest, self).setUp()
        self.user = UserFactory.create()
        self.course_key = CourseLocator(org='some_org',
                                        course='some_course',
                                        run='some_run')
        self.usage_key = BlockUsageLocator(course_key=self.course_key,
                                           block_type='problem',
                                           block_id='block_id')
        self.consumer = LtiConsumer(consumer_name='consumer',
                                    consumer_key='consumer_key',
                                    consumer_secret='secret')
        self.consumer.save()

    def get_valid_request_params(self):
        """
        Returns a dictionary containing a complete set of required LTI
        parameters.
        """
        return {
            'lis_result_sourcedid': 'sourcedid',
            'lis_outcome_service_url': 'http://example.com/service_url',
            'oauth_consumer_key': 'consumer_key',
            'tool_consumer_instance_guid': 'tool_instance_guid',
            'usage_key': self.usage_key,
            'course_key': self.course_key,
        }

    def test_graded_assignment_created(self):
        params = self.get_valid_request_params()
        with self.assertNumQueries(8):
            outcomes.store_outcome_parameters(params, self.user, self.consumer)
        assignment = GradedAssignment.objects.get(
            lis_result_sourcedid=params['lis_result_sourcedid'])
        self.assertEqual(assignment.course_key, self.course_key)
        self.assertEqual(assignment.usage_key, self.usage_key)
        self.assertEqual(assignment.user, self.user)

    def test_outcome_service_created(self):
        params = self.get_valid_request_params()
        with self.assertNumQueries(8):
            outcomes.store_outcome_parameters(params, self.user, self.consumer)
        outcome = OutcomeService.objects.get(lti_consumer=self.consumer)
        self.assertEqual(outcome.lti_consumer, self.consumer)

    def test_graded_assignment_references_outcome_service(self):
        params = self.get_valid_request_params()
        with self.assertNumQueries(8):
            outcomes.store_outcome_parameters(params, self.user, self.consumer)
        outcome = OutcomeService.objects.get(lti_consumer=self.consumer)
        assignment = GradedAssignment.objects.get(
            lis_result_sourcedid=params['lis_result_sourcedid'])
        self.assertEqual(assignment.outcome_service, outcome)

    def test_no_duplicate_graded_assignments(self):
        params = self.get_valid_request_params()
        with self.assertNumQueries(8):
            outcomes.store_outcome_parameters(params, self.user, self.consumer)
        with self.assertNumQueries(2):
            outcomes.store_outcome_parameters(params, self.user, self.consumer)
        assignments = GradedAssignment.objects.filter(
            lis_result_sourcedid=params['lis_result_sourcedid'])
        self.assertEqual(len(assignments), 1)

    def test_no_duplicate_outcome_services(self):
        params = self.get_valid_request_params()
        with self.assertNumQueries(8):
            outcomes.store_outcome_parameters(params, self.user, self.consumer)
        with self.assertNumQueries(2):
            outcomes.store_outcome_parameters(params, self.user, self.consumer)
        outcome_services = OutcomeService.objects.filter(
            lti_consumer=self.consumer)
        self.assertEqual(len(outcome_services), 1)

    def test_no_db_update_for_ungraded_assignment(self):
        params = self.get_valid_request_params()
        del params['lis_result_sourcedid']
        with self.assertNumQueries(0):
            outcomes.store_outcome_parameters(params, self.user, self.consumer)

    def test_no_db_update_for_bad_request(self):
        params = self.get_valid_request_params()
        del params['lis_outcome_service_url']
        with self.assertNumQueries(0):
            outcomes.store_outcome_parameters(params, self.user, self.consumer)

    def test_db_record_created_without_consumer_id(self):
        params = self.get_valid_request_params()
        del params['tool_consumer_instance_guid']
        with self.assertNumQueries(8):
            outcomes.store_outcome_parameters(params, self.user, self.consumer)
        self.assertEqual(GradedAssignment.objects.count(), 1)
        self.assertEqual(OutcomeService.objects.count(), 1)
Пример #26
0
def lti_launch(request, course_id, usage_id):
    """
    Endpoint for all requests to embed edX content via the LTI protocol. This
    endpoint will be called by a POST message that contains the parameters for
    an LTI launch (we support version 1.2 of the LTI specification):
        http://www.imsglobal.org/lti/ltiv1p2/ltiIMGv1p2.html

    An LTI launch is successful if:
        - The launch contains all the required parameters
        - The launch data is correctly signed using a known client key/secret
          pair
        - The user is logged into the edX instance

    Authentication in this view is a little tricky, since clients use a POST
    with parameters to fetch it. We can't just use @login_required since in the
    case where a user is not logged in it will redirect back after login using a
    GET request, which would lose all of our LTI parameters.

    Instead, we verify the LTI launch in this view before checking if the user
    is logged in, and store the required LTI parameters in the session. Then we
    do the authentication check, and if login is required we redirect back to
    the lti_run view. If the user is already logged in, we just call that view
    directly.
    """

    if not settings.FEATURES['ENABLE_LTI_PROVIDER']:
        return HttpResponseForbidden()

    # Check the OAuth signature on the message
    try:
        if not SignatureValidator().verify(request):
            return HttpResponseForbidden()
    except LtiConsumer.DoesNotExist:
        return HttpResponseForbidden()

    params = get_required_parameters(request.POST)
    if not params:
        return HttpResponseBadRequest()
    params.update(get_optional_parameters(request.POST))

    # Store the course, and usage ID in the session to prevent privilege
    # escalation if a staff member in one course tries to access material in
    # another.
    try:
        course_key, usage_key = parse_course_and_usage_keys(course_id, usage_id)
    except InvalidKeyError:
        log.error(
            'Invalid course key %s or usage key %s from request %s',
            course_id,
            usage_id,
            request
        )
        raise Http404()
    params['course_key'] = course_key
    params['usage_key'] = usage_key

    try:
        lti_consumer = LtiConsumer.get_or_supplement(
            params.get('tool_consumer_instance_guid', None),
            params['oauth_consumer_key']
        )
    except LtiConsumer.DoesNotExist:
        return HttpResponseForbidden()

    # Create an edX account if the user identifed by the LTI launch doesn't have
    # one already, and log the edX account into the platform.
    authenticate_lti_user(request, params['user_id'], lti_consumer)

    request.session[LTI_SESSION_KEY] = params

    return lti_run(request)
Пример #27
0
class AuthenticateLtiUserTest(TestCase):
    """
    Tests for the authenticate_lti_user function in users.py
    """
    def setUp(self):
        super(AuthenticateLtiUserTest, self).setUp()
        self.lti_consumer = LtiConsumer(
            consumer_name='TestConsumer',
            consumer_key='TestKey',
            consumer_secret='TestSecret'
        )
        self.lti_consumer.save()
        self.lti_user_id = 'lti_user_id'
        self.edx_user_id = 'edx_user_id'
        self.old_user = UserFactory.create()
        self.request = RequestFactory().post('/')
        self.request.user = self.old_user

    def create_lti_user_model(self):
        """
        Generate and save a User and an LTI user model
        """
        edx_user = User(username=self.edx_user_id)
        edx_user.save()
        lti_user = LtiUser(
            lti_consumer=self.lti_consumer,
            lti_user_id=self.lti_user_id,
            edx_user=edx_user
        )
        lti_user.save()
        return lti_user

    def test_authentication_with_new_user(self, _create_user, switch_user):
        lti_user = MagicMock()
        lti_user.edx_user_id = self.edx_user_id
        with patch('lti_provider.users.create_lti_user', return_value=lti_user) as create_user:
            users.authenticate_lti_user(self.request, self.lti_user_id, self.lti_consumer)
            create_user.assert_called_with(self.lti_user_id, self.lti_consumer)
            switch_user.assert_called_with(self.request, lti_user, self.lti_consumer)

    def test_authentication_with_authenticated_user(self, create_user, switch_user):
        lti_user = self.create_lti_user_model()
        self.request.user = lti_user.edx_user
        self.request.user.is_authenticated = MagicMock(return_value=True)
        users.authenticate_lti_user(self.request, self.lti_user_id, self.lti_consumer)
        self.assertFalse(create_user.called)
        self.assertFalse(switch_user.called)

    def test_authentication_with_unauthenticated_user(self, create_user, switch_user):
        lti_user = self.create_lti_user_model()
        self.request.user = lti_user.edx_user
        self.request.user.is_authenticated = MagicMock(return_value=False)
        users.authenticate_lti_user(self.request, self.lti_user_id, self.lti_consumer)
        self.assertFalse(create_user.called)
        switch_user.assert_called_with(self.request, lti_user, self.lti_consumer)

    def test_authentication_with_wrong_user(self, create_user, switch_user):
        lti_user = self.create_lti_user_model()
        self.request.user = self.old_user
        self.request.user.is_authenticated = MagicMock(return_value=True)
        users.authenticate_lti_user(self.request, self.lti_user_id, self.lti_consumer)
        self.assertFalse(create_user.called)
        switch_user.assert_called_with(self.request, lti_user, self.lti_consumer)
Пример #28
0
class StoreOutcomeParametersTest(TestCase):
    """
    Tests for the store_outcome_parameters method in outcomes.py
    """

    def setUp(self):
        super(StoreOutcomeParametersTest, self).setUp()
        self.user = UserFactory.create()
        self.course_key = CourseLocator(
            org='some_org',
            course='some_course',
            run='some_run'
        )
        self.usage_key = BlockUsageLocator(
            course_key=self.course_key,
            block_type='problem',
            block_id='block_id'
        )
        self.consumer = LtiConsumer(
            consumer_name='consumer',
            consumer_key='consumer_key',
            consumer_secret='secret'
        )
        self.consumer.save()

    def get_valid_request_params(self):
        """
        Returns a dictionary containing a complete set of required LTI
        parameters.
        """
        return {
            'lis_result_sourcedid': 'sourcedid',
            'lis_outcome_service_url': 'http://example.com/service_url',
            'oauth_consumer_key': 'consumer_key',
            'tool_consumer_instance_guid': 'tool_instance_guid',
            'usage_key': self.usage_key,
            'course_key': self.course_key,
        }

    def test_graded_assignment_created(self):
        params = self.get_valid_request_params()
        with self.assertNumQueries(4):
            outcomes.store_outcome_parameters(params, self.user, self.consumer)
        assignment = GradedAssignment.objects.get(
            lis_result_sourcedid=params['lis_result_sourcedid']
        )
        self.assertEqual(assignment.course_key, self.course_key)
        self.assertEqual(assignment.usage_key, self.usage_key)
        self.assertEqual(assignment.user, self.user)

    def test_outcome_service_created(self):
        params = self.get_valid_request_params()
        with self.assertNumQueries(4):
            outcomes.store_outcome_parameters(params, self.user, self.consumer)
        outcome = OutcomeService.objects.get(
            lti_consumer=self.consumer
        )
        self.assertEqual(outcome.lti_consumer, self.consumer)

    def test_graded_assignment_references_outcome_service(self):
        params = self.get_valid_request_params()
        with self.assertNumQueries(4):
            outcomes.store_outcome_parameters(params, self.user, self.consumer)
        outcome = OutcomeService.objects.get(
            lti_consumer=self.consumer
        )
        assignment = GradedAssignment.objects.get(
            lis_result_sourcedid=params['lis_result_sourcedid']
        )
        self.assertEqual(assignment.outcome_service, outcome)

    def test_no_duplicate_graded_assignments(self):
        params = self.get_valid_request_params()
        with self.assertNumQueries(4):
            outcomes.store_outcome_parameters(params, self.user, self.consumer)
        with self.assertNumQueries(2):
            outcomes.store_outcome_parameters(params, self.user, self.consumer)
        assignments = GradedAssignment.objects.filter(
            lis_result_sourcedid=params['lis_result_sourcedid']
        )
        self.assertEqual(len(assignments), 1)

    def test_no_duplicate_outcome_services(self):
        params = self.get_valid_request_params()
        with self.assertNumQueries(4):
            outcomes.store_outcome_parameters(params, self.user, self.consumer)
        with self.assertNumQueries(2):
            outcomes.store_outcome_parameters(params, self.user, self.consumer)
        outcome_services = OutcomeService.objects.filter(
            lti_consumer=self.consumer
        )
        self.assertEqual(len(outcome_services), 1)

    def test_no_db_update_for_ungraded_assignment(self):
        params = self.get_valid_request_params()
        del params['lis_result_sourcedid']
        with self.assertNumQueries(0):
            outcomes.store_outcome_parameters(params, self.user, self.consumer)

    def test_no_db_update_for_bad_request(self):
        params = self.get_valid_request_params()
        del params['lis_outcome_service_url']
        with self.assertNumQueries(0):
            outcomes.store_outcome_parameters(params, self.user, self.consumer)

    def test_db_record_created_without_consumer_id(self):
        params = self.get_valid_request_params()
        del params['tool_consumer_instance_guid']
        with self.assertNumQueries(4):
            outcomes.store_outcome_parameters(params, self.user, self.consumer)
        self.assertEqual(GradedAssignment.objects.count(), 1)
        self.assertEqual(OutcomeService.objects.count(), 1)
Пример #29
0
class LtiBackendTest(TestCase):
    """
    Tests for the authentication backend that authenticates LTI users.
    """

    def setUp(self):
        super(LtiBackendTest, self).setUp()
        self.edx_user = UserFactory.create()
        self.edx_user.save()
        self.lti_consumer = LtiConsumer(
            consumer_key="Consumer Key",
            consumer_secret="Consumer Secret"
        )
        self.lti_consumer.save()
        self.lti_user_id = 'LTI User ID'
        LtiUser(
            lti_consumer=self.lti_consumer,
            lti_user_id=self.lti_user_id,
            edx_user=self.edx_user
        ).save()

    def test_valid_user_authenticates(self):
        user = users.LtiBackend().authenticate(
            username=self.edx_user.username,
            lti_user_id=self.lti_user_id,
            lti_consumer=self.lti_consumer
        )
        self.assertEqual(user, self.edx_user)

    def test_missing_user_returns_none(self):
        user = users.LtiBackend().authenticate(
            username=self.edx_user.username,
            lti_user_id='Invalid Username',
            lti_consumer=self.lti_consumer
        )
        self.assertIsNone(user)

    def test_non_lti_user_returns_none(self):
        non_edx_user = UserFactory.create()
        non_edx_user.save()
        user = users.LtiBackend().authenticate(
            username=non_edx_user.username,
        )
        self.assertIsNone(user)

    def test_missing_lti_id_returns_null(self):
        user = users.LtiBackend().authenticate(
            username=self.edx_user.username,
            lti_consumer=self.lti_consumer
        )
        self.assertIsNone(user)

    def test_missing_lti_consumer_returns_null(self):
        user = users.LtiBackend().authenticate(
            username=self.edx_user.username,
            lti_user_id=self.lti_user_id,
        )
        self.assertIsNone(user)

    def test_existing_user_returned_by_get_user(self):
        user = users.LtiBackend().get_user(self.edx_user.id)
        self.assertEqual(user, self.edx_user)

    def test_get_user_returns_none_for_invalid_user(self):
        user = users.LtiBackend().get_user(-1)
        self.assertIsNone(user)
Пример #30
0
def lti_launch(request, course_id, usage_id):
    """
    Endpoint for all requests to embed edX content via the LTI protocol. This
    endpoint will be called by a POST message that contains the parameters for
    an LTI launch (we support version 1.2 of the LTI specification):
        http://www.imsglobal.org/lti/ltiv1p2/ltiIMGv1p2.html

    An LTI launch is successful if:
        - The launch contains all the required parameters
        - The launch data is correctly signed using a known client key/secret
          pair
        - The user is logged into the edX instance

    Authentication in this view is a little tricky, since clients use a POST
    with parameters to fetch it. We can't just use @login_required since in the
    case where a user is not logged in it will redirect back after login using a
    GET request, which would lose all of our LTI parameters.

    Instead, we verify the LTI launch in this view before checking if the user
    is logged in, and store the required LTI parameters in the session. Then we
    do the authentication check, and if login is required we redirect back to
    the lti_run view. If the user is already logged in, we just call that view
    directly.
    """

    if not settings.FEATURES['ENABLE_LTI_PROVIDER']:
        return HttpResponseForbidden()

    # Check the OAuth signature on the message
    try:
        if not SignatureValidator().verify(request):
            return HttpResponseForbidden()
    except LtiConsumer.DoesNotExist:
        return HttpResponseForbidden()

    params = get_required_parameters(request.POST)
    if not params:
        return HttpResponseBadRequest()
    params.update(get_optional_parameters(request.POST))

    # Store the course, and usage ID in the session to prevent privilege
    # escalation if a staff member in one course tries to access material in
    # another.
    try:
        course_key, usage_key = parse_course_and_usage_keys(
            course_id, usage_id)
    except InvalidKeyError:
        log.error('Invalid course key %s or usage key %s from request %s',
                  course_id, usage_id, request)
        raise Http404()
    params['course_key'] = course_key
    params['usage_key'] = usage_key

    try:
        lti_consumer = LtiConsumer.get_or_supplement(
            params.get('tool_consumer_instance_guid', None),
            params['oauth_consumer_key'])
    except LtiConsumer.DoesNotExist:
        return HttpResponseForbidden()

    # Create an edX account if the user identifed by the LTI launch doesn't have
    # one already, and log the edX account into the platform.
    authenticate_lti_user(request, params['user_id'], lti_consumer)

    request.session[LTI_SESSION_KEY] = params

    return lti_run(request)