add lti variable substitution support

Added a basic variable substitution framework and implemented
a few variables as examples: $Person.name.full,
$Person.name.given, $Person.name.family

Added a concluded enrollment roles variable so that a tool
can know what a users concluded roles were. The variable is
$Canvas.membership.concludedRoles

Variable names are case sensitive.

Test Plan:
 * Create an lti tool launch with a custom key like this:
   * custom_my_var=$Person.name.full
 * With a concluded user do a tool launch that has the
   $Canvas.membership.concludedRoles variable
	 * it should list the concluded roles
 * Configure tools through an XML file with a custom param
   and make sure it works

Change-Id: Iefb85c441680c7ab3623ce85e405e1c48cda837c
Reviewed-on: https://gerrit.instructure.com/21193
QA-Review: Clare Strong <clare@instructure.com>
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Brad Humphrey <brad@instructure.com>
Product-Review: Bracken Mosbacker <bracken@instructure.com>
This commit is contained in:
Bracken Mosbacker 2013-06-04 12:49:02 -06:00
parent d01c5fddf6
commit 5a689faa6d
7 changed files with 508 additions and 257 deletions

View File

@ -1,209 +1,24 @@
#
# Copyright (C) 2013 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
module BasicLTI
def self.explicit_signature_settings(timestamp, nonce)
@timestamp = timestamp
@nonce = nonce
end
def self.generate_params(params, url, key, secret)
require 'uri'
require 'oauth'
require 'oauth/consumer'
uri = URI.parse(url)
if uri.port == uri.default_port
host = uri.host
else
host = "#{uri.host}:#{uri.port}"
end
consumer = OAuth::Consumer.new(key, secret, {
:site => "#{uri.scheme}://#{host}",
:signature_method => "HMAC-SHA1"
})
path = uri.path
path = '/' if path.empty?
if !uri.query.blank?
CGI.parse(uri.query).each do |query_key, query_values|
unless params[query_key]
params[query_key] = query_values.first
end
end
end
options = {
:scheme => 'body',
:timestamp => @timestamp,
:nonce => @nonce
}
request = consumer.create_signed_request(:post, path, nil, options, params.stringify_keys)
# the request is made by a html form in the user's browser, so we
# want to revert the escapage and return the hash of post parameters ready
# for embedding in a html view
hash = {}
request.body.split(/&/).each do |param|
key, val = param.split(/=/).map{|v| CGI.unescape(v) }
hash[key] = val
end
hash
end
def self.generate(*args)
BasicLTI::ToolLaunch.new(*args).generate
end
# Returns the LTI membership based on the LTI specs here: http://www.imsglobal.org/LTI/v1p1pd/ltiIMGv1p1pd.html#_Toc309649701
def self.user_lti_data(user, context=nil)
data = {}
memberships = []
if context.is_a?(Course)
memberships += user.current_enrollments.find_all_by_course_id(context.id).uniq
data['enrollment_state'] = memberships.any?{|membership| membership.state_based_on_date == :active} ? 'active' : 'inactive'
end
if context.respond_to?(:account_chain) && !context.account_chain_ids.empty?
memberships += user.account_users.find_all_by_account_id(context.account_chain_ids).uniq
end
data['role_types'] = memberships.map{|membership|
case membership
when StudentEnrollment, StudentViewEnrollment
'Learner'
when TeacherEnrollment
'Instructor'
when TaEnrollment
'urn:lti:role:ims/lis/TeachingAssistant'
when DesignerEnrollment
'ContentDeveloper'
when ObserverEnrollment
'urn:lti:instrole:ims/lis/Observer'
when AccountUser
'urn:lti:instrole:ims/lis/Administrator'
else
'urn:lti:instrole:ims/lis/Observer'
end
}.uniq
data['role_types'] = ["urn:lti:sysrole:ims/lis/None"] if memberships.empty?
data
end
class ToolLaunch < Struct.new(:url, :tool, :user, :context, :link_code, :return_url, :resource_type, :root_account, :hash)
def initialize(options)
self.url = options[:url] || raise("URL required for generating Basic LTI content")
self.tool = options[:tool] || raise("Tool required for generating Basic LTI content")
self.user = options[:user] || raise("User required for generating Basic LTI content")
self.context = options[:context] || raise("Context required for generating Basic LTI content")
self.link_code = options[:link_code] || raise("Link Code required for generating Basic LTI content")
self.return_url = options[:return_url] || raise("Return URL required for generating Basic LTI content")
self.resource_type = options[:resource_type]
if self.context.respond_to? :root_account
self.root_account = context.root_account
elsif self.tool.context.respond_to? :root_account
self.root_account = tool.context.root_account
end
root_account || raise("Root account required for generating Basic LTI content")
self.hash = {}
end
def for_assignment!(assignment, outcome_service_url, legacy_outcome_service_url)
hash['lis_result_sourcedid'] = BasicLTI::BasicOutcomes.encode_source_id(tool, context, assignment, user)
hash['lis_outcome_service_url'] = outcome_service_url
hash['ext_ims_lis_basic_outcome_url'] = legacy_outcome_service_url
hash['ext_outcome_data_values_accepted'] = ['url', 'text'].join(',')
hash['custom_canvas_assignment_title'] = assignment.title
hash['custom_canvas_assignment_points_possible'] = assignment.points_possible
if tool.public?
hash['custom_canvas_assignment_id'] = assignment.id
end
end
def for_homework_submission!(assignment)
self.resource_type = 'homework_submission'
return_types_map = {'online_upload' => 'file', 'online_url' => 'url'}
return_types = []
assignment.submission_types.split(',').each do |submission_type|
submission_type.strip!
return_types << return_types_map[submission_type.strip] if return_types_map.has_key? submission_type
end
hash['ext_content_return_types'] = return_types.join(',') unless return_types.blank?
hash['ext_content_file_extensions'] = assignment.allowed_extensions.join(',') unless assignment.allowed_extensions.blank?
hash['custom_canvas_assignment_id'] = assignment.id if tool.public?
end
def generate
hash['lti_message_type'] = 'basic-lti-launch-request'
hash['lti_version'] = 'LTI-1p0'
hash['resource_link_id'] = link_code
hash['resource_link_title'] = tool.name
hash['user_id'] = user.opaque_identifier(:asset_string)
hash['user_image'] = user.avatar_url
user_data = BasicLTI.user_lti_data(user, context)
hash['roles'] = user_data['role_types'].join(',') # AccountAdmin, Student, Faculty or Observer
hash['custom_canvas_enrollment_state'] = user_data['enrollment_state'] if user_data['enrollment_state']
if tool.include_name?
hash['lis_person_name_given'] = user.first_name
hash['lis_person_name_family'] = user.last_name
hash['lis_person_name_full'] = user.name
end
if tool.include_email?
hash['lis_person_contact_email_primary'] = user.email
end
if tool.public?
hash['custom_canvas_user_id'] = user.id
pseudo = user.find_pseudonym_for_account(self.root_account)
if pseudo
hash['lis_person_sourcedid'] = pseudo.sis_user_id if pseudo.sis_user_id
hash['custom_canvas_user_login_id'] = pseudo.unique_id
end
if context.is_a?(Course)
hash['custom_canvas_course_id'] = context.id
hash['lis_course_offering_sourcedid'] = context.sis_source_id if context.sis_source_id
elsif context.is_a?(Account)
hash['custom_canvas_account_id'] = context.id
hash['custom_canvas_account_sis_id'] = context.sis_source_id if context.sis_source_id
end
hash['custom_canvas_api_domain'] = root_account.domain
end
# need to set the locale here (instead of waiting for the first call to
# I18n.t like we usually do), because otherwise we'll have the wrong code
# for the launch_presentation_locale.
I18n.set_locale_with_localizer
hash['context_id'] = context.opaque_identifier(:asset_string)
hash['context_title'] = context.name
hash['context_label'] = context.course_code if context.respond_to?(:course_code)
hash['launch_presentation_locale'] = I18n.locale || I18n.default_locale.to_s
hash['launch_presentation_document_target'] = 'iframe'
hash['launch_presentation_width'] = tool.extension_setting(resource_type, :selection_width) if resource_type
hash['launch_presentation_height'] = tool.extension_setting(resource_type, :selection_height) if resource_type
hash['launch_presentation_return_url'] = return_url
hash['tool_consumer_instance_guid'] = root_account.lti_guid
hash['tool_consumer_instance_name'] = root_account.name
hash['tool_consumer_instance_contact_email'] = HostUrl.outgoing_email_address # TODO: find a better email address to use here
hash['tool_consumer_info_product_family_code'] = 'canvas'
hash['tool_consumer_info_version'] = 'cloud'
tool.set_custom_fields(hash, resource_type)
if resource_type == 'editor_button'
hash['selection_directive'] = 'embed_content' #backwards compatibility
hash['ext_content_intended_use'] = 'embed'
hash['ext_content_return_types'] = 'oembed,lti_launch_url,url,image_url,iframe'
hash['ext_content_return_url'] = return_url
elsif resource_type == 'resource_selection'
hash['selection_directive'] = 'select_link' #backwards compatibility
hash['ext_content_intended_use'] = 'navigation'
hash['ext_content_return_types'] = 'lti_launch_url'
hash['ext_content_return_url'] = return_url
elsif resource_type == 'homework_submission'
hash['ext_content_intended_use'] = 'homework'
hash['ext_content_return_url'] = return_url
end
hash['oauth_callback'] = 'about:blank'
BasicLTI.generate_params(hash, url, tool.consumer_key, tool.shared_secret)
end
end
end
require_dependency 'basic_lti/basic_lti'
require_dependency 'basic_lti/tool_launch'
require_dependency 'basic_lti/basic_outcomes'
require_dependency 'basic_lti/variable_substitutor'

