Skip to content

Commit

Permalink
fix: fix LTI block
Browse files Browse the repository at this point in the history
  • Loading branch information
ttqureshi committed Oct 31, 2024
1 parent cb9b866 commit e7ac22e
Show file tree
Hide file tree
Showing 4 changed files with 243 additions and 203 deletions.
235 changes: 126 additions & 109 deletions xblocks_contrib/lti/lti.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,12 @@
from web_fragments.fragment import Fragment
from xblock.core import List, Scope, String, XBlock
from xblock.fields import Boolean, Float
from xblock.utils.resources import ResourceLoader
try:
from xblock.utils.resources import ResourceLoader
from xblock.utils.studio_editable import StudioEditableXBlockMixin
except ModuleNotFoundError:
from xblockutils.resources import ResourceLoader
from xblockutils.studio_editable import StudioEditableXBlockMixin

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

_ = noop

class LTIFields:
@XBlock.needs("i18n")
@XBlock.needs("user")
@XBlock.needs("rebind_user")
class LTIBlock(
LTI20BlockMixin,
StudioEditableXBlockMixin,
XBlock,
): # pylint: disable=abstract-method
"""
Fields to define and obtain LTI tool from provider are set here,
except credentials, which should be set in course settings::
THIS MODULE IS DEPRECATED IN FAVOR OF https://github.com/openedx/xblock-lti-consumer
Module provides LTI integration to course.
Except usual Xmodule structure it proceeds with OAuth signing.
How it works::
1. Get credentials from course settings.
2. There is minimal set of parameters need to be signed (presented for Vitalsource)::
user_id
oauth_callback
lis_outcome_service_url
lis_result_sourcedid
launch_presentation_return_url
lti_message_type
lti_version
roles
*+ all custom parameters*
These parameters should be encoded and signed by *OAuth1* together with
`launch_url` and *POST* request type.
3. Signing proceeds with client key/secret pair obtained from course settings.
That pair should be obtained from LTI provider and set into course settings by course author.
After that signature and other OAuth data are generated.
OAuth data which is generated after signing is usual::
`lti_id` is id to connect tool with credentials in course settings. It should not contain :: (double semicolon)
`launch_url` is launch URL of tool.
`custom_parameters` are additional parameters to navigate to proper book and book page.
oauth_callback
oauth_nonce
oauth_consumer_key
oauth_signature_method
oauth_timestamp
oauth_version
For example, for Vitalsource provider, `launch_url` should be
*https://bc-staging.vitalsource.com/books/book*,
and to get to proper book and book page, you should set custom parameters as::
vbid=put_book_id_here
book_location=page/put_page_number_here
4. All that data is passed to form and sent to LTI provider server by browser via
autosubmit via JavaScript.
Default non-empty URL for `launch_url` is needed due to oauthlib demand (URL scheme should be presented)::
Form example::
https://github.com/idan/oauthlib/blob/master/oauthlib/oauth1/rfc5849/signature.py#L136
<form
action="${launch_url}"
name="ltiLaunchForm-${element_id}"
class="ltiLaunchForm"
method="post"
target="ltiLaunchFrame-${element_id}"
encType="application/x-www-form-urlencoded"
>
<input name="launch_presentation_return_url" value="" />
<input name="lis_outcome_service_url" value="" />
<input name="lis_result_sourcedid" value="" />
<input name="lti_message_type" value="basic-lti-launch-request" />
<input name="lti_version" value="LTI-1p0" />
<input name="oauth_callback" value="about:blank" />
<input name="oauth_consumer_key" value="${oauth_consumer_key}" />
<input name="oauth_nonce" value="${oauth_nonce}" />
<input name="oauth_signature_method" value="HMAC-SHA1" />
<input name="oauth_timestamp" value="${oauth_timestamp}" />
<input name="oauth_version" value="1.0" />
<input name="user_id" value="${user_id}" />
<input name="role" value="student" />
<input name="oauth_signature" value="${oauth_signature}" />
<input name="custom_1" value="${custom_param_1_value}" />
<input name="custom_2" value="${custom_param_2_value}" />
<input name="custom_..." value="${custom_param_..._value}" />
<input type="submit" value="Press to Launch" />
</form>
5. LTI provider has same secret key and it signs data string via *OAuth1* and compares signatures.
If signatures are correct, LTI provider redirects iframe source to LTI tool web page,
and LTI tool is rendered to iframe inside course.
Otherwise error message from LTI provider is generated.
"""

