Skip to content

Commit fe9f7ae

Browse files
committed
fix: fix LTI block
1 parent cb9b866 commit fe9f7ae

File tree

4 files changed

+242
-202
lines changed

4 files changed

+242
-202
lines changed

xblocks_contrib/lti/lti.py

+126-109
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,12 @@
7474
from web_fragments.fragment import Fragment
7575
from xblock.core import List, Scope, String, XBlock
7676
from xblock.fields import Boolean, Float
77-
from xblock.utils.resources import ResourceLoader
77+
try:
78+
from xblock.utils.resources import ResourceLoader
79+
from xblock.utils.studio_editable import StudioEditableXBlockMixin
80+
except ModuleNotFoundError:
81+
from xblockutils.resources import ResourceLoader
82+
from xblockutils.studio_editable import StudioEditableXBlockMixin
7883

7984
from openedx.core.djangolib.markup import HTML, Text
8085
from .lti_2_util import LTI20BlockMixin, LTIError
@@ -101,26 +106,116 @@ def noop(text):
101106

102107
_ = noop
103108

104-
class LTIFields:
109+
@XBlock.needs("i18n")
110+
@XBlock.needs("user")
111+
@XBlock.needs("rebind_user")
112+
class LTIBlock(
113+
LTI20BlockMixin,
114+
StudioEditableXBlockMixin,
115+
XBlock,
116+
): # pylint: disable=abstract-method
105117
"""
106-
Fields to define and obtain LTI tool from provider are set here,
107-
except credentials, which should be set in course settings::
118+
THIS MODULE IS DEPRECATED IN FAVOR OF https://github.com/openedx/xblock-lti-consumer
119+
120+
Module provides LTI integration to course.
121+
122+
Except usual Xmodule structure it proceeds with OAuth signing.
123+
How it works::
124+
125+
1. Get credentials from course settings.
126+
127+
2. There is minimal set of parameters need to be signed (presented for Vitalsource)::
128+
129+
user_id
130+
oauth_callback
131+
lis_outcome_service_url
132+
lis_result_sourcedid
133+
launch_presentation_return_url
134+
lti_message_type
135+
lti_version
136+
roles
137+
*+ all custom parameters*
138+
139+
These parameters should be encoded and signed by *OAuth1* together with
140+
`launch_url` and *POST* request type.
141+
142+
3. Signing proceeds with client key/secret pair obtained from course settings.
143+
That pair should be obtained from LTI provider and set into course settings by course author.
144+
After that signature and other OAuth data are generated.
145+
146+
OAuth data which is generated after signing is usual::
108147
109-
`lti_id` is id to connect tool with credentials in course settings. It should not contain :: (double semicolon)
110-
`launch_url` is launch URL of tool.
111-
`custom_parameters` are additional parameters to navigate to proper book and book page.
148+
oauth_callback
149+
oauth_nonce
150+
oauth_consumer_key
151+
oauth_signature_method
152+
oauth_timestamp
153+
oauth_version
112154
113-
For example, for Vitalsource provider, `launch_url` should be
114-
*https://bc-staging.vitalsource.com/books/book*,
115-
and to get to proper book and book page, you should set custom parameters as::
116155
117-
vbid=put_book_id_here
118-
book_location=page/put_page_number_here
156+
4. All that data is passed to form and sent to LTI provider server by browser via
157+
autosubmit via JavaScript.
119158
120-
Default non-empty URL for `launch_url` is needed due to oauthlib demand (URL scheme should be presented)::
159+
Form example::
121160
122-
https://github.com/idan/oauthlib/blob/master/oauthlib/oauth1/rfc5849/signature.py#L136
161+
<form
162+
action="${launch_url}"
163+
name="ltiLaunchForm-${element_id}"
164+
class="ltiLaunchForm"
165+
method="post"
166+
target="ltiLaunchFrame-${element_id}"
167+
encType="application/x-www-form-urlencoded"
168+
>
169+
<input name="launch_presentation_return_url" value="" />
170+
<input name="lis_outcome_service_url" value="" />
171+
<input name="lis_result_sourcedid" value="" />
172+
<input name="lti_message_type" value="basic-lti-launch-request" />
173+
<input name="lti_version" value="LTI-1p0" />
174+
<input name="oauth_callback" value="about:blank" />
175+
<input name="oauth_consumer_key" value="${oauth_consumer_key}" />
176+
<input name="oauth_nonce" value="${oauth_nonce}" />
177+
<input name="oauth_signature_method" value="HMAC-SHA1" />
178+
<input name="oauth_timestamp" value="${oauth_timestamp}" />
179+
<input name="oauth_version" value="1.0" />
180+
<input name="user_id" value="${user_id}" />
181+
<input name="role" value="student" />
182+
<input name="oauth_signature" value="${oauth_signature}" />
183+
184+
<input name="custom_1" value="${custom_param_1_value}" />
185+
<input name="custom_2" value="${custom_param_2_value}" />
186+
<input name="custom_..." value="${custom_param_..._value}" />
187+
188+
<input type="submit" value="Press to Launch" />
189+
</form>
190+
191+
5. LTI provider has same secret key and it signs data string via *OAuth1* and compares signatures.
192+
193+
If signatures are correct, LTI provider redirects iframe source to LTI tool web page,
194+
and LTI tool is rendered to iframe inside course.
195+
196+
Otherwise error message from LTI provider is generated.
123197
"""
198+
199+
# Indicates that this XBlock has been extracted from edx-platform.
200+
is_extracted = True
201+
202+
######################################
203+
# LTI FIELDS #
204+
######################################
205+
# `lti_id` is id to connect tool with credentials in course settings. It should not contain :: (double semicolon)
206+
# `launch_url` is launch URL of tool.
207+
# `custom_parameters` are additional parameters to navigate to proper book and book page.
208+
209+
# For example, for Vitalsource provider, `launch_url` should be
210+
# *https://bc-staging.vitalsource.com/books/book*,
211+
# and to get to proper book and book page, you should set custom parameters as::
212+
213+
# vbid=put_book_id_here
214+
# book_location=page/put_page_number_here
215+
216+
# Default non-empty URL for `launch_url` is needed due to oauthlib demand (URL scheme should be presented)::
217+
218+
# https://github.com/idan/oauthlib/blob/master/oauthlib/oauth1/rfc5849/signature.py#L136
124219
display_name = String(
125220
display_name=_("Display Name"),
126221
help=_(
@@ -130,6 +225,7 @@ class LTIFields:
130225
scope=Scope.settings,
131226
default="LTI",
132227
)
228+
133229
lti_id = String(
134230
display_name=_("LTI ID"),
135231
help=Text(_(
@@ -145,6 +241,7 @@ class LTIFields:
145241
default='',
146242
scope=Scope.settings
147243
)
244+
148245
launch_url = String(
149246
display_name=_("LTI URL"),
150247
help=Text(_(
@@ -158,6 +255,7 @@ class LTIFields:
158255
),
159256
default='http://www.example.com',
160257
scope=Scope.settings)
258+
161259
custom_parameters = List(
162260
display_name=_("Custom Parameters"),
163261
help=Text(_(
@@ -170,6 +268,7 @@ class LTIFields:
170268
anchor_close=HTML("</a>")
171269
),
172270
scope=Scope.settings)
271+
173272
open_in_a_new_page = Boolean(
174273
display_name=_("Open in New Page"),
175274
help=_(
@@ -180,6 +279,7 @@ class LTIFields:
180279
default=True,
181280
scope=Scope.settings
182281
)
282+
183283
has_score = Boolean(
184284
display_name=_("Scored"),
185285
help=_(
@@ -188,6 +288,7 @@ class LTIFields:
188288
default=False,
189289
scope=Scope.settings
190290
)
291+
191292
weight = Float(
192293
display_name=_("Weight"),
193294
help=_(
@@ -199,16 +300,19 @@ class LTIFields:
199300
scope=Scope.settings,
200301
values={"min": 0},
201302
)
303+
202304
module_score = Float(
203305
help=_("The score kept in the xblock KVS -- duplicate of the published score in django DB"),
204306
default=None,
205307
scope=Scope.user_state
206308
)
309+
207310
score_comment = String(
208311
help=_("Comment as returned from grader, LTI2.0 spec"),
209312
default="",
210313
scope=Scope.user_state
211314
)
315+
212316
hide_launch = Boolean(
213317
display_name=_("Hide External Tool"),
214318
help=_(
@@ -229,6 +333,7 @@ class LTIFields:
229333
default=False,
230334
scope=Scope.settings
231335
)
336+
232337
ask_to_send_email = Boolean(
233338
display_name=_("Request user's email"),
234339
# Translators: This is used to request the user's email for a third party service.
@@ -262,100 +367,12 @@ class LTIFields:
262367
default=True,
263368
scope=Scope.settings
264369
)
265-
266-
267-
@XBlock.needs("i18n")
268-
@XBlock.needs("user")
269-
@XBlock.needs("rebind_user")
270-
class LTIBlock(
271-
XBlock,
272-
LTIFields,
273-
LTI20BlockMixin,
274-
): # pylint: disable=abstract-method
275-
"""
276-
THIS MODULE IS DEPRECATED IN FAVOR OF https://github.com/openedx/xblock-lti-consumer
277-
278-
Module provides LTI integration to course.
279-
280-
Except usual Xmodule structure it proceeds with OAuth signing.
281-
How it works::
282-
283-
1. Get credentials from course settings.
284-
285-
2. There is minimal set of parameters need to be signed (presented for Vitalsource)::
286-
287-
user_id
288-
oauth_callback
289-
lis_outcome_service_url
290-
lis_result_sourcedid
291-
launch_presentation_return_url
292-
lti_message_type
293-
lti_version
294-
roles
295-
*+ all custom parameters*
296-
297-
These parameters should be encoded and signed by *OAuth1* together with
298-
`launch_url` and *POST* request type.
299-
300-
3. Signing proceeds with client key/secret pair obtained from course settings.
301-
That pair should be obtained from LTI provider and set into course settings by course author.
302-
After that signature and other OAuth data are generated.
303-
304-
OAuth data which is generated after signing is usual::
305-
306-
oauth_callback
307-
oauth_nonce
308-
oauth_consumer_key
309-
oauth_signature_method
310-
oauth_timestamp
311-
oauth_version
312-
313-
314-
4. All that data is passed to form and sent to LTI provider server by browser via
315-
autosubmit via JavaScript.
316-
317-
Form example::
318-
319-
<form
320-
action="${launch_url}"
321-
name="ltiLaunchForm-${element_id}"
322-
class="ltiLaunchForm"
323-
method="post"
324-
target="ltiLaunchFrame-${element_id}"
325-
encType="application/x-www-form-urlencoded"
326-
>
327-
<input name="launch_presentation_return_url" value="" />
328-
<input name="lis_outcome_service_url" value="" />
329-
<input name="lis_result_sourcedid" value="" />
330-
<input name="lti_message_type" value="basic-lti-launch-request" />
331-
<input name="lti_version" value="LTI-1p0" />
332-
<input name="oauth_callback" value="about:blank" />
333-
<input name="oauth_consumer_key" value="${oauth_consumer_key}" />
334-
<input name="oauth_nonce" value="${oauth_nonce}" />
335-
<input name="oauth_signature_method" value="HMAC-SHA1" />
336-
<input name="oauth_timestamp" value="${oauth_timestamp}" />
337-
<input name="oauth_version" value="1.0" />
338-
<input name="user_id" value="${user_id}" />
339-
<input name="role" value="student" />
340-
<input name="oauth_signature" value="${oauth_signature}" />
341-
342-
<input name="custom_1" value="${custom_param_1_value}" />
343-
<input name="custom_2" value="${custom_param_2_value}" />
344-
<input name="custom_..." value="${custom_param_..._value}" />
345-
346-
<input type="submit" value="Press to Launch" />
347-
</form>
348-
349-
5. LTI provider has same secret key and it signs data string via *OAuth1* and compares signatures.
350-
351-
If signatures are correct, LTI provider redirects iframe source to LTI tool web page,
352-
and LTI tool is rendered to iframe inside course.
353-
354-
Otherwise error message from LTI provider is generated.
355-
"""
356-
357-
# Indicates that this XBlock has been extracted from edx-platform.
358-
is_extracted = True
370+
371+
editable_fields = (
372+
"accept_grades_past_due", "button_text", "custom_parameters", "display_name",
373+
"hide_launch", "description", "lti_id", "launch_url", "open_in_a_new_page",
374+
"ask_to_send_email", "ask_to_send_username", "has_score", "weight",
375+
)
359376

360377
def max_score(self):
361378
return self.weight if self.has_score else None
@@ -509,7 +526,7 @@ def preview_handler(self, _, __):
509526
"""
510527
This is called to get context with new oauth params to iframe.
511528
"""
512-
template = resource_loader.load_unicode("templates/lti_form.html").format(**self.get_context())
529+
template = resource_loader.render_django_template("templates/lti_form.html", self.get_context())
513530
return Response(template, content_type='text/html')
514531

515532
@XBlock.handler
+18-31
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,23 @@
11
/* JavaScript for LTIBlock. */
22

3-
(function() {
3+
function LTIBlock(element) {
44
'use strict';
55

6-
/**
7-
* This function will process all the attributes from the DOM element passed, taking all of
8-
* the configuration attributes. It uses the request-username and request-email
9-
* to prompt the user to decide if they want to share their personal information
10-
* with the third party application connecting through LTI.
11-
* @constructor
12-
* @param {jQuery} element DOM element with the lti container.
13-
*/
14-
this.LTI = function(element) {
15-
var dataAttrs = $(element).find('.lti').data(),
16-
askToSendUsername = (dataAttrs.askToSendUsername === 'True'),
17-
askToSendEmail = (dataAttrs.askToSendEmail === 'True');
6+
const $lti = $(element).find('.lti');
7+
const askToSendUsername = $lti.data('ask-to-send-username') === 'True';
8+
const askToSendEmail = $lti.data('ask-to-send-email') === 'True';
189

19-
// When the lti button is clicked, provide users the option to
20-
// accept or reject sending their information to a third party
21-
$(element).on('click', '.link_lti_new_window', function() {
22-
if (askToSendUsername && askToSendEmail) {
23-
// eslint-disable-next-line no-alert
24-
return confirm(gettext('Click OK to have your username and e-mail address sent to a 3rd party application.\n\nClick Cancel to return to this page without sending your information.'));
25-
} else if (askToSendUsername) {
26-
// eslint-disable-next-line no-alert
27-
return confirm(gettext('Click OK to have your username sent to a 3rd party application.\n\nClick Cancel to return to this page without sending your information.'));
28-
} else if (askToSendEmail) {
29-
// eslint-disable-next-line no-alert
30-
return confirm(gettext('Click OK to have your e-mail address sent to a 3rd party application.\n\nClick Cancel to return to this page without sending your information.'));
31-
} else {
32-
return true;
33-
}
34-
});
35-
};
36-
}).call(this);
10+
// When the lti button is clicked, provide users the option to
11+
// accept or reject sending their information to a third party
12+
$(element).on('click', '.link_lti_new_window', function() {
13+
if (askToSendUsername && askToSendEmail) {
14+
return confirm('Click OK to have your username and e-mail address sent to a 3rd party application.\n\nClick Cancel to return to this page without sending your information.');
15+
} else if (askToSendUsername) {
16+
return confirm('Click OK to have your username sent to a 3rd party application.\n\nClick Cancel to return to this page without sending your information.');
17+
} else if (askToSendEmail) {
18+
return confirm('Click OK to have your e-mail address sent to a 3rd party application.\n\nClick Cancel to return to this page without sending your information.');
19+
} else {
20+
return true;
21+
}
22+
});
23+
}

0 commit comments

Comments
 (0)