120
lib/basic_lti/basic_lti.rb Normal file
View File

@ -0,0 +1,120 @@
#
# Copyright (C) 2013 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
module BasicLTI
def self.explicit_signature_settings(timestamp, nonce)
@timestamp = timestamp
@nonce = nonce
end
def self.generate_params(params, url, key, secret)
require 'uri'
require 'oauth'
require 'oauth/consumer'
uri = URI.parse(url)
if uri.port == uri.default_port
host = uri.host
else
host = "#{uri.host}:#{uri.port}"
end
consumer = OAuth::Consumer.new(key, secret, {
:site => "#{uri.scheme}://#{host}",
:signature_method => "HMAC-SHA1"
})
path = uri.path
path = '/' if path.empty?
if !uri.query.blank?
CGI.parse(uri.query).each do |query_key, query_values|
unless params[query_key]
params[query_key] = query_values.first
end
end
end
options = {
:scheme => 'body',
:timestamp => @timestamp,
:nonce => @nonce
}
request = consumer.create_signed_request(:post, path, nil, options, params.stringify_keys)
# the request is made by a html form in the user's browser, so we
# want to revert the escapage and return the hash of post parameters ready
# for embedding in a html view
hash = {}
request.body.split(/&/).each do |param|
key, val = param.split(/=/).map{|v| CGI.unescape(v) }
hash[key] = val
end
hash
end
def self.generate(*args)
BasicLTI::ToolLaunch.new(*args).generate
end
# Returns the LTI membership based on the LTI specs here: http://www.imsglobal.org/LTI/v1p1pd/ltiIMGv1p1pd.html#_Toc309649701
def self.user_lti_data(user, context=nil)
data = {}
memberships = []
concluded_memberships = []
# collect canvas course/account enrollments
if context.is_a?(Course)
memberships += user.current_enrollments.find_all_by_course_id(context.id).uniq
data['enrollment_state'] = memberships.any?{|membership| membership.state_based_on_date == :active} ? 'active' : 'inactive'
concluded_memberships = user.concluded_enrollments.find_all_by_course_id(context.id).uniq
end
if context.respond_to?(:account_chain) && !context.account_chain_ids.empty?
memberships += user.account_users.find_all_by_account_id(context.account_chain_ids).uniq
end
# convert canvas enrollments to LIS roles
data['role_types'] = memberships.map{|membership|
enrollment_to_membership(membership)
}.uniq
data['role_types'] = ["urn:lti:sysrole:ims/lis/None"] if memberships.empty?
data['concluded_role_types'] = concluded_memberships.map{|membership|
enrollment_to_membership(membership)
}.uniq
data['concluded_role_types'] = ["urn:lti:sysrole:ims/lis/None"] if concluded_memberships.empty?
data
end
def self.enrollment_to_membership(membership)
case membership
when StudentEnrollment, StudentViewEnrollment
'Learner'
when TeacherEnrollment
'Instructor'
when TaEnrollment
'urn:lti:role:ims/lis/TeachingAssistant'
when DesignerEnrollment
'ContentDeveloper'
when ObserverEnrollment
'urn:lti:instrole:ims/lis/Observer'
when AccountUser
'urn:lti:instrole:ims/lis/Administrator'
else
'urn:lti:instrole:ims/lis/Observer'
end
end
end

