add lti2 models and registration endpoints

fixes PLAT-522 PLAT-429 PLAT-545 PLAT-540 PLAT-498

test-plan
you can register an lti2 tool

Change-Id: If045b694213e1019f8a595b63a7e33ea4ac28e64
Reviewed-on: https://gerrit.instructure.com/37095
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Bracken Mosbacker <bracken@instructure.com>
Product-Review: Bracken Mosbacker <bracken@instructure.com>
QA-Review: Clare Strong <clare@instructure.com>
Reviewed-by: Brad Humphrey <brad@instructure.com>
This commit is contained in:
Nathan Mills 2014-05-23 16:30:13 -06:00
parent 0212b59646
commit a91d9c09d0
34 changed files with 1642 additions and 2 deletions

View File

@ -148,4 +148,5 @@ gem 'paginated_collection', :path => 'gems/paginated_collection'
gem 'twitter', :path => 'gems/twitter'
gem 'utf8_cleaner', :path => 'gems/utf8_cleaner'
gem 'workflow', :path => 'gems/workflow'
gem 'ims-lti', '~> 2.0.0.beta.1'

View File

@ -1075,9 +1075,10 @@ class ApplicationController < ActionController::Base
end
API_REQUEST_REGEX = %r{\A/api/v\d}
LTI_API_REQUEST_REGEX = %r{\A/api/lti/}
def api_request?
@api_request ||= !!request.path.match(API_REQUEST_REGEX)
@api_request ||= !!request.path.match(API_REQUEST_REGEX) || !!request.path.match(LTI_API_REQUEST_REGEX)
end
def session_loaded?

View File

@ -0,0 +1,45 @@
#
# 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 ToolConsumerProfileController < ApplicationController
before_filter :require_context
skip_before_filter :require_user
skip_before_filter :load_user
def show
profile = Lti::ToolConsumerProfileCreator.new(@account, tool_proxy_url).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
end
end

View File

@ -0,0 +1,107 @@
# 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 ToolProxyController < ApplicationController
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)
render json: tool_proxy.raw_data
else
render json: {error: 'unauthorized'}, status: :unauthorized
end
end
def create
secret = RegistrationRequestService.retrieve_registration_password(oauth_consumer_key)
if authorized_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",
"@type" => "ToolProxy",
"@id" => nil,
"tool_proxy_guid" => tool_proxy.guid
}
render json: json, status: :created
else
render json: {error: 'unauthorized'}, status: :unauthorized
end
end
def register
@lti_launch = Launch.new
@lti_launch.resource_url = params[:tool_consumer_url]
message = RegistrationRequestService.create_request(tool_consumer_profile_url, registration_return_url)
@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
@lti_launch.message_type = message.lti_message_type
render template: 'lti/framed_launch'
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(authorization_header(request))['oauth_consumer_key']
end
def tool_consumer_profile_url
tp_id = SecureRandom.uuid
case context
when Course
course_tool_consumer_profile_url(context, tp_id)
when Account
account_tool_consumer_profile_url(context, tp_id)
else
raise "Unsupported context"
end
end
def registration_return_url
case context
when Course
course_settings_url(context)
when Account
account_settings_url(context)
else
raise "Unsupported context"
end
end
def authorization_header(request)
if CANVAS_RAILS3
request.authorization
else
request.env['HTTP_AUTHORIZATION'] ||
request.env['X-HTTP_AUTHORIZATION'] ||
request.env['X_HTTP_AUTHORIZATION'] ||
request.env['REDIRECT_X_HTTP_AUTHORIZATION']
end
end
end
end

View File

@ -19,6 +19,8 @@
require 'oauth/client/action_pack'
class LtiApiController < ApplicationController
skip_before_filter :require_user
skip_before_filter :load_user
skip_before_filter :verify_authenticity_token
# this API endpoint passes all the existing tests for the LTI v1.1 outcome service specification

5
app/models/lti.rb Normal file
View File

@ -0,0 +1,5 @@
module Lti
def self.table_name_prefix
'lti_'
end
end

18
app/models/lti/launch.rb Normal file
View File

@ -0,0 +1,18 @@
module Lti
class Launch
attr_writer :analytics_id
attr_accessor :link_text, :resource_url, :params, :launch_type, :message_type
def resource_path
url = URI.parse(resource_url)
url.path.empty? ? '/' : url.path
end
def analytics_id
@analytics_id || URI.parse(resource_url).host || 'unknown'
end
end
end

View File

@ -0,0 +1,32 @@
#
# 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 MessageHandler< ActiveRecord::Base
attr_accessible :message_type, :launch_path, :capabilities, :parameters, :resource
belongs_to :resource, class_name: "Lti::ResourceHandler", :foreign_key => :resource_handler_id
serialize :capabilities
serialize :parameters
validates_presence_of :message_type, :resource, :launch_path
end
end

View File

@ -0,0 +1,32 @@
#
# 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 ProductFamily < ActiveRecord::Base
attr_accessible :vendor_code, :product_code, :vendor_name, :vendor_description, :website, :vendor_email, :root_account
belongs_to :root_account, class_name: 'Account'
has_many :tool_proxies, class_name: "Lti::ToolProxy"
validates_presence_of :vendor_code, :product_code, :vendor_name, :root_account
validates_uniqueness_of :product_code, scope: [:vendor_code, :root_account_id]
end
end

