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
This commit is contained in:
committed by
Oleg Marshev
parent
d7c715ebee
commit
b797c6bd09
2
AUTHORS
2
AUTHORS
@@ -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>
|
||||
|
||||
32
common/lib/xmodule/xmodule/js/src/lti/lti.js
Normal file
32
common/lib/xmodule/xmodule/js/src/lti/lti.js
Normal file
@@ -0,0 +1,32 @@
|
||||
(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>
|
||||
|
||||
Reference in New Issue
Block a user