add tool setting service

fixes PLAT-608 PLAT-613 PLAT-615 PLAT-616 PLAT-620

test-plan
*spec should pass

Change-Id: Idf2a7d89973231a070a8d368bd60554e3501cdf6
Reviewed-on: https://gerrit.instructure.com/40227
Reviewed-by: Brad Humphrey <brad@instructure.com>
Tested-by: Jenkins <jenkins@instructure.com>
Product-Review: Nathan Mills <nathanm@instructure.com>
QA-Review: Nathan Mills <nathanm@instructure.com>
This commit is contained in:
Nathan Mills 2014-08-26 13:50:51 -06:00
parent ea232fd3f1
commit 63945c4d35
24 changed files with 508 additions and 193 deletions

View File

@ -46,7 +46,7 @@ gem 'hoe', '3.8.1'
gem 'i18n', '0.6.9'
gem 'i18nema', RUBY_VERSION >= '2.2' ? '0.0.8' : '0.0.7'
gem 'icalendar', '1.5.4'
gem 'ims-lti', '2.0.0.beta.6'
gem 'ims-lti', '2.0.0.beta.7'
gem 'jammit', '0.6.6'
gem 'cssmin', '1.0.3'
gem 'jsmin', '1.0.1'

View File

@ -27,50 +27,49 @@ module Lti
@lti_launch.params = message.post_params
@lti_launch.link_text = I18n.t('lti2.register_tool', 'Register Tool')
@lti_launch.launch_type = message.launch_presentation_document_target
render template: 'lti/framed_launch'
end
def basic_lti_launch_request
if message_handler = MessageHandler.where(id: params[:lti_message_handler_id]).first
resource_handler = message_handler.resource
if message_handler = MessageHandler.find(params[:message_handler_id])
resource_handler = message_handler.resource_handler
tool_proxy = resource_handler.tool_proxy
#TODO create scoped method for query
if ToolProxyBinding.where(tool_proxy_id: tool_proxy.id, context_id: @context.id, context_type: @context.class).count(:all) > 0
if tool_proxy.workflow_state == 'active'
message = IMS::LTI::Models::Messages::BasicLTILaunchRequest.new(
launch_url: message_handler.launch_path,
oauth_consumer_key: tool_proxy.guid,
lti_version: IMS::LTI::Models::LTIModel::LTI_VERSION_2P0,
resource_link_id: Lti::Asset.opaque_identifier_for(@context),
resource_link_id: build_resource_link_id(tool_proxy),
context_id: Lti::Asset.opaque_identifier_for(@context),
tool_consumer_instance_guid: @context.root_account.lti_guid,
launch_presentation_document_target: IMS::LTI::Models::Messages::Message::LAUNCH_TARGET_IFRAME
)
message.add_custom_params(custom_params(message_handler.parameters))
message.add_custom_params(custom_params(message_handler.parameters, tool_proxy, message.resource_link_id))
@lti_launch = Launch.new
@lti_launch.resource_url = message.launch_url
@lti_launch.params = message.signed_post_params(tool_proxy.shared_secret)
@lti_launch.link_text = message_handler.resource.name
@lti_launch.link_text = resource_handler.name
@lti_launch.launch_type = message.launch_presentation_document_target
render template: 'lti/framed_launch' and return
end
end
not_found and return
not_found
end
private
def custom_params(parameters)
lookup_hash = common_variable_substitutions.inject({}) { |hash, (k,v)| hash[k.gsub(/\A\$/, '')] = v ; hash}
def custom_params(parameters, tool_proxy, resource_link_id)
params = IMS::LTI::Models::Parameter.from_json(parameters || [])
IMS::LTI::Models::Parameter.process_params(params, lookup_hash)
IMS::LTI::Models::Parameter.process_params(params, lti2_variable_substitutions(parameters, tool_proxy, resource_link_id))
end
def tool_consumer_profile_url
tp_id = SecureRandom.uuid
tp_id = "339b6700-e4cb-47c5-a54f-3ee0064921a9" #Hard coded until we start persisting the tcp
case context
when Course
course_tool_consumer_profile_url(context, tp_id)
@ -92,5 +91,44 @@ module Lti
end
end
def find_binding(tool_proxy)
if @context.is_a?(Course)
binding = ToolProxyBinding.where(context_type: 'Course', context: @context.id, tool_proxy_id: tool_proxy.id)
return binding if binding
end
account_ids = @context.account_chain.map{ |a| a.id }
bindings = ToolProxyBinding.where(context_type: 'Account', context_id: account_ids, tool_proxy_id: tool_proxy.id)
binding_lookup = bindings.each_with_object({}) {|binding, hash| hash[binding.context_id] = binding }
sorted_bindings = account_ids.map { |account_id| binding_lookup[account_id] }
sorted_bindings.first
end
def build_resource_link_id(message_handler, postfix = nil)
resource_link_id = "#{params[:tool_launch_context]}_#{message_handler.id}"
resource_link_id += "_#{params[:postfix_id]}" if params[:postfix_id]
Base64.urlsafe_encode64("#{resource_link_id}")
end
def lti2_variable_substitutions(parameters, tool_proxy, resource_link_id)
substitutions = common_variable_substitutions.inject({}) { |hash, (k,v)| hash[k.gsub(/\A\$/, '')] = v ; hash}
substitutions.merge!(prep_tool_settings(parameters, tool_proxy, resource_link_id))
substitutions
end
def prep_tool_settings(parameters, tool_proxy, resource_link_id)
if parameters && (parameters.map {|p| p['variable']}.compact & (%w( LtiLink.custom.url ToolProxyBinding.custom.url ToolProxy.custom.url ))).any?
link = ToolSetting.first_or_create(tool_proxy: tool_proxy, context: @context, resource_link_id: resource_link_id)
binding = ToolSetting.first_or_create(tool_proxy: tool_proxy, context: @context, resource_link_id: nil)
proxy = ToolSetting.first_or_create(tool_proxy: tool_proxy, context: nil, resource_link_id: nil)
{
'LtiLink.custom.url' => show_lti_tool_settings_url(link.id),
'ToolProxyBinding.custom.url' => show_lti_tool_settings_url(binding.id),
'ToolProxy.custom.url' => show_lti_tool_settings_url(proxy.id)
}
else
{}
end
end
end
end