View File

@ -0,0 +1,33 @@
module Lti
class RegistrationRequestService
def self.retrieve_registration_password(guid)
Rails.cache.read(req_cache_key(guid))
end
def self.create_request(tc_profile_url, return_url)
registration_request = IMS::LTI::Models::Messages::RegistrationRequest.new(
lti_version: IMS::LTI::Models::LTIModel::LTI_VERSION_2P0,
launch_presentation_document_target: IMS::LTI::Models::Messages::Message::LAUNCH_TARGET_IFRAME,
tc_profile_url: tc_profile_url,
launch_presentation_return_url: return_url
)
reg_key, reg_password = registration_request.generate_key_and_password
cache_registration(reg_key, reg_password)
registration_request
end
private
def self.cache_registration(reg_key, reg_password)
Rails.cache.write(req_cache_key(reg_key), reg_password, :expires_in => 1.hour)
end
def self.req_cache_key(reg_key)
['lti_registration_request', reg_key].cache_key
end
end
end

View File

@ -0,0 +1,33 @@
#
# 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 ResourceHandler < ActiveRecord::Base
attr_accessible :resource_type_code, :placements, :name, :description, :icon_info, :tool_proxy
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'
serialize :icon_info
validates_presence_of :resource_type_code, :name, :tool_proxy
end
end

View File

@ -0,0 +1,28 @@
#
# 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 ResourcePlacement < ActiveRecord::Base
attr_accessible :placement, :resource
belongs_to :resource, class_name: 'Lti::ResourceHandler'
validates_presence_of :resource, :placement
end
end

View File

@ -0,0 +1,66 @@
module Lti
class ToolConsumerProfileCreator
def initialize(account, tp_registration_url)
@root_account = account.root_account
@tp_registration_url = tp_registration_url
end
def create
profile = IMS::LTI::Models::ToolConsumerProfile.new
profile.lti_version = IMS::LTI::Models::ToolConsumerProfile::LTI_VERSION_2P0
profile.product_instance = create_product_instance
profile.service_offered = [create_tp_registration_service]
profile.capability_offered = capabilities
profile
end
private
def create_product_instance
product_instance = IMS::LTI::Models::ProductInstance.new
product_instance.guid = @root_account.lti_guid
product_instance.product_info = create_product_info
product_instance
end
def create_product_info
product_info = IMS::LTI::Models::ProductInfo.new
product_info.create_product_name('Canvas by Instructure')
product_info.product_version = 'none'
product_info.product_family = create_product_family
product_info
end
def create_product_family
product_family = IMS::LTI::Models::ProductFamily.new
product_family.code = 'canvas'
product_family.vendor = create_vendor
product_family
end
def create_vendor
vendor = IMS::LTI::Models::Vendor.new
vendor.code = 'https://instructure.com'
vendor.create_vendor_name('Instructure')
vendor.timestamp = Time.parse('2008-03-27 00:00:00 -0600')
vendor
end
def create_tp_registration_service
reg_srv = IMS::LTI::Models::RestService.new
reg_srv.id = 'tcp:ToolProxy.collection'
reg_srv.endpoint = @tp_registration_url
reg_srv.type = 'RestService'
reg_srv.format = ['application/vnd.ims.lti.v2.toolproxy+json']
reg_srv.action = 'POST'
reg_srv
end
def capabilities
['basic-lti-launch-request']
end
end
end

View File

@ -0,0 +1,35 @@
#
# 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 ToolProxy < ActiveRecord::Base
attr_accessible :shared_secret, :guid, :product_version, :lti_version, :product_family, :root_account, :workflow_state, :raw_data
has_many :bindings, class_name: 'Lti::ToolProxyBinding'
has_many :resources, class_name: 'Lti::ResourceHandler'
belongs_to :root_account, class_name: 'Account'
belongs_to :product_family, class_name: 'Lti::ProductFamily'
serialize :raw_data
validates_presence_of :shared_secret, :guid, :product_version, :lti_version, :product_family_id, :root_account_id, :workflow_state, :raw_data
validates_uniqueness_of :guid
end
end

View File

@ -0,0 +1,32 @@
# 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 ToolProxyBinding < ActiveRecord::Base
attr_accessible :context, :tool_proxy
belongs_to :tool_proxy, class_name: 'Lti::ToolProxy'
validates_inclusion_of :context_type, :allow_nil => true, :in => ['Course', 'Account']
belongs_to :context, :polymorphic => true
validates_presence_of :tool_proxy, :context
after_save :touch_context
end
end

View File