# Indicates that this XBlock has been extracted from edx-platform.
is_extracted = True

######################################
# LTI FIELDS #
######################################
# `lti_id` is id to connect tool with credentials in course settings. It should not contain :: (double semicolon)
# `launch_url` is launch URL of tool.
# `custom_parameters` are additional parameters to navigate to proper book and book page.

# For example, for Vitalsource provider, `launch_url` should be
# *https://bc-staging.vitalsource.com/books/book*,
# and to get to proper book and book page, you should set custom parameters as::

# vbid=put_book_id_here
# book_location=page/put_page_number_here

# Default non-empty URL for `launch_url` is needed due to oauthlib demand (URL scheme should be presented)::

# https://github.com/idan/oauthlib/blob/master/oauthlib/oauth1/rfc5849/signature.py#L136
display_name = String(
display_name=_("Display Name"),
help=_(
Expand All @@ -130,6 +225,7 @@ class LTIFields:
scope=Scope.settings,
default="LTI",
)

lti_id = String(
display_name=_("LTI ID"),
help=Text(_(
Expand All @@ -145,6 +241,7 @@ class LTIFields:
default='',
scope=Scope.settings
)

launch_url = String(
display_name=_("LTI URL"),
help=Text(_(
Expand All @@ -158,6 +255,7 @@ class LTIFields:
),
default='http://www.example.com',
scope=Scope.settings)

custom_parameters = List(
display_name=_("Custom Parameters"),
help=Text(_(
Expand All @@ -170,6 +268,7 @@ class LTIFields:
anchor_close=HTML("</a>")
),
scope=Scope.settings)

open_in_a_new_page = Boolean(
display_name=_("Open in New Page"),
help=_(
Expand All @@ -180,6 +279,7 @@ class LTIFields:
default=True,
scope=Scope.settings
)

has_score = Boolean(
display_name=_("Scored"),
help=_(
Expand All @@ -188,6 +288,7 @@ class LTIFields:
default=False,
scope=Scope.settings
)

weight = Float(
display_name=_("Weight"),
help=_(
Expand All @@ -199,16 +300,19 @@ class LTIFields:
scope=Scope.settings,
values={"min": 0},
)

module_score = Float(
help=_("The score kept in the xblock KVS -- duplicate of the published score in django DB"),
default=None,
scope=Scope.user_state
)

score_comment = String(
help=_("Comment as returned from grader, LTI2.0 spec"),
default="",
scope=Scope.user_state
)

hide_launch = Boolean(
display_name=_("Hide External Tool"),
help=_(
Expand All @@ -229,6 +333,7 @@ class LTIFields:
default=False,
scope=Scope.settings
)

ask_to_send_email = Boolean(
display_name=_("Request user's email"),
# Translators: This is used to request the user's email for a third party service.
Expand Down Expand Up @@ -262,100 +367,12 @@ class LTIFields:
default=True,
scope=Scope.settings
)


@XBlock.needs("i18n")
@XBlock.needs("user")
@XBlock.needs("rebind_user")
class LTIBlock(
XBlock,
LTIFields,
LTI20BlockMixin,
): # pylint: disable=abstract-method
"""
THIS MODULE IS DEPRECATED IN FAVOR OF https://github.com/openedx/xblock-lti-consumer
Module provides LTI integration to course.
Except usual Xmodule structure it proceeds with OAuth signing.
How it works::
1. Get credentials from course settings.
2. There is minimal set of parameters need to be signed (presented for Vitalsource)::
user_id
oauth_callback
lis_outcome_service_url
lis_result_sourcedid
launch_presentation_return_url
lti_message_type
lti_version
roles
*+ all custom parameters*
These parameters should be encoded and signed by *OAuth1* together with
`launch_url` and *POST* request type.
3. Signing proceeds with client key/secret pair obtained from course settings.
That pair should be obtained from LTI provider and set into course settings by course author.
After that signature and other OAuth data are generated.
OAuth data which is generated after signing is usual::
oauth_callback
oauth_nonce
oauth_consumer_key
oauth_signature_method
oauth_timestamp
oauth_version
4. All that data is passed to form and sent to LTI provider server by browser via
autosubmit via JavaScript.
Form example::
<form
action="${launch_url}"
name="ltiLaunchForm-${element_id}"
class="ltiLaunchForm"
method="post"
target="ltiLaunchFrame-${element_id}"
encType="application/x-www-form-urlencoded"
>
<input name="launch_presentation_return_url" value="" />
<input name="lis_outcome_service_url" value="" />
<input name="lis_result_sourcedid" value="" />
<input name="lti_message_type" value="basic-lti-launch-request" />
<input name="lti_version" value="LTI-1p0" />
<input name="oauth_callback" value="about:blank" />
<input name="oauth_consumer_key" value="${oauth_consumer_key}" />
<input name="oauth_nonce" value="${oauth_nonce}" />
<input name="oauth_signature_method" value="HMAC-SHA1" />
<input name="oauth_timestamp" value="${oauth_timestamp}" />
<input name="oauth_version" value="1.0" />
<input name="user_id" value="${user_id}" />
<input name="role" value="student" />
<input name="oauth_signature" value="${oauth_signature}" />
<input name="custom_1" value="${custom_param_1_value}" />
<input name="custom_2" value="${custom_param_2_value}" />
<input name="custom_..." value="${custom_param_..._value}" />
<input type="submit" value="Press to Launch" />
</form>
5. LTI provider has same secret key and it signs data string via *OAuth1* and compares signatures.
If signatures are correct, LTI provider redirects iframe source to LTI tool web page,
and LTI tool is rendered to iframe inside course.
Otherwise error message from LTI provider is generated.
"""

# Indicates that this XBlock has been extracted from edx-platform.
is_extracted = True

editable_fields = (
"accept_grades_past_due", "button_text", "custom_parameters", "display_name",
"hide_launch", "description", "lti_id", "launch_url", "open_in_a_new_page",
"ask_to_send_email", "ask_to_send_username", "has_score", "weight",
)

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

@XBlock.handler
Expand Down
51 changes: 19 additions & 32 deletions xblocks_contrib/lti/static/js/src/lti.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,23 @@
/* JavaScript for LTIBlock. */
// /* JavaScript for LTIBlock. */

(function() {
function LTIBlock(element) {
'use strict';

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

// When the lti button is clicked, provide users the option to
// accept or reject sending their information to a third party
$(element).on('click', '.link_lti_new_window', function() {
if (askToSendUsername && askToSendEmail) {
// eslint-disable-next-line no-alert
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.'));
} else if (askToSendUsername) {
// eslint-disable-next-line no-alert
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.'));
} else if (askToSendEmail) {
// eslint-disable-next-line no-alert
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.'));
} else {
return true;
}
});
};
}).call(this);
// When the lti button is clicked, provide users the option to
// accept or reject sending their information to a third party
$(element).on('click', '.link_lti_new_window', function() {
if (askToSendUsername && askToSendEmail) {
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.');
} else if (askToSendUsername) {
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.');
} else if (askToSendEmail) {
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.');
} else {
return true;
}
});
}
Loading

0 comments on commit e7ac22e

Please sign in to comment.