View File

@ -1,3 +1,20 @@
#
# Copyright (C) 2013 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
module BasicLTI::BasicOutcomes
class Unauthorized < Exception; end

View File

@ -0,0 +1,141 @@
#
# Copyright (C) 2013 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
module BasicLTI
class ToolLaunch < Struct.new(:url, :tool, :user, :context, :link_code, :return_url, :resource_type, :root_account, :hash, :user_data)
def initialize(options)
self.url = options[:url] || raise("URL required for generating Basic LTI content")
self.tool = options[:tool] || raise("Tool required for generating Basic LTI content")
self.user = options[:user] || raise("User required for generating Basic LTI content")
self.context = options[:context] || raise("Context required for generating Basic LTI content")
self.link_code = options[:link_code] || raise("Link Code required for generating Basic LTI content")
self.return_url = options[:return_url] || raise("Return URL required for generating Basic LTI content")
self.resource_type = options[:resource_type]
if self.context.respond_to? :root_account
self.root_account = context.root_account
elsif self.tool.context.respond_to? :root_account
self.root_account = tool.context.root_account
end
root_account || raise("Root account required for generating Basic LTI content")
self.hash = {}
end
def for_assignment!(assignment, outcome_service_url, legacy_outcome_service_url)
hash['lis_result_sourcedid'] = BasicLTI::BasicOutcomes.encode_source_id(tool, context, assignment, user)
hash['lis_outcome_service_url'] = outcome_service_url
hash['ext_ims_lis_basic_outcome_url'] = legacy_outcome_service_url
hash['ext_outcome_data_values_accepted'] = ['url', 'text'].join(',')
hash['custom_canvas_assignment_title'] = assignment.title
hash['custom_canvas_assignment_points_possible'] = assignment.points_possible
if tool.public?
hash['custom_canvas_assignment_id'] = assignment.id
end
end
def for_homework_submission!(assignment)
self.resource_type = 'homework_submission'
return_types_map = {'online_upload' => 'file', 'online_url' => 'url'}
return_types = []
assignment.submission_types.split(',').each do |submission_type|
submission_type.strip!
return_types << return_types_map[submission_type.strip] if return_types_map.has_key? submission_type
end
hash['ext_content_return_types'] = return_types.join(',') unless return_types.blank?
hash['ext_content_file_extensions'] = assignment.allowed_extensions.join(',') unless assignment.allowed_extensions.blank?
hash['custom_canvas_assignment_id'] = assignment.id if tool.public?
end
def generate
hash['lti_message_type'] = 'basic-lti-launch-request'
hash['lti_version'] = 'LTI-1p0'
hash['resource_link_id'] = link_code
hash['resource_link_title'] = tool.name
hash['user_id'] = user.opaque_identifier(:asset_string)
hash['user_image'] = user.avatar_url
self.user_data = BasicLTI.user_lti_data(user, context)
hash['roles'] = self.user_data['role_types'].join(',') # AccountAdmin, Student, Faculty or Observer
hash['custom_canvas_enrollment_state'] = self.user_data['enrollment_state'] if self.user_data['enrollment_state']
if tool.include_name?
hash['lis_person_name_given'] = user.first_name
hash['lis_person_name_family'] = user.last_name
hash['lis_person_name_full'] = user.name
end
if tool.include_email?
hash['lis_person_contact_email_primary'] = user.email
end
if tool.public?
hash['custom_canvas_user_id'] = user.id
pseudo = user.find_pseudonym_for_account(self.root_account)
if pseudo
hash['lis_person_sourcedid'] = pseudo.sis_user_id if pseudo.sis_user_id
hash['custom_canvas_user_login_id'] = pseudo.unique_id
end
if context.is_a?(Course)
hash['custom_canvas_course_id'] = context.id
hash['lis_course_offering_sourcedid'] = context.sis_source_id if context.sis_source_id
elsif context.is_a?(Account)
hash['custom_canvas_account_id'] = context.id
hash['custom_canvas_account_sis_id'] = context.sis_source_id if context.sis_source_id
end
hash['custom_canvas_api_domain'] = root_account.domain
end
# need to set the locale here (instead of waiting for the first call to
# I18n.t like we usually do), because otherwise we'll have the wrong code
# for the launch_presentation_locale.
I18n.set_locale_with_localizer
hash['context_id'] = context.opaque_identifier(:asset_string)
hash['context_title'] = context.name
hash['context_label'] = context.course_code if context.respond_to?(:course_code)
hash['launch_presentation_locale'] = I18n.locale || I18n.default_locale.to_s
hash['launch_presentation_document_target'] = 'iframe'
hash['launch_presentation_width'] = tool.extension_setting(resource_type, :selection_width) if resource_type
hash['launch_presentation_height'] = tool.extension_setting(resource_type, :selection_height) if resource_type
hash['launch_presentation_return_url'] = return_url
hash['tool_consumer_instance_guid'] = root_account.lti_guid
hash['tool_consumer_instance_name'] = root_account.name
hash['tool_consumer_instance_contact_email'] = HostUrl.outgoing_email_address # TODO: find a better email address to use here
hash['tool_consumer_info_product_family_code'] = 'canvas'
hash['tool_consumer_info_version'] = 'cloud'
tool.set_custom_fields(hash, resource_type)
if resource_type == 'editor_button'
hash['selection_directive'] = 'embed_content' #backwards compatibility
hash['ext_content_intended_use'] = 'embed'
hash['ext_content_return_types'] = 'oembed,lti_launch_url,url,image_url,iframe'
hash['ext_content_return_url'] = return_url
elsif resource_type == 'resource_selection'
hash['selection_directive'] = 'select_link' #backwards compatibility
hash['ext_content_intended_use'] = 'navigation'
hash['ext_content_return_types'] = 'lti_launch_url'
hash['ext_content_return_url'] = return_url
elsif resource_type == 'homework_submission'
hash['ext_content_intended_use'] = 'homework'
hash['ext_content_return_url'] = return_url
end
hash['oauth_callback'] = 'about:blank'
VariableSubstitutor.new(self).substitute!
BasicLTI.generate_params(hash, url, tool.consumer_key, tool.shared_secret)
end
end
end