@ -0,0 +1,113 @@
#
# 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 ToolProxyService
def process_tool_proxy_json(json, context, guid)
tp = IMS::LTI::Models::ToolProxy.new.from_json(json)
tp.tool_proxy_guid = guid
tool_proxy = nil
ToolProxy.transaction do
product_family = create_product_family(tp, context.root_account)
tool_proxy = create_tool_proxy(tp, context.root_account, product_family)
process_resources(tp, tool_proxy)
create_proxy_binding(tool_proxy, context)
end
tool_proxy
end
private
def create_tool_proxy(tp, account, product_family)
tool_proxy = ToolProxy.new
tool_proxy.product_family = product_family
tool_proxy.guid = tp.tool_proxy_guid
tool_proxy.shared_secret = tp.security_contract.shared_secret
tool_proxy.product_version = tp.tool_profile.product_instance.product_info.product_version
tool_proxy.lti_version = tp.tool_profile.lti_version
tool_proxy.root_account = account.root_account
tool_proxy.workflow_state = 'disabled'
tool_proxy.raw_data = tp.as_json
tool_proxy.save!
tool_proxy
end
def create_product_family(tp, account)
vendor_code = tp.tool_profile.product_instance.product_info.product_family.vendor.code
product_code = tp.tool_profile.product_instance.product_info.product_family.code
unless product_family = ProductFamily.where(vendor_code: vendor_code, product_code: product_code).first
product_family = ProductFamily.new
product_family.vendor_code = vendor_code
product_family.product_code = product_code
product_family.vendor_name = tp.tool_profile.product_instance.product_info.product_family.vendor.default_name
product_family.vendor_description = tp.tool_profile.product_instance.product_info.product_family.vendor.default_description
product_family.website = tp.tool_profile.product_instance.product_info.product_family.vendor.website
product_family.vendor_email = tp.tool_profile.product_instance.product_info.product_family.vendor.contact.email
product_family.root_account = account.root_account
product_family.save!
end
product_family
end
def create_message_handler(mh, base_path, resource)
message_handler = MessageHandler.new
message_handler.message_type = mh.message_type
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.save!
message_handler
end
def create_resource_handler(rh, tool_proxy)
resource_handler = ResourceHandler.new
resource_handler.resource_type_code = rh.resource_type.code
resource_handler.name = rh.default_name
resource_handler.description = rh.default_description
resource_handler.icon_info = create_json(rh.icon_info)
resource_handler.tool_proxy = tool_proxy
resource_handler.save!
resource_handler
end
def create_proxy_binding(tool_proxy, context)
binding = ToolProxyBinding.new
binding.context = context
binding.tool_proxy = tool_proxy
binding.save!
binding
end
def process_resources(tp, tool_proxy)
tp.tool_profile.resource_handler.each do |rh|
resource = create_resource_handler(rh, tool_proxy)
rh.message.each do |mh|
create_message_handler(mh, tp.tool_profile.base_message_url, resource)
end
end
end
private
def create_json(obj)
obj.kind_of?(Array) ? obj.map(&:as_json) : obj.as_json
end
end
end

View File

@ -0,0 +1,43 @@
<% content_for :page_title do %>
<%= @lti_launch.link_text %>
<% end %>
<% js_bundle 'legacy/external_tools_tool_show' %>
<% if @lti_launch.resource_url.start_with?('http://') %>
<div class="alert alert-error" id="insecure_content_msg">
<p>You are trying to launch insecure content from within a secure site (canvas). Some internet browsers may
prevent this content from loading.</p>
<p id="load_failure" class="hide">It looks like your content might not load. You can use this button to try
launching it in a new tab.</p>
</div>
<% end %>
<form action="<%= @lti_launch.resource_url %>"
class="hide"
method="POST" target=tool_content
id="tool_form"
data-tool-launch-type="<%= @lti_launch.launch_type %>"
data-tool-id="<%= @lti_launch.analytics_id %>"
data-tool-path="<%= @lti_launch.resource_path %>"
data-message-type="<%= @lti_launch.message_type %>">
<% @lti_launch.params.each do |key, value| %>
<%= hidden_field_tag key, value %>
<% end %>
<div style="margin-bottom: 20px;">
<div class="load_tab">
<%= t :new_tab, "This tool needs to be loaded in a new browser window" %>
<div style="margin: 10px 0;">
<button class="btn" type="submit"
data-expired_message="<%= t :new_tab_expired, "The session for this tool has expired. Please reload the page to access the tool again" %>">
<%= t :load_tool_new_tab_button, "Load %{tool} in a new window", :tool => @lti_launch.link_text %>
</button>
</div>
</div>
<div class="tab_loaded" style="display: none;">
<%= t :new_tab_loaded, "This tool was successfully loaded in a new browser window. Reload the page to access the tool again." %>
</div>
</div>
</form>
<%= iframe("about:blank", :name => 'tool_content', :id => 'tool_content', :width => '100%', :height => '400') %>

View File

@ -0,0 +1 @@
<%= render 'lti/lti_message' %>

View File