View File

@ -24,23 +24,12 @@ module Lti
def show
uuid = "339b6700-e4cb-47c5-a54f-3ee0064921a9" #Hard coded until we start persisting the tcp
profile = Lti::ToolConsumerProfileCreator.new(@account, tool_consumer_profile_url(uuid), tool_proxy_url).create
profile = Lti::ToolConsumerProfileCreator.new(@account, tool_consumer_profile_url(uuid), request.domain, context.class.name.downcase).create
render json: profile.to_json, :content_type => 'application/json'
end
private
def tool_proxy_url
case context
when Course
create_course_lti_tool_proxy_url(context)
when Account
create_account_lti_tool_proxy_url(context)
else
raise "Unsupported context"
end
end
def tool_consumer_profile_url(uuid)
case context
when Course

View File

@ -17,13 +17,15 @@
module Lti
class ToolProxyController < ApplicationController
include Lti::ApiServiceHelper
before_filter :require_context, :except => [:show]
skip_before_filter :require_user, only: [:create, :show]
skip_before_filter :load_user, only: [:create, :show]
def show
tool_proxy = ToolProxy.where(guid: params['tool_proxy_guid']).first
if tool_proxy && authorized_request?(tool_proxy.shared_secret)
if tool_proxy && oauth_authenticated_request?(tool_proxy.shared_secret)
render json: tool_proxy.raw_data
else
render json: {error: 'unauthorized'}, status: :unauthorized
@ -32,7 +34,7 @@ module Lti
def create
secret = RegistrationRequestService.retrieve_registration_password(oauth_consumer_key)
if authorized_request?(secret)
if oauth_authenticated_request?(secret)
tool_proxy = ToolProxyService.new.process_tool_proxy_json(request.body.read, context, oauth_consumer_key)
json = {
"@context" => "http://purl.imsglobal.org/ctx/lti/v2/ToolProxyId",
@ -47,14 +49,5 @@ module Lti
end
end
private
def authorized_request?(secret)
OAuth::Signature.build(request, :consumer_secret => secret).verify()
end
def oauth_consumer_key
@oauth_consumer_key ||= OAuth::Helper.parse_header(request.authorization)['oauth_consumer_key']
end
end
end

View File

@ -0,0 +1,93 @@
# Copyright (C) 2014 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 Lti
class ToolSettingController < ApplicationController
include Lti::ApiServiceHelper
skip_before_filter :require_context
skip_before_filter :require_user
skip_before_filter :load_user
def show
tool_setting = ToolSetting.includes(:tool_proxy).find(params[:tool_setting_id])
if tool_setting && oauth_authenticated_request?(tool_setting.tool_proxy.shared_secret)
render json: tool_setting_json(tool_setting, value_to_boolean(params[:bubble]))
else
render json: {error: 'unauthorized'}, status: :unauthorized
end
end
def update
tool_setting = ToolSetting.includes(:tool_proxy).find(params[:tool_setting_id])
if tool_setting && oauth_authenticated_request?(tool_setting.tool_proxy.shared_secret)
tool_setting.update_attribute(:custom, custom_settings(tool_setting_type(tool_setting), JSON.parse(request.body.read)))
else
render json: {error: 'unauthorized'}, status: :unauthorized
end
end
private
def tool_setting_json(tool_setting, bubble)
if bubble
graph = []
while tool_setting do
graph << collect_tool_settings(tool_setting)
case tool_setting_type(tool_setting)
when 'LtiLink'
tool_setting = ToolSetting.where(tool_proxy_id: tool_setting.tool_proxy_id, context_type: tool_setting.context_type, context_id: tool_setting.context_id, resource_link_id: nil).first
when 'ToolProxyBinding'
tool_setting = ToolSetting.where(tool_proxy_id: tool_setting.tool_proxy_id, context_type: nil, context_id: nil, resource_link_id: nil).first
when 'ToolProxy'
tool_setting = nil
end
end
IMS::LTI::Models::ToolSettingContainer.new(graph: graph)
else
tool_setting.custom
end
end
def collect_tool_settings(tool_setting)
type = tool_setting_type(tool_setting)
url = show_lti_tool_settings_url(tool_setting.id)
custom = tool_setting.custom || {}
IMS::LTI::Models::ToolSetting.new(custom: custom, type: type, id: url)
end
def custom_settings(type, json)
if request.content_type == 'application/vnd.ims.lti.v2.toolsettings+json'
setting = json['@graph'].find { |setting| setting['type'] == type }
setting['custom']
else
json
end
end
def tool_setting_type(tool_setting)
if tool_setting.resource_link_id.present?
'LtiLink'
elsif tool_setting.context.present?
'ToolProxyBinding'
else
'ToolProxy'
end
end
end
end

View File

@ -18,15 +18,15 @@
module Lti
class MessageHandler< ActiveRecord::Base
attr_accessible :message_type, :launch_path, :capabilities, :parameters, :resource_handler, :links
attr_accessible :message_type, :launch_path, :capabilities, :parameters, :resource
belongs_to :resource, class_name: "Lti::ResourceHandler", :foreign_key => :resource_handler_id
belongs_to :resource_handler, class_name: "Lti::ResourceHandler", :foreign_key => :resource_handler_id
has_many :links, :class_name => 'Lti::LtiLink'
serialize :capabilities
serialize :parameters
validates_presence_of :message_type, :resource, :launch_path
validates_presence_of :message_type, :resource_handler, :launch_path
end
end

View File

@ -24,7 +24,6 @@ module Lti
belongs_to :tool_proxy, class_name: 'Lti::ToolProxy'
has_many :message_handlers, class_name: 'Lti::MessageHandler', :foreign_key => :resource_handler_id
has_many :placements, class_name: 'Lti::ResourcePlacement'
has_many :tool_links, :class_name => 'Lti::ToolLink'
serialize :icon_info

View File

@ -1,10 +1,11 @@
module Lti
class ToolConsumerProfileCreator
def initialize(account, tcp_url, tp_registration_url)
@root_account = account.root_account
def initialize(account, tcp_url, domain, context_type)
@tcp_url = tcp_url
@tp_registration_url = tp_registration_url
@context_type = context_type
@root_account = account.root_account
@domain = domain
end
def create
@ -12,7 +13,7 @@ module Lti
profile.id = @tcp_url
profile.lti_version = IMS::LTI::Models::ToolConsumerProfile::LTI_VERSION_2P0
profile.product_instance = create_product_instance
profile.service_offered = [create_tp_registration_service]
profile.service_offered = [ create_tp_registration_service, create_tp_item_service, create_tp_settings_service, create_binding_settings_service, create_link_settings_service ]
profile.capability_offered = capabilities
profile
@ -53,16 +54,55 @@ module Lti
def create_tp_registration_service
reg_srv = IMS::LTI::Models::RestService.new
reg_srv.id = "#{@tcp_url}#ToolProxy.collection"
reg_srv.endpoint = @tp_registration_url
reg_srv.endpoint = "https://#{@domain}/api/lti/#{@context_type}s/{#{@context_type}_id}/tool_proxy"
reg_srv.type = 'RestService'
reg_srv.format = ['application/vnd.ims.lti.v2.toolproxy+json']
reg_srv.action = 'POST'
reg_srv
end
def capabilities
def create_tp_item_service
reg_srv = IMS::LTI::Models::RestService.new
reg_srv.id = "#{@tcp_url}#ToolProxy.item"
reg_srv.endpoint = "https://#{@domain}/api/lti/tool_settings/tool_proxy/{tool_proxy_id}"
reg_srv.type = 'RestService'
reg_srv.format = ["application/vnd.ims.lti.v2.toolproxy+json"]
reg_srv.action = ['GET']
reg_srv
end
%w( basic-lti-launch-request Canvas.api.domain)
def create_tp_settings_service
reg_srv = IMS::LTI::Models::RestService.new
reg_srv.id = "#{@tcp_url}#ToolProxySettings"
reg_srv.endpoint = "https://#{@domain}/api/lti/tool_settings/tool_proxy/{tool_proxy_id}"
reg_srv.type = 'RestService'
reg_srv.format = ['application/vnd.ims.lti.v2.toolsettings+json', 'application/vnd.ims.lti.v2.toolsettings.simple+json']
reg_srv.action = ['GET', 'PUT']
reg_srv
end
def create_binding_settings_service
reg_srv = IMS::LTI::Models::RestService.new
reg_srv.id = "#{@tcp_url}#ToolProxyBindingSettings"
reg_srv.endpoint = "https://#{@domain}/api/lti/tool_settings/bindings/{binding_id}"
reg_srv.type = 'RestService'
reg_srv.format = ['application/vnd.ims.lti.v2.toolsettings+json', 'application/vnd.ims.lti.v2.toolsettings.simple+json']
reg_srv.action = ['GET', 'PUT']
reg_srv
end
def create_link_settings_service
reg_srv = IMS::LTI::Models::RestService.new
reg_srv.id = "#{@tcp_url}#LtiLinkSettings"
reg_srv.endpoint = "https://#{@domain}/api/lti/tool_settings/links/{tool_proxy_id}"
reg_srv.type = 'RestService'
reg_srv.format = ['application/vnd.ims.lti.v2.toolsettings+json', 'application/vnd.ims.lti.v2.toolsettings.simple+json']
reg_srv.action = ['GET', 'PUT']
reg_srv
end
def capabilities
%w( basic-lti-launch-request Canvas.api.domain LtiLink.custom.url ToolProxyBinding.custom.url ToolProxy.custom.url)
end
end

View File

@ -27,13 +27,12 @@ module Lti
belongs_to :context, :polymorphic => true
belongs_to :product_family, class_name: 'Lti::ProductFamily'
has_one :tool_setting, :class_name => 'Lti::ToolSetting', as: :settable
serialize :raw_data
validates_presence_of :shared_secret, :guid, :product_version, :lti_version, :product_family_id, :workflow_state, :raw_data, :context
validates_uniqueness_of :guid
validates_inclusion_of :context_type, :allow_nil => true, :in => ['Course', 'Account']
validates_inclusion_of :workflow_state, in: ['active', 'deleted', 'disabled']
end
end

View File

@ -18,16 +18,15 @@
module Lti
class ToolProxyBinding < ActiveRecord::Base
attr_accessible :context, :tool_proxy
attr_accessible :context, :tool_proxy, :enabled
belongs_to :tool_proxy, class_name: 'Lti::ToolProxy'
validates_inclusion_of :context_type, :allow_nil => true, :in => ['Course', 'Account']
belongs_to :context, :polymorphic => true
has_one :tool_setting, :class_name => 'Lti::ToolSetting', as: :settable
has_many :links, :class_name => 'Lti::LtiLink', foreign_key: 'tool_proxy_binding_id'
validates_presence_of :tool_proxy, :context
after_save :touch_context
validates_presence_of :tool_proxy, :context, :enabled
validates_inclusion_of :context_type, :allow_nil => true, :in => ['Course', 'Account']
end
end

View File

@ -73,7 +73,7 @@ module Lti
message_handler.launch_path = "#{base_path}#{mh.path}"
message_handler.capabilities = create_json(mh.enabled_capability)
message_handler.parameters = create_json(mh.parameter.as_json)
message_handler.resource = resource
message_handler.resource_handler = resource
message_handler.save!
message_handler
end

View File

@ -17,8 +17,22 @@
module Lti
class ToolSetting < ActiveRecord::Base
belongs_to :settable, polymorphic: true
attr_accessible :tool_proxy, :context, :resource_link_id, :custom
belongs_to :tool_proxy
belongs_to :context, polymorphic: true
validates_presence_of :tool_proxy
validates_presence_of :context, if: :has_resource_link_id?
validates_inclusion_of :context_type, :allow_nil => true, :in => ['Course', 'Account']
serialize :custom
private
def has_resource_link_id?
resource_link_id.present?
end
end
end

View File

@ -285,7 +285,7 @@ CanvasRails::Application.routes.draw do
end
end
get 'lti/basic_lti_launch_request/:lti_message_handler_id', controller: 'lti/message', action: 'basic_lti_launch_request', as: :basic_lti_launch_request
get 'lti/basic_lti_launch_request/:message_handler_id', controller: 'lti/message', action: 'basic_lti_launch_request', as: :basic_lti_launch_request
get 'lti/tool_proxy_registration', controller: 'lti/message', action: 'registration', :as => :tool_proxy_registration
@ -548,7 +548,7 @@ CanvasRails::Application.routes.draw do
end
get 'lti/basic_lti_launch_request/:lti_message_handler_id', controller: 'lti/message', action: 'basic_lti_launch_request', as: :basic_lti_launch_request
get 'lti/basic_lti_launch_request/:message_handler_id', controller: 'lti/message', action: 'basic_lti_launch_request', as: :basic_lti_launch_request
get 'lti/tool_proxy_registration', controller: 'lti/message', action: 'registration', :as => :tool_proxy_registration
@ -1595,7 +1595,13 @@ CanvasRails::Application.routes.draw do
get "#{prefix}/tool_consumer_profile/:tool_consumer_profile_id", controller: 'lti/tool_consumer_profile', action: 'show', :as => "#{context}_tool_consumer_profile"
post "#{prefix}/tool_proxy", :controller => 'lti/tool_proxy', :action => :create, :path_name => "create_#{context}_lti_tool_proxy"
end
#Tool Setting Services
get "tool_settings/:tool_setting_id", controller: 'lti/tool_setting', action: :show, as: 'show_lti_tool_settings'
put "tool_settings/:tool_setting_id", controller: 'lti/tool_setting', action: :update, as: 'update_lti_tool_settings'
#Tool Proxy Services
get "tool_proxy/:tool_proxy_guid", :controller => 'lti/tool_proxy', :action => :show, :path_name => "show_lti_tool_proxy"
end
match '/assets/:package.:extension' => 'jammit#package', :as => :jammit if defined?(Jammit)

View File

@ -0,0 +1,42 @@
class AddLtiLinkBindingAssociation < ActiveRecord::Migration
tag :predeploy
def self.up
drop_table :lti_tool_links
drop_table :lti_tool_settings
add_column :lti_tool_proxy_bindings, :enabled, :boolean, null: false, default: true
create_table :lti_tool_settings do |t|
t.integer :tool_proxy_id, limit:8, null: false
t.integer :context_id, limit: 8
t.string :context_type
t.text :resource_link_id
t.text :custom
t.timestamps
end
add_index :lti_tool_settings, [:resource_link_id, :context_type, :context_id, :tool_proxy_id],name: 'index_lti_tool_settings_on_link_context_and_tool_proxy', unique: true
end
def self.down
remove_column :lti_tool_proxy_bindings, :enabled
drop_table :lti_tool_settings
create_table :lti_tool_settings do |t|
t.integer :settable_id, limit: 8, null: false
t.string :settable_type, null: false
t.text :custom
end
create_table :lti_tool_links do |t|
t.integer :resource_handler_id, limit: 8, null: false
t.string :uuid, null: false
end
add_index :lti_tool_settings, [:settable_id, :settable_type], unique: true
add_index :lti_tool_links, :uuid, unique: true
end
end

View File

@ -1,4 +1,5 @@
# Copyright (C) 2014 Instructure, Inc.
#
# Copyright (C) 2011 - 2014 Instructure, Inc.
#
# This file is part of Canvas.
#
@ -15,14 +16,19 @@
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Filters added to this controller apply to all controllers in the application.
# Likewise, all the methods added will be available for all controllers.
module Lti
class ToolLink < ActiveRecord::Base
belongs_to :resource_handler, class_name: 'Lti::ResourceHandler'
has_one :tool_setting, :class_name => 'Lti::ToolSetting', as: :settable
module ApiServiceHelper
attr_accessible :uuid
def oauth_authenticated_request?(secret)
!!OAuth::Signature.build(request, :consumer_secret => secret).verify()
end
after_initialize { self.uuid = SecureRandom::uuid}
def oauth_consumer_key
@oauth_consumer_key ||= OAuth::Helper.parse_header(request.authorization)['oauth_consumer_key']
end
end
end

View File

@ -45,14 +45,14 @@ module Lti
if @context.is_a? Course
substitutions.merge!(
{
'$Canvas.course.id' => @context.id,
'$Canvas.course.sisSourceId' => @context.sis_source_id,
'$Canvas.enrollment.enrollmentState' => -> { lti_helper.enrollment_state },
'$Canvas.membership.roles' => -> { lti_helper.current_canvas_roles },
#This is a list of IMS LIS roles should have a different key
'$Canvas.membership.concludedRoles' => -> { lti_helper.concluded_lis_roles },
}
{
'$Canvas.course.id' => @context.id,
'$Canvas.course.sisSourceId' => @context.sis_source_id,
'$Canvas.enrollment.enrollmentState' => -> { lti_helper.enrollment_state },
'$Canvas.membership.roles' => -> { lti_helper.current_canvas_roles },
#This is a list of IMS LIS roles should have a different key
'$Canvas.membership.concludedRoles' => -> { lti_helper.concluded_lis_roles },
}
)
end

View File

@ -0,0 +1,156 @@
#
# Copyright (C) 2014 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__) + '/../api_spec_helper')
module Lti
describe ToolSettingController, type: :request do
let(:account) { Account.new }
let (:product_family) { ProductFamily.create(vendor_code: '123', product_code: 'abc', vendor_name: 'acme', root_account: account) }
let(:tool_proxy) do
ToolProxy.create!(
context: account,
guid: SecureRandom.uuid,
shared_secret: 'abc',
product_family: product_family,
root_account: account,
product_version: '1',
workflow_state: 'disabled',
raw_data: {'proxy' => 'value'},
lti_version: '1'
)
end
let(:resource_handler) { ResourceHandler.create!(resource_type_code: 'code', name: 'name', tool_proxy: tool_proxy) }
let(:message_handler) { MessageHandler.create(message_type: 'basic-lti-launch-request', launch_path: 'https://samplelaunch/blti', resource_handler: resource_handler) }
before do
OAuth::Signature.stubs(:build).returns(mock(verify: true))
@link_setting = ToolSetting.create(tool_proxy: tool_proxy, context: account, resource_link_id: 'abc', custom: {link: :setting})
@binding_setting = ToolSetting.create(tool_proxy: tool_proxy, context: account, custom: {binding: :setting})
@proxy_setting = ToolSetting.create(tool_proxy: tool_proxy, custom: {proxy: :setting})
end
describe '#lti_link_show', type: :request do
it 'returns the lti link simple json' do
get "/api/lti/tool_settings/#{@link_setting.id}.json", tool_setting_id: @link_setting, bubble: false
JSON.parse(body).should == {"link" => "setting"}
end
it 'returns the lti link tool settings json with bubble' do
get "/api/lti/tool_settings/#{@link_setting.id}.json", tool_setting_id: @link_setting, bubble: true
json = JSON.parse(body)
setting = json['@graph'].find { |setting| setting['@type'] == "LtiLink" }
setting['custom'].should == {"link" => "setting"}
end
it 'creates a new lti link tool setting' do
tool_setting = ToolSetting.create(tool_proxy: tool_proxy, context: account, resource_link_id: 'resource_link')
params = {'link' => 'settings'}
put "/api/lti/tool_settings/#{tool_setting.id}.json", params.to_json, {'CONTENT_TYPE' => 'application/vnd.ims.lti.v2.toolsettings.simple+json', 'ACCEPT' => 'application/vnd.ims.lti.v2.toolsettings.simple+json'}
tool_setting.reload.custom.should == {'link' => 'settings'}
end
end
describe '#tool_proxy_binding_show' do
it 'returns the lti link simple json' do
get "/api/lti/tool_settings/#{@binding_setting.id}.json", tool_setting_id: @binding_setting.id, bubble: false
JSON.parse(body).should == {"binding" => "setting"}
end
it 'returns the lti link tool settings json with bubble' do
get "/api/lti/tool_settings/#{@binding_setting.id}.json", tool_setting_id: @binding_setting.id, bubble: true
json = JSON.parse(body)
setting = json['@graph'].find { |setting| setting['@type'] == "ToolProxyBinding" }
setting['custom'].should == {"binding" => "setting"}
end
it 'creates a new binding tool setting' do
tool_setting = ToolSetting.create(tool_proxy: tool_proxy, context: account)
params = {'binding' => 'settings'}
put "/api/lti/tool_settings/#{tool_setting.id}.json", params.to_json, {'CONTENT_TYPE' => 'application/vnd.ims.lti.v2.toolsettings.simple+json', 'ACCEPT' => 'application/vnd.ims.lti.v2.toolsettings.simple+json'}
tool_setting.reload.custom.should == {'binding' => 'settings'}
end
end
describe '#tool_proxy_show' do
it 'returns the lti link simple json' do
get "/api/lti/tool_settings/#{@proxy_setting.id}.json", link_id: @proxy_setting.id, bubble: false
JSON.parse(body).should == {"proxy" => "setting"}
end
it 'returns the lti link tool settings json with bubble' do
get "/api/lti/tool_settings/#{@proxy_setting.id}.json", link_id: @proxy_setting.id, bubble: true
json = JSON.parse(body)
setting = json['@graph'].find { |setting| setting['@type'] == "ToolProxy" }
setting['custom'].should == {"proxy" => "setting"}
end
it 'creates a new tool_proxy tool setting' do
tool_setting = ToolSetting.create(tool_proxy: tool_proxy)
params = {'tool_proxy' => 'settings'}
put "/api/lti/tool_settings/#{tool_setting.id}.json", params.to_json, {'CONTENT_TYPE' => 'application/vnd.ims.lti.v2.toolsettings.simple+json', 'ACCEPT' => 'application/vnd.ims.lti.v2.toolsettings.simple+json'}
tool_setting.reload.custom.should == {'tool_proxy' => 'settings'}
end
context 'bubble' do
it 'bubbles up all levels' do
get "/api/lti/tool_settings/#{@link_setting.id}.json", tool_setting_id: @link_setting.id, bubble: true
json = JSON.parse(body)
link_setting = json['@graph'].find { |setting| setting['@type'] == "LtiLink" }
link_setting['custom'].should == {"link" => "setting"}
binding_setting = json['@graph'].find { |setting| setting['@type'] == "ToolProxyBinding" }
binding_setting['custom'].should == {"binding" => "setting"}
proxy_setting = json['@graph'].find { |setting| setting['@type'] == "ToolProxy" }
proxy_setting['custom'].should == {"proxy" => "setting"}
end
it 'bubbles up from binding' do
get "/api/lti/tool_settings/#{@binding_setting.id}.json", tool_setting_id: @binding_setting.id, bubble: true
json = JSON.parse(body)
link_setting = json['@graph'].find { |setting| setting['@type'] == "LtiLink" }
link_setting.should be_nil
binding_setting = json['@graph'].find { |setting| setting['@type'] == "ToolProxyBinding" }
binding_setting['custom'].should == {"binding" => "setting"}
proxy_setting = json['@graph'].find { |setting| setting['@type'] == "ToolProxy" }
proxy_setting['custom'].should == {"proxy" => "setting"}
end
it 'bubbles up from tool proxy' do
get "/api/lti/tool_settings/#{@proxy_setting.id}.json", tool_setting_id: @proxy_setting.id, bubble: true
json = JSON.parse(body)
link_setting = json['@graph'].find { |setting| setting['@type'] == "LtiLink" }
link_setting.should be_nil
binding_setting = json['@graph'].find { |setting| setting['@type'] == "ToolProxyBinding" }
binding_setting.should be_nil
proxy_setting = json['@graph'].find { |setting| setting['@type'] == "ToolProxy" }
proxy_setting['custom'].should == {"proxy" => "setting"}
end
end
end
end
end

View File

@ -64,7 +64,7 @@ module Lti
let (:account) { Account.create }
let (:product_family) { ProductFamily.create(vendor_code: '123', product_code: 'abc', vendor_name: 'acme', root_account: account) }
let (:resource_handler) { ResourceHandler.create(resource_type_code: 'code', name: 'resource name', tool_proxy: tool_proxy) }
let (:message_handler) { MessageHandler.create(message_type: 'message_type', launch_path:'https://samplelaunch/blti', resource: resource_handler)}
let(:message_handler) { MessageHandler.create(message_type: 'basic-lti-launch-request', launch_path: 'https://samplelaunch/blti', resource_handler: resource_handler) }
let (:tool_proxy) { ToolProxy.create(
shared_secret: 'shared_secret',
guid: 'guid',
@ -77,12 +77,13 @@ module Lti
) }
context 'account' do
before :each do
@tool_proxy_binding = ToolProxyBinding.create(context: account, tool_proxy: tool_proxy)
before do
ToolProxyBinding.create(context: account, tool_proxy: tool_proxy)
end
it 'returns the signed params' do
get 'basic_lti_launch_request', account_id: account.id, lti_message_handler_id: message_handler.id
get 'basic_lti_launch_request', account_id: account.id, message_handler_id: message_handler.id, params: {tool_launch_context: 'my_custom_context'}
response.code.should == "200"
lti_launch = assigns[:lti_launch]
@ -97,10 +98,30 @@ module Lti
end
it 'returns a 404 when when no handler is found' do
get 'basic_lti_launch_request', account_id: account.id, lti_message_handler_id: 0
get 'basic_lti_launch_request', account_id: account.id, message_handler_id: 0
response.code.should == "404"
end
it 'does custom variable expansion for tool settings' do
parameters = %w( LtiLink.custom.url ToolProxyBinding.custom.url ToolProxy.custom.url ).map do |key|
IMS::LTI::Models::Parameter.new(name: key.underscore, variable: key )
end
message_handler.parameters = parameters.as_json
message_handler.save
get 'basic_lti_launch_request', account_id: account.id, message_handler_id: message_handler.id, params: {tool_launch_context: 'my_custom_context'}
response.code.should == "200"
params = assigns[:lti_launch].params.with_indifferent_access
params['custom_lti_link.custom.url'].should include('api/lti/tool_settings/')
params['custom_tool_proxy_binding.custom.url'].should include('api/lti/tool_settings/')
params['custom_tool_proxy.custom.url'].should include('api/lti/tool_settings/')
end
it 'uses the correct binding' do
end
end
end
end

View File

@ -25,7 +25,7 @@ module Lti
before(:each) do
subject.message_type = 'message_type'
subject.launch_path = 'launch_path'
subject.resource = ResourceHandler.new
subject.resource_handler = ResourceHandler.new
end
it 'requires the message type' do
@ -41,9 +41,9 @@ module Lti
end
it 'requires a resource_handler' do
subject.resource = nil
subject.resource_handler = nil
subject.save
subject.errors.first.should == [:resource, "can't be blank"]
subject.errors.first.should == [:resource_handler, "can't be blank"]
end
end

View File

@ -6,9 +6,7 @@ module Lti
let(:root_account) { mock('root account', lti_guid: 'my_guid') }
let(:account) { mock('account', root_account: root_account) }
let(:tcp_url) {'http://example.instructure.com/tcp/uuid'}
# let(:root_account) {mock('root account').stubs(:lti_guid).returns('my_guid')}
# let(:account) {mock('account').stubs(:root_account).returns(root_account)}
subject { ToolConsumerProfileCreator.new(account, tcp_url, 'http://tool-consumer.com/tp/reg') }
subject { ToolConsumerProfileCreator.new(account, tcp_url, 'example.instructure.com', 'account') }
describe '#create' do
@ -51,7 +49,7 @@ module Lti
profile = subject.create
reg_srv = profile.service_offered.find {|srv| srv.id.include? 'ToolProxy.collection'}
reg_srv.id.should == "#{tcp_url}#ToolProxy.collection"
reg_srv.endpoint.should == 'http://tool-consumer.com/tp/reg'
reg_srv.endpoint.should == 'https://example.instructure.com/api/lti/accounts/{account_id}/tool_proxy'
reg_srv.type.should == 'RestService'
reg_srv.format.should == ["application/vnd.ims.lti.v2.toolproxy+json"]
reg_srv.action.should include 'POST'

View File

@ -1,64 +0,0 @@
#
# Copyright (C) 2014 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__) + '/../../spec_helper.rb')
module Lti
describe ToolLink do
let (:account) { Account.create }
let (:product_family) { ProductFamily.create(vendor_code: '123', product_code: 'abc', vendor_name: 'acme', root_account: account) }
let (:resource_handler) { ResourceHandler.create(resource_type_code: 'code', name: 'resource name', tool_proxy: tool_proxy) }
let (:message_handler) { MessageHandler.create(message_type: 'message_type', launch_path:'https://samplelaunch/blti', resource: resource_handler)}
let (:tool_proxy) { ToolProxy.create(
shared_secret: 'shared_secret',
guid: 'guid',
product_version: '1.0beta',
lti_version: 'LTI-2p0',
product_family: product_family,
context: account,
workflow_state: 'active',
raw_data: 'some raw data'
) }
subject{resource_handler.tool_links.create}
describe '#create' do
it 'sets the uuid by default' do
link = resource_handler.tool_links.create
link.uuid.should_not == nil
end
end
context 'tool_settings' do
it 'can have a tool setting' do
subject.create_tool_setting(custom: {name: :foo})
subject.tool_setting[:custom].should == {name: :foo}
end
end
end
end

View File

@ -43,31 +43,6 @@ describe ToolProxyBinding do
subject.errors.first.should == [:tool_proxy, "can't be blank"]
end
context 'tool_settings' do
let (:account) { Account.create }
let (:product_family) { ProductFamily.create(vendor_code: '123', product_code: 'abc', vendor_name: 'acme', root_account: account) }
let (:resource_handler) { ResourceHandler.create(resource_type_code: 'code', name: 'resource name', tool_proxy: tool_proxy) }
let (:message_handler) { MessageHandler.create(message_type: 'message_type', launch_path:'https://samplelaunch/blti', resource: resource_handler)}
let (:tool_proxy) { ToolProxy.create(
shared_secret: 'shared_secret',
guid: 'guid',
product_version: '1.0beta',
lti_version: 'LTI-2p0',
product_family: product_family,
context: account,
workflow_state: 'active',
raw_data: 'some raw data'
) }
subject{ tool_proxy.bindings.create(context:account)}
it 'can have a tool setting' do
subject.create_tool_setting(custom: {name: :foo})
subject.tool_setting[:custom].should == {name: :foo}
end
end
end
end

View File

@ -102,24 +102,6 @@ module Lti
subject.errors[:raw_data].should include("can't be blank")
end
context 'tool_settings' do
subject { ToolProxy.create(
shared_secret: 'shared_secret',
guid: 'guid',
product_version: '1.0beta',
lti_version: 'LTI-2p0',
product_family: product_family,
context: account,
workflow_state: 'active',
raw_data: 'some raw data'
) }
it 'can have a tool setting' do
subject.create_tool_setting(custom: {name: :foo})
subject.tool_setting[:custom].should == {name: :foo}
end
end
end
end

View File

@ -20,6 +20,35 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb')
module Lti
describe ToolSetting do
let (:account) { Account.create }
let (:product_family) { ProductFamily.create(vendor_code: '123', product_code: 'abc', vendor_name: 'acme', root_account: account) }
let (:resource_handler) { ResourceHandler.create(resource_type_code: 'code', name: 'resource name', tool_proxy: tool_proxy) }
let (:message_handler) { MessageHandler.create(message_type: 'message_type', launch_path: 'https://samplelaunch/blti', resource: resource_handler) }
let (:tool_proxy) { ToolProxy.create(
shared_secret: 'shared_secret',
guid: 'guid',
product_version: '1.0beta',
lti_version: 'LTI-2p0',
product_family: product_family,
context: account,
workflow_state: 'active',
raw_data: 'some raw data'
) }
it 'can be associated with a resource link' do
subject.tool_proxy = tool_proxy
subject.context = account
subject.resource_link_id = '123456'
subject.save.should == true
end
it 'fails if there is a resource_link_id and no context' do
subject.tool_proxy = tool_proxy
subject.resource_link_id = '123456'
subject.save.should == false
subject.errors.first.should == [:context, "can't be blank"]
end
end
end