View File

@ -0,0 +1,76 @@
#
# Copyright (C) 2013 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
module BasicLTI
class VariableSubstitutor
def initialize(tool_launch)
@launch = tool_launch
end
# modifies the launch hash by substituting all the known variables
# if a variable is not supported or not allowed the value will not change
def substitute!
@launch.hash.each do |key, val|
if val.to_s.starts_with? '$'
method_name = "sub_#{var_to_method(val)}"
if self.respond_to?(method_name, true)
if new_val = self.send(method_name)
@launch.hash[key] = new_val
end
end
end
end
end
private
def var_to_method(var_name)
var_name.gsub('$', '').gsub('.', '_')
end
### These should return the value of substituting the variable the method is named for
### The method name should be prefixed with 'sub_' and have the same name as the variable except change all . to _
### For Example, to support substituting $Person.name.full, create a method called sub_Person_name_full
### If appropriate, check permissions by using the @launch object to reference the user/course
# $Person.name.full
def sub_Person_name_full
@launch.tool.include_name? ? @launch.user.name : nil
end
# $Person.name.family
def sub_Person_name_family
@launch.tool.include_name? ? @launch.user.last_name : nil
end
# $Person.name.given
def sub_Person_name_given
@launch.tool.include_name? ? @launch.user.first_name : nil
end
# returns the same LIS Role values as the default 'roles' parameter,
# but for concluded enrollments
# $Canvas.membership.concludedRoles
def sub_Canvas_membership_concludedRoles
@launch.user_data['concluded_role_types'] ? @launch.user_data['concluded_role_types'].join(',') : nil
end
end
end