@ -289,6 +289,7 @@ routes.draw do
match 'homework_submission' => 'external_tools#homework_submission', :as => :homework_submission
match 'finished' => 'external_tools#finished', :as => :finished
collection do
get :tool_proxy_registration, controller: 'lti/tool_proxy', action: 'register', :as => :tool_proxy_registration
get :retrieve
get :homework_submissions
end
@ -551,6 +552,9 @@ routes.draw do
resources :external_tools do
match 'finished' => 'external_tools#finished', :as => :finished
match 'resource_selection' => 'external_tools#resource_selection', :as => :resource_selection
collection do
get :tool_proxy_registration, controller: 'lti/tool_proxy', action: 'register', :as => :tool_proxy_registration
end
end
match 'outcomes/users/:user_id' => 'outcomes#user_outcome_results', :as => :user_outcomes_results
@ -1567,5 +1571,14 @@ routes.draw do
post "tools/:tool_id/ext_grade_passback", :controller => :lti_api, :action => :legacy_grade_passback, :path_name => "blti_legacy_grade_passback_api"
end
ApiRouteSet.draw(self, "/api/lti") do
['course', 'account'].each do |context|
prefix = "#{context}s/:#{context}_id"
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
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)
end

View File

@ -0,0 +1,88 @@
class AddLti2Tables < ActiveRecord::Migration
tag :predeploy
def self.up
create_table :lti_product_families do |t|
t.string :vendor_code, null: false
t.string :product_code, null: false
t.string :vendor_name, null: false
t.text :vendor_description
t.string :website
t.string :vendor_email
t.integer :root_account_id, limit: 8, null: false
t.timestamps
end
create_table :lti_message_handlers do |t|
t.string :message_type, null: false
t.string :launch_path, null: false
t.text :capabilities
t.text :parameters
t.integer :resource_handler_id, limit: 8, null: false
t.timestamps
end
create_table :lti_resource_handlers do |t|
t.string :resource_type_code, null: false
t.string :placements
t.string :name, null: false
t.text :description
t.text :icon_info
t.integer :tool_proxy_id, limit: 8, null: false
t.timestamps
end
create_table :lti_resource_placements do |t|
t.integer :resource_handler_id, limit: 8, null: false
t.string :placement, null: false
t.timestamps
end
create_table :lti_tool_proxies do |t|
t.string :shared_secret, null: false
t.string :guid, null: false
t.string :product_version, null: false
t.string :lti_version, null: false
t.integer :product_family_id, limit: 8, null: false
t.integer :root_account_id, limit: 8, null: false
t.string :workflow_state, null: false
t.text :raw_data, null: false
t.timestamps
end
create_table :lti_tool_proxy_bindings do |t|
t.integer :context_id, limit: 8, null: false
t.string :context_type, null: false
t.integer :tool_proxy_id, limit:8, null: false
t.timestamps
end
add_index :lti_product_families, [:root_account_id, :vendor_code, :product_code], name: 'index_lti_product_families_on_root_account_vend_code_prod_code', unique: true
add_index :lti_message_handlers, [:resource_handler_id, :message_type], name: 'index_lti_message_handlers_on_resource_handler_and_type', unique: true
add_index :lti_resource_handlers, [:tool_proxy_id, :resource_type_code], name: 'index_lti_resource_handlers_on_tool_proxy_and_type_code', unique: true
add_index :lti_resource_placements, [:placement, :resource_handler_id], name: 'index_lti_resource_placements_on_placement_and_handler', unique: true
add_index :lti_tool_proxies, [:root_account_id, :product_family_id, :product_version], name: 'index_lti_tool_proxies_on_root_account_prod_fam_and_prod_ver', unique: true
add_index :lti_tool_proxy_bindings, [:context_id, :context_type, :tool_proxy_id], name: 'index_lti_tool_proxy_bindings_on_context_and_tool_proxy', unique: true
add_index :lti_tool_proxies, [:guid]
add_foreign_key :lti_product_families, :accounts, column: :root_account_id
add_foreign_key :lti_message_handlers, :lti_resource_handlers, column: :resource_handler_id
add_foreign_key :lti_resource_handlers, :lti_tool_proxies, column: :tool_proxy_id
add_foreign_key :lti_resource_placements, :lti_resource_handlers, column: :resource_handler_id
add_foreign_key :lti_tool_proxies, :lti_product_families, column: :product_family_id
add_foreign_key :lti_tool_proxies, :accounts, column: :root_account_id
add_foreign_key :lti_tool_proxy_bindings, :lti_tool_proxies, column: :tool_proxy_id
end
def self.down
drop_table :lti_tool_proxy_bindings
drop_table :lti_message_handlers
drop_table :lti_resource_placements
drop_table :lti_resource_handlers
drop_table :lti_tool_proxies
drop_table :lti_product_families
end
end

View File

