diff --git a/Gemfile.d/other_stuff.rb b/Gemfile.d/other_stuff.rb
index fee043cf310..87710d69f65 100644
--- a/Gemfile.d/other_stuff.rb
+++ b/Gemfile.d/other_stuff.rb
@@ -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'
diff --git a/app/controllers/lti/message_controller.rb b/app/controllers/lti/message_controller.rb
index 6987696460a..27b49f0ac3c 100644
--- a/app/controllers/lti/message_controller.rb
+++ b/app/controllers/lti/message_controller.rb
@@ -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
\ No newline at end of file
diff --git a/app/controllers/lti/tool_consumer_profile_controller.rb b/app/controllers/lti/tool_consumer_profile_controller.rb
index beea2aadafd..1503183e3fc 100644
--- a/app/controllers/lti/tool_consumer_profile_controller.rb
+++ b/app/controllers/lti/tool_consumer_profile_controller.rb
@@ -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
diff --git a/app/controllers/lti/tool_proxy_controller.rb b/app/controllers/lti/tool_proxy_controller.rb
index 0ed86375340..21631714d2b 100644
--- a/app/controllers/lti/tool_proxy_controller.rb
+++ b/app/controllers/lti/tool_proxy_controller.rb
@@ -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
\ No newline at end of file
diff --git a/app/controllers/lti/tool_setting_controller.rb b/app/controllers/lti/tool_setting_controller.rb
new file mode 100644
index 00000000000..f46eff8274a
--- /dev/null
+++ b/app/controllers/lti/tool_setting_controller.rb
@@ -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 .
+#
+
+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
\ No newline at end of file
diff --git a/app/models/lti/message_handler.rb b/app/models/lti/message_handler.rb
index 5a6e97ef188..f26f0715038 100644
--- a/app/models/lti/message_handler.rb
+++ b/app/models/lti/message_handler.rb
@@ -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
\ No newline at end of file
diff --git a/app/models/lti/resource_handler.rb b/app/models/lti/resource_handler.rb
index 246dcd55c1c..a1ba73a4422 100644
--- a/app/models/lti/resource_handler.rb
+++ b/app/models/lti/resource_handler.rb
@@ -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
diff --git a/app/models/lti/tool_consumer_profile_creator.rb b/app/models/lti/tool_consumer_profile_creator.rb
index c8f34f29161..7c74a357586 100644
--- a/app/models/lti/tool_consumer_profile_creator.rb
+++ b/app/models/lti/tool_consumer_profile_creator.rb
@@ -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
diff --git a/app/models/lti/tool_proxy.rb b/app/models/lti/tool_proxy.rb
index 3a6a4f0cfba..bd8d4957ccc 100644
--- a/app/models/lti/tool_proxy.rb
+++ b/app/models/lti/tool_proxy.rb
@@ -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
\ No newline at end of file
diff --git a/app/models/lti/tool_proxy_binding.rb b/app/models/lti/tool_proxy_binding.rb
index 6fa52f90923..d7c4a3aaee2 100644
--- a/app/models/lti/tool_proxy_binding.rb
+++ b/app/models/lti/tool_proxy_binding.rb
@@ -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
\ No newline at end of file
diff --git a/app/models/lti/tool_proxy_service.rb b/app/models/lti/tool_proxy_service.rb
index 26a5787e702..9d06b56c12f 100644
--- a/app/models/lti/tool_proxy_service.rb
+++ b/app/models/lti/tool_proxy_service.rb
@@ -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
diff --git a/app/models/lti/tool_setting.rb b/app/models/lti/tool_setting.rb
index a5b387d050e..009fc22a56b 100644
--- a/app/models/lti/tool_setting.rb
+++ b/app/models/lti/tool_setting.rb
@@ -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
\ No newline at end of file
diff --git a/config/routes.rb b/config/routes.rb
index 4c4449bb141..c3ef2bd8a7d 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -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)
diff --git a/db/migrate/20140825200057_add_lti_link_binding_association.rb b/db/migrate/20140825200057_add_lti_link_binding_association.rb
new file mode 100644
index 00000000000..b1deacb1242
--- /dev/null
+++ b/db/migrate/20140825200057_add_lti_link_binding_association.rb
@@ -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
diff --git a/app/models/lti/tool_link.rb b/lib/lti/api_service_helper.rb
similarity index 56%
rename from app/models/lti/tool_link.rb
rename to lib/lti/api_service_helper.rb
index b4f84d3b712..eb6605997d6 100644
--- a/app/models/lti/tool_link.rb
+++ b/lib/lti/api_service_helper.rb
@@ -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 .
#
+# 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
\ No newline at end of file
diff --git a/lib/lti/message_helper.rb b/lib/lti/message_helper.rb
index fed02885164..c5129b8338f 100644
--- a/lib/lti/message_helper.rb
+++ b/lib/lti/message_helper.rb
@@ -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
diff --git a/spec/apis/lti/tool_setting_api_spec.rb b/spec/apis/lti/tool_setting_api_spec.rb
new file mode 100644
index 00000000000..394d6eeaa54
--- /dev/null
+++ b/spec/apis/lti/tool_setting_api_spec.rb
@@ -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 .
+#
+
+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
\ No newline at end of file
diff --git a/spec/controllers/lti/message_controller_spec.rb b/spec/controllers/lti/message_controller_spec.rb
index 869b7fb075b..8463b0e7695 100644
--- a/spec/controllers/lti/message_controller_spec.rb
+++ b/spec/controllers/lti/message_controller_spec.rb
@@ -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
diff --git a/spec/models/lti/message_handler_spec.rb b/spec/models/lti/message_handler_spec.rb
index c61aa043f6a..e380101a144 100644
--- a/spec/models/lti/message_handler_spec.rb
+++ b/spec/models/lti/message_handler_spec.rb
@@ -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
diff --git a/spec/models/lti/tool_consumer_profile_creator_spec.rb b/spec/models/lti/tool_consumer_profile_creator_spec.rb
index 6a42c079f09..0226ea20175 100644
--- a/spec/models/lti/tool_consumer_profile_creator_spec.rb
+++ b/spec/models/lti/tool_consumer_profile_creator_spec.rb
@@ -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'
diff --git a/spec/models/lti/tool_link_spec.rb b/spec/models/lti/tool_link_spec.rb
deleted file mode 100644
index 1bac87af9d1..00000000000
--- a/spec/models/lti/tool_link_spec.rb
+++ /dev/null
@@ -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 .
-#
-
-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
\ No newline at end of file
diff --git a/spec/models/lti/tool_proxy_binding_spec.rb b/spec/models/lti/tool_proxy_binding_spec.rb
index 4ca50d437d2..2fab823e946 100644
--- a/spec/models/lti/tool_proxy_binding_spec.rb
+++ b/spec/models/lti/tool_proxy_binding_spec.rb
@@ -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
diff --git a/spec/models/lti/tool_proxy_spec.rb b/spec/models/lti/tool_proxy_spec.rb
index 67b0f87c1ad..a9c4e61dfce 100644
--- a/spec/models/lti/tool_proxy_spec.rb
+++ b/spec/models/lti/tool_proxy_spec.rb
@@ -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
diff --git a/spec/models/lti/tool_setting_spec.rb b/spec/models/lti/tool_setting_spec.rb
index 1f81175d56e..a08cbc2a9aa 100644
--- a/spec/models/lti/tool_setting_spec.rb
+++ b/spec/models/lti/tool_setting_spec.rb
@@ -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
\ No newline at end of file