View File

@ -0,0 +1,73 @@
#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require File.expand_path(File.dirname(__FILE__) + '/../../sharding_spec_helper.rb')
describe BasicLTI::VariableSubstitutor do
before do
@launch = mock()
@subber = BasicLTI::VariableSubstitutor.new(@launch)
end
it "should substitute user info if allowed" do
@launch.stubs(:user).returns(@launch)
@launch.stubs(:tool).returns(@launch)
@launch.stubs("include_name?").returns(true)
@launch.stubs(:name).returns("full name")
@launch.stubs(:first_name).returns("full")
@launch.stubs(:last_name).returns("name")
@hash = {'full' => '$Person.name.full', 'last' => '$Person.name.family', 'first' => '$Person.name.given'}
@launch.stubs(:hash).returns(@hash)
@subber.substitute!
@hash.should == {'full' => 'full name', 'last' => 'name', 'first' => 'full'}
end
it "should leave variable if not allowed" do
@launch.stubs(:user).returns(@launch)
@launch.stubs(:tool).returns(@launch)
@launch.stubs("include_name?").returns(false)
@launch.stubs(:name).returns("full name")
@hash = {'full' => '$Person.name.full'}
@launch.stubs(:hash).returns(@hash)
@subber.substitute!
@hash.should == {'full' => '$Person.name.full'}
end
it "should leave variable if not supported" do
@hash = {'something_crazy' => '$Person.social_security_number'}
@launch.stubs(:hash).returns(@hash)
@subber.substitute!
@hash.should == {'something_crazy' => '$Person.social_security_number'}
end
it "should add concluded enrollments" do
@hash = {'concluded_roles' => '$Canvas.membership.concludedRoles'}
@launch.stubs(:hash).returns(@hash)
@launch.stubs(:user_data).returns({'concluded_role_types' => ['hey']})
@subber.substitute!
@hash.should == {'concluded_roles' => 'hey'}
end
end