@ -77,7 +77,8 @@ switch($toolForm.data('tool-launch-type')){
//Google analytics tracking code
var toolName = $toolForm.data('tool-id') || "unknown";
var toolPath = $toolForm.data('tool-path');
$.trackEvent('tool_launch', toolName, toolPath);
var messageType = $toolForm.data('message-type') || 'tool_launch';
$.trackEvent(messageType, toolName, toolPath);
//Iframe resize handler
$(document).ready(function() {

View File

@ -0,0 +1,44 @@
#
# 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 ToolConsumerProfileController, type: :request do
describe "GET 'tool_consumer_profile'" do
let(:account) { Account.create! }
it 'renders "application/json"' do
tool_consumer_profile_id = 'a_made_up_id'
get "/api/lti/accounts/#{account.id}/tool_consumer_profile/#{tool_consumer_profile_id}", tool_consumer_profile_id: tool_consumer_profile_id, account_id: account.id
response.content_type.should == 'application/json'
end
it 'returns the consumer profile JSON' do
tool_consumer_profile_id = 'a_made_up_id'
get "/api/lti/accounts/#{account.id}/tool_consumer_profile/#{tool_consumer_profile_id}", tool_consumer_profile_id: tool_consumer_profile_id, account_id: account.id
profile = IMS::LTI::Models::ToolConsumerProfile.new.from_json(response.body)
profile.type.should == 'ToolConsumerProfile'
end
end
end
end

View File

@ -0,0 +1,68 @@
#
# 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 ToolProxyController, 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(
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
before(:each) do
OAuth::Signature.stubs(:build).returns(mock(verify:true))
OAuth::Helper.stubs(:parse_header).returns({'oauth_consumer_key' => 'key'})
end
describe "Get #show" do
it 'the tool proxy raw data' do
get "api/lti/tool_proxy/#{tool_proxy.guid}", tool_proxy_guid: tool_proxy.guid
JSON.parse(body).should == tool_proxy.raw_data
end
end
describe "POST #create" do
it 'returns a tool_proxy id object' do
course_with_teacher_logged_in(:active_all => true)
tool_proxy_fixture = File.read(File.join(Rails.root, 'spec', 'fixtures', 'lti', 'tool_proxy.json'))
json = JSON.parse(tool_proxy_fixture)
json[:format] = 'json'
json[:account_id] = @course.account.id
headers = { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' }
response = post "/api/lti/accounts/#{@course.account.id}/tool_proxy.json", tool_proxy_fixture, headers
response.should == 201
JSON.parse(body).keys.should =~ ["@context", "@type", "@id", "tool_proxy_guid"]
end
end
end
end

View File

@ -0,0 +1,64 @@
#
# 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')
module Lti
describe ToolProxyController do
describe "GET #register" do
context 'course' do
it 'initiates a tool proxy registration request' do
course_with_teacher_logged_in(:active_all => true)
get 'register', course_id: @course.id, tool_consumer_url: 'http://tool.consumer.url'
lti_launch = assigns[:lti_launch]
lti_launch.resource_url.should == 'http://tool.consumer.url'
launch_params = lti_launch.params
launch_params['lti_message_type'].should == 'ToolProxyRegistrationRequest'
launch_params['lti_version'].should == 'LTI-2p0'
launch_params['launch_presentation_document_target'].should == 'iframe'
launch_params['reg_key'].should_not be_empty
launch_params['reg_password'].should_not be_empty
account_tp_url_stub = course_tool_consumer_profile_url(@course, 'abc123').gsub('abc123', '')
launch_params['tc_profile_url'].should include(account_tp_url_stub)
end
end
context 'account' do
it 'initiates a tool proxy registration request' do
course_with_teacher_logged_in(:active_all => true)
get 'register', account_id: @course.root_account.id, tool_consumer_url: 'http://tool.consumer.url'
lti_launch = assigns[:lti_launch]
lti_launch.resource_url.should == 'http://tool.consumer.url'
launch_params = lti_launch.params
launch_params['lti_message_type'].should == 'ToolProxyRegistrationRequest'
launch_params['lti_version'].should == 'LTI-2p0'
launch_params['launch_presentation_document_target'].should == 'iframe'
launch_params['reg_key'].should_not be_empty
launch_params['reg_password'].should_not be_empty
account_tp_url_stub = account_tool_consumer_profile_url(@course.root_account, 'abc123').gsub('abc123', '')
launch_params['tc_profile_url'].should include(account_tp_url_stub)
end
end
end
end
end

176
spec/fixtures/lti/tool_proxy.json vendored Normal file
View File

@ -0,0 +1,176 @@
{
"@context": [
"http://purl.imsglobal.org/ctx/lti/v2/ToolProxy",
"http://purl.org/blackboard/ctx/v1/iconStyle"
],
"@type": "ToolProxy",
"@id": "http://lms.example.com/ToolProxy/869e5ce5-214c-4e85-86c6-b99e8458a592",
"lti_version": "LTI-2p0",
"tool_proxy_guid": "869e5ce5-214c-4e85-86c6-b99e8458a592",
"tool_consumer_profile": "http://lms.example.com/profile/b6ffa601-ce1d-4549-9ccf-145670a964d4",
"tool_profile": {
"lti_version": "LTI-2p0",
"product_instance": {
"guid": "fd75124a-140e-470f-944c-114d2d92bb40",
"product_info": {
"product_name": {
"default_value": "Acme Assessments",
"key": "tool.name"
},
"description": {
"default_value": "Acme Assessments provide an interactive test format.",
"key": "tool.description"
},
"product_version": "10.3",
"technical_description": {
"default_value": "Support provided for all LTI 1 extensions as well as LTI 2",
"key": "tool.technical"
},
"product_family": {
"@id": "http://toolprovider.example.com/vendor/acme.com/product/assessment-tool",
"code": "assessment-tool",
"vendor": {
"code": "acme.com",
"vendor_name": {
"default_value": "Acme",
"key": "tool.vendor.name"
},
"description": {
"default_value": "Acme is a leading provider of interactive tools for education",
"key": "tool.vendor.description"
},
"website": "http://acme.example.com",
"timestamp": "2012-04-05T09:08:16-04:00",
"contact": {
"email": "info@example.com"
}
}
}
},
"support": {
"email": "helpdesk@example.com"
},
"service_provider": {
"guid": "18e7ea50-3d6d-4f6b-aff2-ed3ab577716c",
"service_provider_name": {
"default_value": "Acme Hosting",
"key": "service_provider.name"
},
"description": {
"default_value": "Provider of high performance managed hosting environments",
"key": "service_provider.description"
},
"support": {
"email": "support@acme-hosting.example.com"
},
"timestamp": "2012-04-05T09:08:16-04:00"
},
"service_owner": {
"service_owner_name": {
"default_value": "Acme Institution",
"key": "service_owner.name"
},
"description": {
"default_value": "Provider of high quality education",
"key": "service_owner.description"
},
"timestamp": "2012-04-05T09:08:16-04:00"
}
},
"base_url_choice": [
{
"default_base_url": "http://acme.example.com/",
"secure_base_url": "https://acme.example.com/",
"selector": {
"applies_to": ["IconEndpoint", "MessageHandler"]
}
}
],
"resource_handler": [
{
"resource_type": {
"code": "asmt"
},
"resource_name": {
"default_value": "Acme Assessment",
"key": "assessment.resource.name"
},
"description": {
"default_value": "An interactive assessment using the Acme scale.",
"key": "assessment.resource.description"
},
"message": [
{
"message_type": "basic-lti-launch-request",
"path": "handler/launchRequest",
"enabled_capability": [
"Result.autocreate"
],
"parameter": [
{
"name": "result_url",
"variable": "Result.url"
},
{
"name": "discipline",
"fixed": "chemistry"
}
]
}
],
"icon_info": [
{
"default_location": {
"path": "images/bb/en/icon.png"
},
"key": "iconStyle.default.path"
},
{
"icon_style": ["BbListElementIcon"],
"default_location": {
"path": "images/bb/en/listElement.png"
},
"key": "iconStyle.bb.listElement.path"
},
{
"icon_style": ["BbPushButtonIcon"],
"default_location": {
"path": "images/bb/en/pushButton.png"
},
"key": "iconStyle.bb.pushButton.path"
}
]
}
]
},
"custom": {
"customerId": "394892759526"
},
"security_contract": {
"shared_secret": "ThisIsASecret!",
"tool_service": [
{
"@type": "RestServiceProfile",
"service": "http://lms.example.com/profile/b6ffa601-ce1d-4549-9ccf-145670a964d4#ToolProxy.collection",
"action": ["POST"]
},
{
"@type": "RestServiceProfile",
"service": "http://lms.example.com/profile/b6ffa601-ce1d-4549-9ccf-145670a964d4#ToolProxy.item",
"action": ["GET", "PUT"]
},
{
"@type": "RestServiceProfile",
"service": "http://lms.example.com/profile/b6ffa601-ce1d-4549-9ccf-145670a964d4#Result.item",
"action": ["GET", "PUT"]
}
],
"end_user_service": [
{
"@type": "RestServiceProfile",
"service": "http://lms.example.com/profile/b6ffa601-ce1d-4549-9ccf-145670a964d4#Result.item",
"action": ["PUT"]
}
]
}
}

View File

@ -0,0 +1,53 @@
#
# 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 MessageHandler do
describe 'validations' do
before(:each) do
subject.message_type = 'message_type'
subject.launch_path = 'launch_path'
subject.resource = ResourceHandler.new
end
it 'requires the message type' do
subject.message_type = nil
subject.save
subject.errors.first.should == [:message_type, "can't be blank"]
end
it 'requires the launch path' do
subject.launch_path = nil
subject.save
subject.errors.first.should == [:launch_path, "can't be blank"]
end
it 'requires a resource_handler' do
subject.resource = nil
subject.save
subject.errors.first.should == [:resource, "can't be blank"]
end
end
end
end

View File

@ -0,0 +1,61 @@
#
# 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 ProductFamily do
let(:account){Account.new}
describe 'validations' do
before(:each) do
subject.vendor_code = 'vendor_code'
subject.product_code = 'product_code'
subject.vendor_name = 'vendor_name'
subject.root_account_id = account
end
it 'requires a vendor_code' do
subject.vendor_code = nil
subject.save
subject.errors.first.should == [:vendor_code, "can't be blank"]
end
it 'requires a product_code' do
subject.product_code = nil
subject.save
subject.errors.first.should == [:product_code, "can't be blank"]
end
it 'requires a vendor_name' do
subject.vendor_name = nil
subject.save
subject.errors.first.should == [:vendor_name, "can't be blank"]
end
it 'requires a root_account' do
subject.root_account = nil
subject.save
subject.errors.first.should == [:root_account, "can't be blank"]
end
end
end
end

View File

@ -0,0 +1,34 @@
require 'spec_helper'
module Lti
describe RegistrationRequestService do
describe '#create' do
it 'creates a RegistrationRequest' do
enable_cache do
reg_request = described_class.create_request('profile_url', 'return_url')
reg_request.lti_version.should == 'LTI-2p0'
reg_request.launch_presentation_document_target.should == 'iframe'
reg_request.tc_profile_url.should == 'profile_url'
end
end
it 'writes the reg password to the cache' do
enable_cache do
IMS::LTI::Models::Messages::RegistrationRequest.any_instance.expects(:generate_key_and_password).
returns(['key', 'password'])
Rails.cache.expects(:write).with('lti_registration_request/key', 'password', anything)
described_class.create_request('profile_url', 'return_url')
end
end
it 'generates the cache key' do
described_class.req_cache_key('reg_key').should == 'lti_registration_request/reg_key'
end
end
end
end

View File

@ -0,0 +1,47 @@
#
# 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 ResourceHandler do
describe 'validations' do
before(:each) do
subject.resource_type_code = 'code'
subject.name = 'name'
subject.tool_proxy = ToolProxy.new
end
it 'requires the name' do
subject.name = nil
subject.save
subject.errors.first.should == [:name, "can't be blank"]
end
it 'requires a tool proxy' do
subject.tool_proxy = nil
subject.save
subject.errors.first.should == [:tool_proxy, "can't be blank"]
end
end
end
end

View File

@ -0,0 +1,34 @@
#
# 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 ResourcePlacement do
describe 'validations' do
it 'requires a resource' do
subject.save
subject.errors.first.should == [:resource, "can't be blank"]
end
end
end
end

View File

@ -0,0 +1,65 @@
require 'spec_helper'
module Lti
describe ToolConsumerProfileCreator do
let(:root_account) { mock('root account', lti_guid: 'my_guid') }
let(:account) { mock('account', root_account: root_account) }
# 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, 'http://tool-consumer.com/tp/reg') }
describe '#create' do
it 'creates the tool consumer profile' do
profile = subject.create
profile.lti_version.should == 'LTI-2p0'
profile.product_instance.should be_an_instance_of IMS::LTI::Models::ProductInstance
end
it 'creates the product instance' do
product_instance = subject.create.product_instance
product_instance.guid.should == 'my_guid'
product_instance.product_info.should be_an IMS::LTI::Models::ProductInfo
end
it 'creates the product info' do
product_info = subject.create.product_instance.product_info
product_info.product_name.default_value.should == 'Canvas by Instructure'
product_info.product_version.should == 'none'
product_info.product_family.should be_a IMS::LTI::Models::ProductFamily
end
it 'creates the product family' do
product_family = subject.create.product_instance.product_info.product_family
product_family.code.should == 'canvas'
product_family.vendor.should be_a IMS::LTI::Models::Vendor
end
it 'creates the vendor' do
vendor = subject.create.product_instance.product_info.product_family.vendor
vendor.code.should == 'https://instructure.com'
vendor.vendor_name.default_value.should == 'Instructure'
vendor.vendor_name.key.should == 'vendor.name'
vendor.timestamp.to_i.should == Time.parse('2008-03-27 00:00:00 -0600').to_i
end
it 'creates the registration service' do
profile = subject.create
reg_srv = profile.service_offered.find {|srv| srv.id == 'tcp:ToolProxy.collection'}
reg_srv.endpoint.should == 'http://tool-consumer.com/tp/reg'
reg_srv.type.should == 'RestService'
reg_srv.format.should == ["application/vnd.ims.lti.v2.toolproxy+json"]
reg_srv.action.should include 'POST'
end
it 'add the basic_launch capability' do
subject.create.capability_offered.should include 'basic-lti-launch-request'
end
end
end
end

View File

@ -0,0 +1,49 @@
#
# 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 ToolProxyBinding do
let(:account) {Account.new}
let(:tool_proxy) {ToolProxy.new}
describe 'validations' do
before(:each) do
subject.context = account
subject.tool_proxy = tool_proxy
end
it 'requires a context' do
subject.context = nil
subject.save
subject.errors.first.should == [:context, "can't be blank"]
end
it 'requires a tool_proxy' do
subject.tool_proxy = nil
subject.save
subject.errors.first.should == [:tool_proxy, "can't be blank"]
end
end
end
end

View File

@ -0,0 +1,108 @@
#
# 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 ToolProxyService do
describe '#process_tool_proxy_json' do
let(:tool_proxy_fixture){File.read(File.join(Rails.root, 'spec', 'fixtures', 'lti', 'tool_proxy.json'))}
let(:tool_proxy_guid){'guid'}
let(:account){Account.new}
it "creates the product_family if it doesn't exist" do
tool_proxy = subject.process_tool_proxy_json(tool_proxy_fixture, account, tool_proxy_guid)
pf = tool_proxy.product_family
pf.vendor_code.should == 'acme.com'
pf.product_code.should == 'assessment-tool'
pf.vendor_name.should == 'Acme'
pf.website.should == 'http://acme.example.com'
pf.vendor_description.should == 'Acme is a leading provider of interactive tools for education'
pf.vendor_email.should == 'info@example.com'
pf.root_account.should == account
end
it "uses an exisiting product family if it can" do
pf = ProductFamily.new
pf.vendor_code = 'acme.com'
pf.product_code = 'assessment-tool'
pf.vendor_name = 'Acme'
pf.root_account = account.root_account
pf.save!
tool_proxy = subject.process_tool_proxy_json(tool_proxy_fixture, account, tool_proxy_guid)
tool_proxy.product_family.id.should == pf.id
end
it "creates the resource handler" do
tool_proxy = subject.process_tool_proxy_json(tool_proxy_fixture, account, tool_proxy_guid)
rh = tool_proxy.resources.first
rh.resource_type_code.should == 'asmt'
rh.name.should == 'Acme Assessment'
rh.description.should == 'An interactive assessment using the Acme scale.'
rh.icon_info.should == [
{
'default_location' => {'path' => 'images/bb/en/icon.png'},
'key' => 'iconStyle.default.path'
},
{
'icon_style' => ['BbListElementIcon'],
'default_location' => {'path' => 'images/bb/en/listElement.png'},
'key' => 'iconStyle.bb.listElement.path'
},
{
'icon_style' => ['BbPushButtonIcon'],
'default_location' => {'path' => 'images/bb/en/pushButton.png'},
'key' => 'iconStyle.bb.pushButton.path'
}
]
end
it "creates the message_handlers" do
tool_proxy = subject.process_tool_proxy_json(tool_proxy_fixture, account, tool_proxy_guid)
mh = tool_proxy.resources.first.message_handlers.first
mh.message_type.should == 'basic-lti-launch-request'
mh.launch_path.should == 'https://acme.example.com/handler/launchRequest'
mh.capabilities.should == [ "Result.autocreate" ]
mh.parameters.should == [{'name' => 'result_url', 'variable' => 'Result.url'}, {'name' => 'discipline', 'fixed' => 'chemistry'}]
end
it 'creates a tool proxy biding' do
tool_proxy = subject.process_tool_proxy_json(tool_proxy_fixture, account, tool_proxy_guid)
tool_proxy.bindings.count.should == 1
binding = tool_proxy.bindings.first
binding.context.should == account
end
it 'creates a tool_proxy' do
SecureRandom.stubs(:uuid).returns('my_uuid')
tool_proxy = subject.process_tool_proxy_json(tool_proxy_fixture, account, tool_proxy_guid)
tool_proxy.shared_secret.should == 'ThisIsASecret!'
tool_proxy.guid.should == tool_proxy_guid
tool_proxy.product_version.should == '10.3'
tool_proxy.lti_version.should == 'LTI-2p0'
tool_proxy.root_account.should == account
tool_proxy.workflow_state.should == 'disabled'
end
end
end
end

View File

@ -0,0 +1,108 @@
#
# 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 ToolProxy do
let (:account) {Account.new}
let (:product_family) {ProductFamily.create(vendor_code: '123', product_code:'abc', vendor_name:'acme', root_account:account)}
let (:resource_handler) {ResourceHandler.new}
describe 'validations' do
before(:each) do
subject.shared_secret = 'shared_secret'
subject.guid = 'guid'
subject.product_version = '1.0beta'
subject.lti_version = 'LTI-2p0'
subject.product_family = product_family
subject.root_account = account
subject.workflow_state = 'active'
subject.raw_data = 'some raw data'
end
it 'requires a shared_secret' do
subject.shared_secret = nil
subject.save
error = subject.errors.find {|e| e == [:shared_secret, "can't be blank"]}
error.should_not == nil
end
it 'requires a guid' do
subject.guid = nil
subject.save
error = subject.errors.find {|e| e == [:guid, "can't be blank"]}
error.should_not == nil
end
it 'must have a unique guid' do
tool_proxy = described_class.new
tool_proxy.shared_secret = 'foo'
tool_proxy.guid = 'guid'
tool_proxy.product_version = '2.0_beta'
tool_proxy.lti_version = 'LTI-2p0'
tool_proxy.product_family = product_family
tool_proxy.root_account = account
tool_proxy.workflow_state = 'active'
tool_proxy.raw_data = 'raw_data'
tool_proxy.save
subject.save
subject.errors[:guid].should include("has already been taken")
end
it 'requires a product_version' do
subject.product_version = nil
subject.save
subject.errors[:product_version].should include("can't be blank")
end
it 'requires a lti_version' do
subject.lti_version = nil
subject.save
subject.errors[:lti_version].should include("can't be blank")
end
it 'requires a product_family' do
subject.product_family = nil
subject.save
error = subject.errors.find {|e| e == [:product_family, "can't be blank"]}
end
it 'requires a root_account' do
subject.root_account = nil
subject.save
subject.errors[:root_account_id].should include("can't be blank")
end
it 'require a workflow_state' do
subject.workflow_state = nil
subject.save
subject.errors[:workflow_state].should include("can't be blank")
end
it 'requires raw_data' do
subject.raw_data = nil
subject.save
subject.errors[:raw_data].should include("can't be blank")
end
end
end
end