diff --git a/common/lib/xmodule/xmodule/lti_module.py b/common/lib/xmodule/xmodule/lti_module.py
index c0c4feb55e..2a529e0ef3 100644
--- a/common/lib/xmodule/xmodule/lti_module.py
+++ b/common/lib/xmodule/xmodule/lti_module.py
@@ -14,6 +14,7 @@ import urllib
from xmodule.editing_module import MetadataOnlyEditingDescriptor
from xmodule.x_module import XModule
+from xmodule.course_module import CourseDescriptor
from pkg_resources import resource_string
from xblock.core import String, Scope, List
@@ -106,9 +107,9 @@ class LTIModule(LTIFields, XModule):
- % for param_name, param_value in custom_parameters.items():
-
- %endfor
+
+
+
@@ -129,13 +130,9 @@ class LTIModule(LTIFields, XModule):
""" Renders parameters to template. """
# Obtains client_key and client_secret credentials from current course:
- # course location example: u'i4x://blades/1/course/2013_Spring'
- course = self.descriptor.system.load_item(
- self.location.tag + '://' +
- self.location.org + '/' +
- self.location.course +
- '/course' +
- '/2013_Spring')
+ course_id = self.runtime.course_id
+ course_location = CourseDescriptor.id_to_location(course_id)
+ course = self.descriptor.runtime.modulestore.get_item(course_location)
client_key, client_secret = '', ''
for lti_passport in course.LTIs:
try:
@@ -147,15 +144,8 @@ class LTIModule(LTIFields, XModule):
client_key, client_secret = key, secret
break
- # these params do not participate in oauth signing
- params = {
- 'launch_url': self.launch_url,
- 'element_id': self.location.html_id(),
- 'element_class': self.location.category,
- }
-
# parsing custom parameters to dict
- parsed_custom_parameters = {}
+ custom_parameters = {}
for custom_parameter in self.custom_parameters:
try:
param_name, param_value = custom_parameter.split('=')
@@ -164,17 +154,26 @@ class LTIModule(LTIFields, XModule):
Should be "x=y" string.'.format(custom_parameter))
# LTI specs: 'custom_' should be prepended before each custom parameter
- parsed_custom_parameters.update(
+ custom_parameters.update(
{u'custom_' + unicode(param_name): unicode(param_value)}
)
- params.update({'custom_parameters': parsed_custom_parameters})
- params.update(self.oauth_params(
- parsed_custom_parameters,
+ input_fields = (self.oauth_params(
+ custom_parameters,
client_key,
client_secret
))
- return self.system.render_template('lti.html', params)
+
+ context = {
+ 'input_fields': input_fields,
+
+ # these params do not participate in oauth signing
+ 'launch_url': self.launch_url,
+ 'element_id': self.location.html_id(),
+ 'element_class': self.location.category,
+ }
+
+ return self.system.render_template('lti.html', context)
def oauth_params(self, custom_parameters, client_key, client_secret):
"""Signs request and returns signature and oauth parameters.
@@ -191,20 +190,19 @@ class LTIModule(LTIFields, XModule):
client_secret=unicode(client_secret)
)
- # @ned - why self.runtime.anonymous_student_id is None in dev env?
user_id = self.runtime.anonymous_student_id
- user_id = user_id if user_id else 'default_user_id'
+ assert user_id is not None
# must have parameters for correct signing from LTI:
body = {
- 'user_id': user_id,
- 'oauth_callback': 'about:blank',
- 'lis_outcome_service_url': '',
- 'lis_result_sourcedid': '',
- 'launch_presentation_return_url': '',
- 'lti_message_type': 'basic-lti-launch-request',
- 'lti_version': 'LTI-1p0',
- 'role': 'student'
+ u'user_id': user_id,
+ u'oauth_callback': u'about:blank',
+ u'lis_outcome_service_url': '',
+ u'lis_result_sourcedid': '',
+ u'launch_presentation_return_url': '',
+ u'lti_message_type': u'basic-lti-launch-request',
+ u'lti_version': 'LTI-1p0',
+ u'role': u'student'
}
# appending custom parameter for signing
@@ -220,13 +218,11 @@ class LTIModule(LTIFields, XModule):
headers=headers)
params = headers['Authorization']
# parse headers to pass to template as part of context:
- params = dict([param.strip().replace('"', '').split('=') for param in params.split('",')])
+ params = dict([param.strip().replace('"', '').split('=') for param in params.split(',')])
params[u'oauth_nonce'] = params[u'OAuth oauth_nonce']
del params[u'OAuth oauth_nonce']
- params['user_id'] = body['user_id']
-
# 0.14.2 (current) version of requests oauth library encodes signature,
# with 'Content-Type': 'application/x-www-form-urlencoded'
# so '='' becomes '%3D'.
@@ -234,6 +230,8 @@ class LTIModule(LTIFields, XModule):
# So we need to decode signature back:
params[u'oauth_signature'] = urllib.unquote(params[u'oauth_signature']).decode('utf8')
+ # add lti parameters to oauth parameters for sending in form
+ params.update(body)
return params
diff --git a/lms/djangoapps/courseware/tests/test_lti.py b/lms/djangoapps/courseware/tests/test_lti.py
index 86419300ad..cf74ea2660 100644
--- a/lms/djangoapps/courseware/tests/test_lti.py
+++ b/lms/djangoapps/courseware/tests/test_lti.py
@@ -1,53 +1,69 @@
-"""LTI test"""
+"""LTI integration tests"""
import requests
from . import BaseTestXmodule
+from collections import OrderedDict
class TestLTI(BaseTestXmodule):
- """Integration test for word cloud xmodule."""
+ """
+ Integration test for lti xmodule.
+ """
CATEGORY = "lti"
def setUp(self):
+ """
+ Mock oauth1 signing of requests library for testing.
+ """
super(TestLTI, self).setUp()
mocked_noonce = u'135685044251684026041377608307'
mocked_timestamp = u'1234567890'
- mocked_signed_signature = u'my_signature%3D'
+ mocked_signature_after_sign = u'my_signature%3D'
mocked_decoded_signature = u'my_signature='
self.correct_headers = {
+ u'oauth_callback': u'about:blank',
+ u'lis_outcome_service_url': '',
+ u'lis_result_sourcedid': '',
+ u'launch_presentation_return_url': '',
+ u'lti_message_type': u'basic-lti-launch-request',
+ u'lti_version': 'LTI-1p0',
+
u'oauth_nonce': mocked_noonce,
u'oauth_timestamp': mocked_timestamp,
u'oauth_consumer_key': u'',
u'oauth_signature_method': u'HMAC-SHA1',
u'oauth_version': u'1.0',
- u'oauth_signature': mocked_decoded_signature}
+ u'user_id': self.runtime.anonymous_student_id,
+ u'role': u'student',
+ u'oauth_signature': mocked_decoded_signature
+ }
saved_sign = requests.auth.Client.sign
def mocked_sign(self, *args, **kwargs):
"""Mocked oauth1 sign function"""
- # self is here:
+ # self is here:
_, headers, _ = saved_sign(self, *args, **kwargs)
# we should replace noonce, timestamp and signed_signature in headers:
old = headers[u'Authorization']
- new = old[:19] + mocked_noonce + old[49:69] + mocked_timestamp + \
- old[79:179] + mocked_signed_signature + old[-1]
- headers[u'Authorization'] = new
+ old_parsed = OrderedDict([param.strip().replace('"', '').split('=') for param in old.split(',')])
+ old_parsed[u'OAuth oauth_nonce'] = mocked_noonce
+ old_parsed[u'oauth_timestamp'] = mocked_timestamp
+ old_parsed[u'oauth_signature'] = mocked_signature_after_sign
+ headers[u'Authorization'] = ', '.join([k+'="'+v+'"' for k, v in old_parsed.items()])
return None, headers, None
requests.auth.Client.sign = mocked_sign
def test_lti_constructor(self):
"""Make sure that all parameters extracted """
- fragment = self.runtime.render(self.item_module, None, 'student_view')
+ self.runtime.render_template = lambda template, context: context
+ generated_context = self.item_module.get_html()
expected_context = {
+ 'input_fields': self.correct_headers,
'element_class': self.item_module.location.category,
'element_id': self.item_module.location.html_id(),
- 'lti_url': '', # default value
+ 'launch_url': '', # default value
}
- self.correct_headers.update(expected_context)
- self.assertEqual(
- fragment.content,
- self.runtime.render_template('lti.html', self.correct_headers)
- )
+ self.assertDictEqual(generated_context, expected_context)
diff --git a/lms/templates/lti.html b/lms/templates/lti.html
index 60bf00d176..3d97c8d808 100644
--- a/lms/templates/lti.html
+++ b/lms/templates/lti.html
@@ -11,22 +11,8 @@
target="ltiLaunchFrame"
encType="application/x-www-form-urlencoded"
>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- % for param_name, param_value in custom_parameters.items():
+ % for param_name, param_value in input_fields.items():
%endfor