canvas-lms/lib/basic_lti.rb

148 lines
6.1 KiB
Ruby

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)
# 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
class ToolLaunch < Struct.new(:url, :tool, :user, :context, :link_code, :return_url, :resource_type, :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]
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 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
hash['roles'] = user.lti_role_types(context).join(',') # AccountAdmin, Student, Faculty or Observer
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
if context.respond_to?(:root_account)
pseudo = user.sis_pseudonym_for(context)
elsif tool.context && tool.context.respond_to?(:root_account)
pseudo = user.sis_pseudonym_for(tool.context)
end
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
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'] = 600
hash['launch_presentation_height'] = 400
hash['launch_presentation_return_url'] = return_url
root_context = (context.respond_to?(:root_account) && context.root_account) || context
hash['tool_consumer_instance_guid'] = "#{root_context.opaque_identifier(:asset_string)}.#{HostUrl.context_host(context)}"
hash['tool_consumer_instance_name'] = root_context.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'
elsif resource_type == 'resource_selection'
hash['selection_directive'] = 'select_link'
end
hash['oauth_callback'] = 'about:blank'
BasicLTI.generate_params(hash, url, tool.consumer_key, tool.shared_secret)
end
end
end