Example #1
0
 def test_verification_parameters(self, verify_mock):
     """
     Verify that the signature validaton library method is called using the
     correct parameters derived from the HttpRequest.
     """
     body = 'oauth_signature_method=HMAC-SHA1&oauth_version=1.0'
     content_type = 'application/x-www-form-urlencoded'
     request = RequestFactory().post('/url',
                                     body,
                                     content_type=content_type)
     headers = {'Content-Type': content_type}
     SignatureValidator(self.lti_consumer).verify(request)
     verify_mock.assert_called_once_with(request.build_absolute_uri(),
                                         'POST', body, headers)
Example #2
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
    if not SignatureValidator().verify(request):
        return HttpResponseForbidden()

    params = get_required_parameters(request.POST)
    if not params:
        return HttpResponseBadRequest()
    # 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.
    params['course_id'] = course_id
    params['usage_id'] = usage_id
    request.session[LTI_SESSION_KEY] = params

    if not request.user.is_authenticated():
        run_url = reverse('lti_provider.views.lti_run')
        return redirect_to_login(run_url, settings.LOGIN_URL)

    return lti_run(request)
Example #3
0
 def test_null_nonce(self):
     """
     Verify that check_nonce fails with a key that is None
     """
     nonce = None
     self.assertFalse(SignatureValidator().check_nonce(nonce))
Example #4
0
 def test_empty_nonce(self):
     """
     Verify that check_nonce fails with a key that is an empty string
     """
     nonce = ''
     self.assertFalse(SignatureValidator().check_nonce(nonce))
Example #5
0
 def test_long_nonce(self):
     """
     Verify that check_nonce fails with a key that is too long
     """
     nonce = '01234567890123456789012345678901234567890123456789012345678901234'
     self.assertFalse(SignatureValidator().check_nonce(nonce))
Example #6
0
 def test_valid_nonce(self):
     """
     Verify that check_nonce succeeds with a key of maximum length
     """
     nonce = '0123456789012345678901234567890123456789012345678901234567890123'
     self.assertTrue(SignatureValidator().check_nonce(nonce))
Example #7
0
 def test_null_client_key(self):
     """
     Verify that check_client_key fails with a key that is None
     """
     key = None
     self.assertFalse(SignatureValidator().check_client_key(key))
Example #8
0
 def test_empty_client_key(self):
     """
     Verify that check_client_key fails with a key that is an empty string
     """
     key = ''
     self.assertFalse(SignatureValidator().check_client_key(key))
Example #9
0
 def test_long_client_key(self):
     """
     Verify that check_client_key fails with a key that is too long
     """
     key = '0123456789012345678901234567890123456789'
     self.assertFalse(SignatureValidator().check_client_key(key))
Example #10
0
 def test_valid_client_key(self):
     """
     Verify that check_client_key succeeds with a valid key
     """
     key = 'valid_key'
     self.assertTrue(SignatureValidator().check_client_key(key))
Example #11
0
 def test_invalid_nonce(self, nonce):
     """
     Verify that check_nonce fails with badly formatted nonce
     """
     self.assertFalse(
         SignatureValidator(self.lti_consumer).check_nonce(nonce))
Example #12
0
 def test_invalid_client_key(self, key):
     """
     Verify that check_client_key fails with a disallowed key
     """
     self.assertFalse(
         SignatureValidator(self.lti_consumer).check_client_key(key))
 def test_valid_client_key(self):
     """
     Verify that check_client_key succeeds with a valid key
     """
     key = self.lti_consumer.consumer_key
     self.assertTrue(SignatureValidator(self.lti_consumer).check_client_key(key))
Example #14
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)