View File

@ -367,57 +367,66 @@ describe BasicLTI do
BasicLTI.user_lti_data(student, @course2)['role_types'].should == ['urn:lti:sysrole:ims/lis/None']
end
it "xml converter should use raise an error when unescaped ampersands are used in launch url" do
xml = <<-XML
<?xml version="1.0" encoding="UTF-8"?>
<cartridge_basiclti_link xmlns="http://www.imsglobal.org/xsd/imslticc_v1p0"
xmlns:blti = "http://www.imsglobal.org/xsd/imsbasiclti_v1p0"
xmlns:lticm ="http://www.imsglobal.org/xsd/imslticm_v1p0"
xmlns:lticp ="http://www.imsglobal.org/xsd/imslticp_v1p0"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://www.imsglobal.org/xsd/imslticc_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticc_v1p0.xsd
http://www.imsglobal.org/xsd/imsbasiclti_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imsbasiclti_v1p0.xsd
http://www.imsglobal.org/xsd/imslticm_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticm_v1p0.xsd
http://www.imsglobal.org/xsd/imslticp_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticp_v1p0.xsd">
<blti:title>Other Name</blti:title>
<blti:description>Description</blti:description>
<blti:launch_url>http://example.com/other_url?unescapedampersands=1&arebadnews=2</blti:launch_url>
<cartridge_bundle identifierref="BLTI001_Bundle"/>
<cartridge_icon identifierref="BLTI001_Icon"/>
</cartridge_basiclti_link>
XML
lti = CC::Importer::BLTIConverter.new
lambda {lti.convert_blti_xml(xml)}.should raise_error
it "should list concluded roles" do
course_with_student(:active_all => true)
course_with_teacher(:course => @course, :active_all => true)
@course.complete
BasicLTI.user_lti_data(@student, @course)['concluded_role_types'].should == ['Learner']
BasicLTI.user_lti_data(@teacher, @course)['concluded_role_types'].should == ['Instructor']
end
it "xml converter should use raise an error when unescaped ampersands are used in custom url properties" do
xml = <<-XML
<?xml version="1.0" encoding="UTF-8"?>
<cartridge_basiclti_link xmlns="http://www.imsglobal.org/xsd/imslticc_v1p0"
xmlns:blti = "http://www.imsglobal.org/xsd/imsbasiclti_v1p0"
xmlns:lticm ="http://www.imsglobal.org/xsd/imslticm_v1p0"
xmlns:lticp ="http://www.imsglobal.org/xsd/imslticp_v1p0"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://www.imsglobal.org/xsd/imslticc_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticc_v1p0.xsd
http://www.imsglobal.org/xsd/imsbasiclti_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imsbasiclti_v1p0.xsd
http://www.imsglobal.org/xsd/imslticm_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticm_v1p0.xsd
http://www.imsglobal.org/xsd/imslticp_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticp_v1p0.xsd">
<blti:title>Other Name</blti:title>
<blti:description>Description</blti:description>
<blti:launch_url>http://example.com</blti:launch_url>
<blti:extensions platform="canvas.instructure.com">
<lticm:property name="privacy_level">public</lticm:property>
<lticm:options name="course_navigation">
<lticm:property name="url">https://example.com/attendance?param1=1&param2=2</lticm:property>
<lticm:property name="enabled">true</lticm:property>
</lticm:options>
</blti:extensions>
<cartridge_bundle identifierref="BLTI001_Bundle"/>
<cartridge_icon identifierref="BLTI001_Icon"/>
</cartridge_basiclti_link>
XML
lti = CC::Importer::BLTIConverter.new
lambda {lti.convert_blti_xml(xml)}.should raise_error
end
end
it "xml converter should use raise an error when unescaped ampersands are used in launch url" do
xml = <<-XML
<?xml version="1.0" encoding="UTF-8"?>
<cartridge_basiclti_link xmlns="http://www.imsglobal.org/xsd/imslticc_v1p0"
xmlns:blti = "http://www.imsglobal.org/xsd/imsbasiclti_v1p0"
xmlns:lticm ="http://www.imsglobal.org/xsd/imslticm_v1p0"
xmlns:lticp ="http://www.imsglobal.org/xsd/imslticp_v1p0"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://www.imsglobal.org/xsd/imslticc_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticc_v1p0.xsd
http://www.imsglobal.org/xsd/imsbasiclti_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imsbasiclti_v1p0.xsd
http://www.imsglobal.org/xsd/imslticm_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticm_v1p0.xsd
http://www.imsglobal.org/xsd/imslticp_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticp_v1p0.xsd">
<blti:title>Other Name</blti:title>
<blti:description>Description</blti:description>
<blti:launch_url>http://example.com/other_url?unescapedampersands=1&arebadnews=2</blti:launch_url>
<cartridge_bundle identifierref="BLTI001_Bundle"/>
<cartridge_icon identifierref="BLTI001_Icon"/>
</cartridge_basiclti_link>
XML
lti = CC::Importer::BLTIConverter.new
lambda {lti.convert_blti_xml(xml)}.should raise_error
end
it "xml converter should use raise an error when unescaped ampersands are used in custom url properties" do
xml = <<-XML
<?xml version="1.0" encoding="UTF-8"?>
<cartridge_basiclti_link xmlns="http://www.imsglobal.org/xsd/imslticc_v1p0"
xmlns:blti = "http://www.imsglobal.org/xsd/imsbasiclti_v1p0"
xmlns:lticm ="http://www.imsglobal.org/xsd/imslticm_v1p0"
xmlns:lticp ="http://www.imsglobal.org/xsd/imslticp_v1p0"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://www.imsglobal.org/xsd/imslticc_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticc_v1p0.xsd
http://www.imsglobal.org/xsd/imsbasiclti_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imsbasiclti_v1p0.xsd
http://www.imsglobal.org/xsd/imslticm_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticm_v1p0.xsd
http://www.imsglobal.org/xsd/imslticp_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticp_v1p0.xsd">
<blti:title>Other Name</blti:title>
<blti:description>Description</blti:description>
<blti:launch_url>http://example.com</blti:launch_url>
<blti:extensions platform="canvas.instructure.com">
<lticm:property name="privacy_level">public</lticm:property>
<lticm:options name="course_navigation">
<lticm:property name="url">https://example.com/attendance?param1=1&param2=2</lticm:property>
<lticm:property name="enabled">true</lticm:property>
</lticm:options>
</blti:extensions>
<cartridge_bundle identifierref="BLTI001_Bundle"/>
<cartridge_icon identifierref="BLTI001_Icon"/>
</cartridge_basiclti_link>
XML
lti = CC::Importer::BLTIConverter.new
lambda {lti.convert_blti_xml(xml)}.should raise_error
end
end