Commit b797c6bd by Paul Medlock-Walton Committed by Oleg Marshev

username and email request for lti module

allow username and email can be passed to a lti third party app
ask user permission when lti button is clicked
allow course editor to customize text on lti launch button
parent d7c715eb
......@@ -173,3 +173,5 @@ Jason Zhu <fmyzjs@gmail.com>
Marceau Cnudde <marceau.cnudde@gmail.com>
Braden MacDonald <mail@bradenm.com>
Jonathan Piacenti <kelketek@gmail.com>
Paul Medlock-Walton <paulmw@mit.edu>
Henry Tareque <henry.tareque@gmail.com>
(function () {
'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');
// 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(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) {
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) {
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);
......@@ -192,6 +192,48 @@ class LTIFields(object):
scope=Scope.settings
)
# Users will be presented with a message indicating that their e-mail/username would be sent to a third
# party application. When "Open in New Page" is not selected, the tool automatically appears without any user action.
ask_to_send_username = Boolean(
display_name=_("Request user's username"),
# Translators: This is used to request the user's username for a third party service.
# Usernames can only be requested if "Open in New Page" is set to True.
help=_(
"Select True to request the user's username. You must also set Open in New Page to True to get the user's information."
),
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.
# Emails can only be requested if "Open in New Page" is set to True.
help=_(
"Select True to request the user's email address. You must also set Open in New Page to True to get the user's information."
),
default=False,
scope=Scope.settings
)
description = String(
display_name=_("LTI Application Information"),
help=_(
"Enter a description of the third party application. If requesting username and/or email, use this text box to inform users "
"why their username and/or email will be forwarded to a third party application."
),
default="",
scope=Scope.settings
)
button_text = String(
display_name=_("Button Text"),
help=_(
"Enter the text on the button used to launch the third party application."
),
default="",
scope=Scope.settings
)
class LTIModule(LTIFields, LTI20ModuleMixin, XModule):
"""
......@@ -274,7 +316,13 @@ class LTIModule(LTIFields, LTI20ModuleMixin, XModule):
Otherwise error message from LTI provider is generated.
"""
js = {
'js': [
resource_string(__name__, 'js/src/lti/lti.js')
]
}
css = {'scss': [resource_string(__name__, 'css/lti/lti.scss')]}
js_module_name = "LTI"
def get_input_fields(self):
# LTI provides a list of default parameters that might be passed as
......@@ -317,6 +365,7 @@ class LTIModule(LTIFields, LTI20ModuleMixin, XModule):
# parsing custom parameters to dict
custom_parameters = {}
for custom_parameter in self.custom_parameters:
try:
param_name, param_value = [p.strip() for p in custom_parameter.split('=', 1)]
......@@ -370,6 +419,11 @@ class LTIModule(LTIFields, LTI20ModuleMixin, XModule):
'weight': self.weight,
'module_score': self.module_score,
'comment': sanitized_comment,
'description': self.description,
'ask_to_send_username': self.ask_to_send_username,
'ask_to_send_email': self.ask_to_send_email,
'button_text': self.button_text,
}
def get_html(self):
......@@ -516,6 +570,29 @@ class LTIModule(LTIFields, LTI20ModuleMixin, XModule):
u'lis_outcome_service_url': self.get_outcome_service_url()
})
self.user_email = ""
self.user_username = ""
# Username and email can't be sent in studio mode, because the user object is not defined.
# To test functionality test in LMS
if callable(self.runtime.get_real_user):
real_user_object = self.runtime.get_real_user(self.runtime.anonymous_student_id)
try:
self.user_email = real_user_object.email
except AttributeError:
self.user_email = ""
try:
self.user_username = real_user_object.username
except AttributeError:
self.user_username = ""
if self.open_in_a_new_page:
if self.ask_to_send_username and self.user_username:
body["lis_person_sourcedid"] = self.user_username
if self.ask_to_send_email and self.user_email:
body["lis_person_contact_email_primary"] = self.user_email
# Appending custom parameter for signing.
body.update(custom_parameters)
......
......@@ -137,3 +137,73 @@ Feature: LMS.LTI component
| True | True |
Then in the LTI component I do not see an provider iframe
Then I see LTI component module title with text "LTI (EXTERNAL RESOURCE)"
#13
Scenario: LTI component button text is correctly displayed
Given the course has correct LTI credentials with registered Instructor
And the course has an LTI component with correct fields:
| button_text |
| Launch Application |
Then I see LTI component button with text "Launch Application"
#14
Scenario: LTI component description is correctly displayed
Given the course has correct LTI credentials with registered Instructor
And the course has an LTI component with correct fields:
| description |
| Application description |
Then I see LTI component description with text "Application description"
#15
Scenario: LTI component requests permission for username and is rejected
Given the course has correct LTI credentials with registered Instructor
And the course has an LTI component with correct fields:
| ask_to_send_username |
| True |
Then I view the permission alert
Then I reject the permission alert and do not view the LTI
#16
Scenario: LTI component requests permission for username and displays LTI when accepted
Given the course has correct LTI credentials with registered Instructor
And the course has an LTI component with correct fields:
| ask_to_send_username |
| True |
Then I view the permission alert
Then I accept the permission alert and view the LTI
#17
Scenario: LTI component requests permission for email and is rejected
Given the course has correct LTI credentials with registered Instructor
And the course has an LTI component with correct fields:
| ask_to_send_email |
| True |
Then I view the permission alert
Then I reject the permission alert and do not view the LTI
#18
Scenario: LTI component requests permission for email and displays LTI when accepted
Given the course has correct LTI credentials with registered Instructor
And the course has an LTI component with correct fields:
| ask_to_send_email |
| True |
Then I view the permission alert
Then I accept the permission alert and view the LTI
#19
Scenario: LTI component requests permission for email and username and is rejected
Given the course has correct LTI credentials with registered Instructor
And the course has an LTI component with correct fields:
| ask_to_send_email | ask_to_send_username |
| True | True |
Then I view the permission alert
Then I reject the permission alert and do not view the LTI
#20
Scenario: LTI component requests permission for email and username and displays LTI when accepted
Given the course has correct LTI credentials with registered Instructor
And the course has an LTI component with correct fields:
| ask_to_send_email | ask_to_send_username |
| True | True |
Then I view the permission alert
Then I accept the permission alert and view the LTI
......@@ -56,11 +56,39 @@ def lti_is_rendered(_step, rendered_in):
assert not world.is_css_present('iframe', wait_time=2)
assert world.is_css_present('.link_lti_new_window', wait_time=0)
assert not world.is_css_present('.error_message', wait_time=0)
check_lti_popup()
click_and_check_lti_popup()
else: # incorrent rendered_in parameter
assert False
@step('I view the permission alert$')
def view_lti_permission_alert(_step):
assert not world.is_css_present('iframe', wait_time=2)
assert world.is_css_present('.link_lti_new_window', wait_time=0)
assert not world.is_css_present('.error_message', wait_time=0)
world.css_find('.link_lti_new_window').first.click()
alert = world.browser.get_alert()
assert alert is not None
assert len(world.browser.windows) == 1
@step('I accept the permission alert and view the LTI$')
def accept_lti_permission_alert(_step):
parent_window = world.browser.current_window # Save the parent window
assert len(world.browser.windows) == 1
alert = world.browser.get_alert()
alert.accept()
assert len(world.browser.windows) != 1
check_lti_popup(parent_window)
@step('I reject the permission alert and do not view the LTI$')
def reject_lti_permission_alert(_step):
alert = world.browser.get_alert()
alert.dismiss()
assert len(world.browser.windows) == 1
@step('I view the LTI but incorrect_signature warning is rendered$')
def incorrect_lti_is_rendered(_step):
assert world.is_css_present('iframe', wait_time=2)
......@@ -216,10 +244,7 @@ def i_am_registered_for_the_course(coursenum, metadata, user='Instructor'):
world.log_in(username=user.username, password='test')
def check_lti_popup():
parent_window = world.browser.current_window # Save the parent window
world.css_find('.link_lti_new_window').first.click()
def check_lti_popup(parent_window):
assert len(world.browser.windows) != 1
for window in world.browser.windows:
......@@ -239,6 +264,12 @@ def check_lti_popup():
world.browser.switch_to_window(parent_window) # Switch to the main window again
def click_and_check_lti_popup():
parent_window = world.browser.current_window # Save the parent window
world.css_find('.link_lti_new_window').first.click()
check_lti_popup(parent_window)
@step('visit the LTI component')
def visit_lti_component(_step):
visit_scenario_item('LTI')
......@@ -249,7 +280,9 @@ def see_elem_text(_step, elem, text):
selector_map = {
'progress': '.problem-progress',
'feedback': '.problem-feedback',
'module title': '.problem-header'
'module title': '.problem-header',
'button': '.link_lti_new_window',
'description': '.lti-description'
}
assert_in(elem, selector_map)
assert_true(world.css_has_text(selector_map[elem], text))
......
......@@ -89,6 +89,10 @@ class TestLTI(BaseTestXmodule):
'module_score': None,
'comment': u'',
'weight': 1.0,
'ask_to_send_username': self.item_descriptor.ask_to_send_username,
'ask_to_send_email': self.item_descriptor.ask_to_send_email,
'description': self.item_descriptor.description,
'button_text': self.item_descriptor.button_text,
}
def mocked_sign(self, *args, **kwargs):
......
......@@ -21,13 +21,18 @@
<div
id="${element_id}"
class="${element_class}"
data-ask-to-send-username="${ask_to_send_username}"
data-ask-to-send-email="${ask_to_send_email}"
>
% if launch_url and launch_url != 'http://www.example.com' and not hide_launch:
% if open_in_a_new_page:
<div class="wrapper-lti-link">
% if description:
<div class="lti-description">${description}</div>
% endif
<p class="lti-link external"><a target="_blank" class="link_lti_new_window" href="${form_url}">
${_('View resource in a new window')}
${button_text or _('View resource in a new window')}
<i class="icon-external-link"></i>
</a></p>
</div>
......@@ -43,7 +48,7 @@
<h3 class="error_message">
${_('Please provide launch_url. Click "Edit", and fill in the required fields.')}
</h3>
%endif
% endif
% if has_score and comment:
<h4 class="problem-feedback-label">${_("Feedback on your work from the grader:")}</h4>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment