WIP basic_lti_outbound gem extraction

Change-Id: Icb8f78bdd5f41e4eae18b30d1a4cfc7dd9942057
Reviewed-on: https://gerrit.instructure.com/28722
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Raphael Weiner <rweiner@pivotallabs.com>
Product-Review: Brad Humphrey <brad@instructure.com>
QA-Review: Brad Humphrey <brad@instructure.com>
This commit is contained in:
Stephan Hagemann 2014-01-21 09:55:21 -07:00 committed by Brad Humphrey
parent 701204a560
commit ea7e45c793
34 changed files with 1636 additions and 0 deletions

17
gems/basic_lti_outbound/.gitignore vendored Normal file
View File

@ -0,0 +1,17 @@
*.gem
*.rbc
.bundle
.config
.yardoc
Gemfile.lock
InstalledFiles
_yardoc
coverage
doc/
lib/bundler/man
pkg
rdoc
spec/reports
test/tmp
test/version_tmp
tmp

View File

@ -0,0 +1,2 @@
--color
--format progress

View File

@ -0,0 +1,4 @@
source 'https://rubygems.org'
# Specify your gem's dependencies in basic_lti_outbound.gemspec
gemspec

View File

@ -0,0 +1,22 @@
Copyright (c) 2014 Brian Palmer
MIT License
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,29 @@
# BasicLtiOutbound
TODO: Write a gem description
## Installation
Add this line to your application's Gemfile:
gem 'basic_lti'
And then execute:
$ bundle
Or install it yourself as:
$ gem install basic_lti
## Usage
TODO: Write usage instructions here
## Contributing
1. Fork it ( http://github.com/<my-github-username>/basic_lti/fork )
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create new Pull Request

View File

@ -0,0 +1 @@
require "bundler/gem_tasks"

View File

@ -0,0 +1,25 @@
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
Gem::Specification.new do |spec|
spec.name = "basic_lti_outbound"
spec.version = "0.0.1"
spec.authors = ["Brian Palmer"]
spec.email = ["brianp@instructure.com"]
spec.summary = %q{LTI consumer service}
spec.homepage = "https://github.com/instructure/canvas-lms"
spec.license = "AGPL"
spec.files = `git ls-files`.split($/)
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
spec.require_paths = ["lib"]
spec.add_dependency "i18n", "0.6.8"
spec.add_dependency "oauth-instructure", "0.4.9"
spec.add_development_dependency "bundler", "~> 1.5"
spec.add_development_dependency "rake"
spec.add_development_dependency "rspec"
end

View File

@ -0,0 +1,21 @@
require 'uri'
require 'oauth'
require 'oauth/consumer'
require "i18n"
module BasicLtiOutbound
require "basic_lti_outbound/lti_tool"
require "basic_lti_outbound/lti_model"
require "basic_lti_outbound/lti_context"
require "basic_lti_outbound/lti_account"
require "basic_lti_outbound/lti_course"
require "basic_lti_outbound/lti_role"
require "basic_lti_outbound/lti_user"
require "basic_lti_outbound/lti_assignment"
require "basic_lti_outbound/tool_launch"
require "basic_lti_outbound/variable_substitutor"
def self.generate(*args)
BasicLtiOutbound::ToolLaunch.new(*args).generate
end
end

View File

@ -0,0 +1,7 @@
module BasicLtiOutbound
class LTIAccount < LTIContext
attr_accessor :lti_guid, :name, :domain
add_variable_mapping ".domain", :domain
end
end

View File

@ -0,0 +1,9 @@
module BasicLtiOutbound
class LTIAssignment < LTIContext
attr_accessor :id, :source_id, :title, :points_possible, :return_types, :allowed_extensions
add_variable_mapping ".id", :id
add_variable_mapping ".title", :title
add_variable_mapping ".pointsPossible", :points_possible
end
end

View File

@ -0,0 +1,8 @@
module BasicLtiOutbound
class LTIContext < LTIModel
attr_accessor :root_account, :opaque_identifier, :id, :sis_source_id
add_variable_mapping ".id", :id
add_variable_mapping ".sisSourceId", :sis_source_id
end
end

View File

@ -0,0 +1,5 @@
module BasicLtiOutbound
class LTICourse < LTIContext
attr_accessor :course_code, :name
end
end

View File

@ -0,0 +1,20 @@
module BasicLtiOutbound
class LTIModel
def variable_substitution_mapping(placeholder)
@@substititions ||= {}
@@substititions[placeholder] && send(@@substititions[placeholder])
end
def has_variable_mapping?(placeholder)
@@substititions ||= {}
!!@@substititions[placeholder]
end
protected
def self.add_variable_mapping(placeholder, substitution_method)
@@substititions ||= {}
@@substititions[placeholder] = substitution_method
end
end
end

View File

@ -0,0 +1,38 @@
module BasicLtiOutbound
class LTIRole
INSTRUCTOR = "Instructor"
LEARNER = "Learner"
ADMIN = "urn:lti:instrole:ims/lis/Administrator"
CONTENT_DEVLOPER = "ContentDeveloper"
OBSERVER = "urn:lti:instrole:ims/lis/Observer"
TEACHING_ASSISTANT = "urn:lti:role:ims/lis/TeachingAssistant"
NONE = "urn:lti:sysrole:ims/lis/None"
#def self.enrollment_to_membership(membership)
# case membership
# when StudentEnrollment, StudentViewEnrollment
# 'Learner'
# when TeacherEnrollment
# 'Instructor'
# when TaEnrollment
# 'urn:lti:role:ims/lis/TeachingAssistant'
# when DesignerEnrollment
# 'ContentDeveloper'
# when ObserverEnrollment
# 'urn:lti:instrole:ims/lis/Observer'
# when AccountUser
# 'urn:lti:instrole:ims/lis/Administrator'
# else
# 'urn:lti:instrole:ims/lis/Observer'
# end
#end
attr_accessor :type, :state
def active?
@state == :active
end
end
end

View File

@ -0,0 +1,76 @@
module BasicLtiOutbound
class LTITool
PRIVACY_LEVEL_PUBLIC = :public
PRIVACY_LEVEL_NAME_ONLY = :name_only
PRIVACY_LEVEL_EMAIL_ONLY = :email_only
PRIVACY_LEVEL_ANYNOMOUS = :anonymous
attr_accessor :consumer_key, :context, :privacy_level, :name, :settings, :shared_secret
def include_name?
[PRIVACY_LEVEL_PUBLIC, PRIVACY_LEVEL_NAME_ONLY].include? privacy_level
end
def include_email?
[PRIVACY_LEVEL_PUBLIC, PRIVACY_LEVEL_EMAIL_ONLY].include? privacy_level
end
def public?
[PRIVACY_LEVEL_PUBLIC].include? privacy_level
end
def settings
@settings || {}
end
# sets the custom fields from the main tool settings, and any on individual resource type settings
def set_custom_fields(hash, resource_type)
fields = [settings[:custom_fields] || {}]
if resource_type && settings[resource_type.to_sym]
fields << (settings[resource_type.to_sym][:custom_fields] || {})
end
fields.each do |field_set|
field_set.each do |key, val|
key = key.to_s.gsub(/[^\w]/, '_').downcase
if key.match(/^custom_/)
hash[key] = val
else
hash["custom_#{key}"] = val
end
end
end
nil
end
def selection_width(resource_type)
extension_setting(resource_type, :selection_width)
end
def selection_height(resource_type)
extension_setting(resource_type, :selection_height)
end
private
#Duplicated in ContextExternalTool
def extension_setting(type, property = nil)
type = type.to_sym
return settings[type] unless property
(settings[type] && settings[type][property]) || settings[property] || extension_default_value(property)
end
#Duplicated in ContextExternalTool
def extension_default_value(property)
case property
when :url
url
when :selection_width
800
when :selection_height
400
else
nil
end
end
end
end

View File

@ -0,0 +1,36 @@
module BasicLtiOutbound
class LTIUser < LTIContext
ACTIVE_STATE = 'active'
INACTIVE_STATE = 'inactive'
attr_accessor :avatar_url, :email, :login_id, :first_name, :last_name, :name,
:current_enrollments, :concluded_enrollments,
:sis_user_id
add_variable_mapping ".login_id", :login_id
add_variable_mapping ".enrollment_state", :enrollment_state
add_variable_mapping ".concluded_roles", :concluded_roles
add_variable_mapping ".full", :full_name
add_variable_mapping ".family", :family_name
add_variable_mapping ".given", :given_name
add_variable_mapping ".timezone", :timezone
def current_role_types
roles = current_enrollments.map(&:type).join(',') if current_enrollments && current_enrollments.size > 0
roles || BasicLtiOutbound::LTIRole::NONE
end
def concluded_role_types
roles = concluded_enrollments.map(&:type).join(',') if concluded_enrollments && concluded_enrollments.size > 0
roles || BasicLtiOutbound::LTIRole::NONE
end
def enrollment_state
current_enrollments.any? { |e| e.active? } ? BasicLtiOutbound::LTIUser::ACTIVE_STATE : BasicLtiOutbound::LTIUser::INACTIVE_STATE
end
def learner?
current_enrollments.any? { |e| e.type == BasicLtiOutbound::LTIRole::LEARNER }
end
end
end

View File

@ -0,0 +1,203 @@
#
# Copyright (C) 2013 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
module BasicLtiOutbound
class ToolLaunch
attr_reader :url, :tool, :user, :context, :link_code, :return_url,
:resource_type, :root_account, :hash, :assignment, :outgoing_email_address
def initialize(options)
@url = options[:url] || raise("URL required for generating Basic LTI content")
@tool = options[:tool] || raise("Tool required for generating Basic LTI content")
@user = options[:user] || raise("User required for generating Basic LTI content")
@context = options[:context] || raise("Context required for generating Basic LTI content")
@link_code = options[:link_code] || raise("Link Code required for generating Basic LTI content")
@return_url = options[:return_url] || raise("Return URL required for generating Basic LTI content")
@resource_type = options[:resource_type]
@outgoing_email_address = options[:outgoing_email_address]
@root_account = context.root_account || tool.context.root_account || raise("Root account required for generating Basic LTI content")
@hash = {}
end
#def pseudonym
# @pseudonym ||= user.find_pseudonym_for_account(root_account)
#end
def for_assignment!(assignment, outcome_service_url, legacy_outcome_service_url)
@assignment = assignment
hash['lis_result_sourcedid'] = assignment.source_id if user.learner?
hash['lis_outcome_service_url'] = outcome_service_url
hash['ext_ims_lis_basic_outcome_url'] = legacy_outcome_service_url
hash['ext_outcome_data_values_accepted'] = assignment.return_types.join(',')
hash['custom_canvas_assignment_title'] = '$Canvas.assignment.title'
hash['custom_canvas_assignment_points_possible'] = '$Canvas.assignment.pointsPossible'
if tool.public?
hash['custom_canvas_assignment_id'] = '$Canvas.assignment.id'
end
end
def for_homework_submission!(assignment)
@assignment = assignment
@resource_type = 'homework_submission'
hash['ext_content_return_types'] = assignment.return_types.join(',')
hash['ext_content_file_extensions'] = assignment.allowed_extensions.join(',') if assignment.allowed_extensions
hash['custom_canvas_assignment_id'] = '$Canvas.assignment.id'
end
def has_selection_html!(html)
hash['text'] = CGI::escape(html)
end
def generate
hash['lti_message_type'] = 'basic-lti-launch-request'
hash['lti_version'] = 'LTI-1p0'
hash['resource_link_id'] = link_code
hash['resource_link_title'] = tool.name
hash['user_id'] = user.opaque_identifier
hash['user_image'] = user.avatar_url
hash['roles'] = user.current_role_types # AccountAdmin, Student, Faculty or Observer
hash['custom_canvas_enrollment_state'] = '$Canvas.enrollment.enrollmentState'
if tool.include_name?
hash['lis_person_name_given'] = user.first_name
hash['lis_person_name_family'] = user.last_name
hash['lis_person_name_full'] = user.name
end
if tool.include_email?
hash['lis_person_contact_email_primary'] = user.email
end
if tool.public?
hash['custom_canvas_user_id'] = '$Canvas.user.id'
hash['lis_person_sourcedid'] = user.sis_user_id if user.sis_user_id
hash['custom_canvas_user_login_id'] = '$Canvas.user.login_id'
if context.is_a?(LTICourse)
hash['custom_canvas_course_id'] = '$Canvas.context.id'
hash['lis_course_offering_sourcedid'] = context.sis_source_id if context.sis_source_id
elsif context.is_a?(LTIAccount) || context.is_a?(LTIUser)
hash['custom_canvas_account_id'] = '$Canvas.account.id'
hash['custom_canvas_account_sis_id'] = '$Canvas.account.sisSourceId'
end
hash['custom_canvas_api_domain'] = '$Canvas.api.domain'
end
# need to set the locale here (instead of waiting for the first call to
# I18n.t like we usually do), because otherwise we'll have the wrong code
# for the launch_presentation_locale.
I18n.set_locale_with_localizer
hash['context_id'] = context.opaque_identifier
hash['context_title'] = context.name
hash['context_label'] = context.course_code if context.respond_to?(:course_code)
hash['launch_presentation_locale'] = I18n.locale || I18n.default_locale.to_s
hash['launch_presentation_document_target'] = 'iframe'
if resource_type
hash['launch_presentation_width'] = tool.selection_width(resource_type)
hash['launch_presentation_height'] = tool.selection_height(resource_type)
end
hash['launch_presentation_return_url'] = return_url
hash['tool_consumer_instance_guid'] = root_account.lti_guid
hash['tool_consumer_instance_name'] = root_account.name
hash['tool_consumer_instance_contact_email'] = outgoing_email_address # TODO: find a better email address to use here
hash['tool_consumer_info_product_family_code'] = 'canvas'
hash['tool_consumer_info_version'] = 'cloud'
tool.set_custom_fields(hash, resource_type)
if resource_type == 'editor_button'
hash['selection_directive'] = 'embed_content' #backwards compatibility
hash['ext_content_intended_use'] = 'embed'
hash['ext_content_return_types'] = 'oembed,lti_launch_url,url,image_url,iframe'
hash['ext_content_return_url'] = return_url
elsif resource_type == 'resource_selection'
hash['selection_directive'] = 'select_link' #backwards compatibility
hash['ext_content_intended_use'] = 'navigation'
hash['ext_content_return_types'] = 'lti_launch_url'
hash['ext_content_return_url'] = return_url
elsif resource_type == 'homework_submission'
hash['ext_content_intended_use'] = 'homework'
hash['ext_content_return_url'] = return_url
end
hash['oauth_callback'] = 'about:blank'
variable_substitutor = VariableSubstitutor.new
variable_substitutor.substitute!(hash, "$Canvas.user", user)
variable_substitutor.substitute!(hash, "$Canvas.context", context)
variable_substitutor.substitute!(hash, "$Canvas.api", root_account)
variable_substitutor.substitute!(hash, "$Canvas.assignment", assignment) if assignment
variable_substitutor.substitute!(hash, "$Canvas.account", context.is_a?(LTIAccount) ? context : root_account)
variable_substitutor.substitute!(hash, "$Canvas.membership", user)
variable_substitutor.substitute!(hash, "$Canvas.enrollment", user)
variable_substitutor.substitute!(hash, "$Person.name", user)
variable_substitutor.substitute!(hash, "$Person.address", user)
self.class.generate_params(hash, url, tool.consumer_key, tool.shared_secret)
end
private
def self.generate_params(params, url, key, secret)
uri = URI.parse(url)
if uri.port == uri.default_port
host = uri.host
else
host = "#{uri.host}:#{uri.port}"
end
consumer = OAuth::Consumer.new(key, secret, {
:site => "#{uri.scheme}://#{host}",
:signature_method => "HMAC-SHA1"
})
path = uri.path
path = '/' if path.empty?
if uri.query && uri.query != ""
CGI.parse(uri.query).each do |query_key, query_values|
unless params[query_key]
params[query_key] = query_values.first
end
end
end
options = {
:scheme => 'body',
:timestamp => @timestamp,
:nonce => @nonce
}
request = consumer.create_signed_request(:post, path, nil, options, stringify_hash(params))
# the request is made by a html form in the user's browser, so we
# want to revert the escapage and return the hash of post parameters ready
# for embedding in a html view
hash = {}
request.body.split(/&/).each do |param|
key, val = param.split(/=/).map{|v| CGI.unescape(v) }
hash[key] = val
end
hash
end
def self.stringify_hash(hash)
hash.dup.tap do |new_hash|
new_hash.keys.each { |k| new_hash[k.to_s] = new_hash.delete(k) unless k.is_a?(String) }
end
end
end
end

View File

@ -0,0 +1,34 @@
#
# Copyright (C) 2013 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
module BasicLtiOutbound
class VariableSubstitutor
#This method changes given data_hash!
def substitute!(data_hash, substitution_base_name, substitution_object)
data_hash.each do |key, val|
if val.to_s.start_with?(substitution_base_name)
attribute = val.gsub(substitution_base_name, "")
if substitution_object.has_variable_mapping?(attribute)
replacement_value = substitution_object.variable_substitution_mapping(attribute)
data_hash[key] = replacement_value if replacement_value
end
end
end
end
end
end

View File

@ -0,0 +1,29 @@
#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require "spec_helper"
describe BasicLtiOutbound::LTIAccount do
it_behaves_like "an LTI context"
it_behaves_like "it has an attribute setter and getter for", :lti_guid
it_behaves_like "it has an attribute setter and getter for", :name
it_behaves_like "it has an attribute setter and getter for", :domain
it_behaves_like "it provides variable mapping", ".domain", :domain
end

View File

@ -0,0 +1,32 @@
#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require "spec_helper"
describe BasicLtiOutbound::LTIAssignment do
it_behaves_like "it has an attribute setter and getter for", :id
it_behaves_like "it has an attribute setter and getter for", :source_id
it_behaves_like "it has an attribute setter and getter for", :title
it_behaves_like "it has an attribute setter and getter for", :points_possible
it_behaves_like "it has an attribute setter and getter for", :return_types
it_behaves_like "it has an attribute setter and getter for", :allowed_extensions
it_behaves_like "it provides variable mapping", ".id", :id
it_behaves_like "it provides variable mapping", ".title", :title
it_behaves_like "it provides variable mapping", ".pointsPossible", :points_possible
end

View File

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

View File

@ -0,0 +1,26 @@
#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require "spec_helper"
describe BasicLtiOutbound::LTICourse do
it_behaves_like "an LTI context"
it_behaves_like "it has an attribute setter and getter for", :course_code
it_behaves_like "it has an attribute setter and getter for", :name
end

View File

@ -0,0 +1,54 @@
#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require "spec_helper"
describe BasicLtiOutbound::LTIModel do
class Dummy < BasicLtiOutbound::LTIModel
attr_accessor :test
add_variable_mapping ".test", :test
end
describe "#has_variable_mapping?" do
it "returns true if mapping exists" do
model = Dummy.new
expect(model.has_variable_mapping?('.test')).to eq true
end
it "returns false if mapping does not exist" do
model = Dummy.new
expect(model.has_variable_mapping?('.none')).to eq false
end
end
describe "#variable_substitution_mapping" do
it "returns nil for any variable_substitution_call" do
model = BasicLtiOutbound::LTIModel.new
expect(model.variable_substitution_mapping(:something)).to eq nil
expect(model.variable_substitution_mapping(:something_else)).to eq nil
expect(model.variable_substitution_mapping(nil)).to eq nil
expect(model.variable_substitution_mapping([])).to eq nil
end
it "calls the mapped method" do
model = Dummy.new
model.test = 'value'
expect(model.variable_substitution_mapping('.test')).to eq 'value'
end
end
end

View File

@ -0,0 +1,44 @@
#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require "spec_helper"
describe BasicLtiOutbound::LTIRole do
it_behaves_like "it has an attribute setter and getter for", :type
it_behaves_like "it has an attribute setter and getter for", :state
it "returns active? IFF the state is :active" do
expect(subject.active?).to be false
subject.state = "something irrelevant"
expect(subject.active?).to be false
subject.state = :active
expect(subject.active?).to be true
end
describe "constants" do
it "provides role constants" do
expect(BasicLtiOutbound::LTIRole::INSTRUCTOR).to eq "Instructor"
expect(BasicLtiOutbound::LTIRole::LEARNER).to eq "Learner"
expect(BasicLtiOutbound::LTIRole::ADMIN).to eq "urn:lti:instrole:ims/lis/Administrator"
expect(BasicLtiOutbound::LTIRole::CONTENT_DEVLOPER).to eq "ContentDeveloper"
expect(BasicLtiOutbound::LTIRole::OBSERVER).to eq "urn:lti:instrole:ims/lis/Observer"
expect(BasicLtiOutbound::LTIRole::TEACHING_ASSISTANT).to eq "urn:lti:role:ims/lis/TeachingAssistant"
expect(BasicLtiOutbound::LTIRole::NONE).to eq "urn:lti:sysrole:ims/lis/None"
end
end
end

View File

@ -0,0 +1,145 @@
#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require "spec_helper"
describe BasicLtiOutbound::LTITool do
it_behaves_like "it has an attribute setter and getter for", :consumer_key
it_behaves_like "it has an attribute setter and getter for", :context
it_behaves_like "it has an attribute setter and getter for", :privacy_level
it_behaves_like "it has an attribute setter and getter for", :name
it_behaves_like "it has an attribute setter and getter for", :shared_secret
describe "#include_name?" do
it "returns true IFF the privacy level is public or name only" do
subject.privacy_level = :something
expect(subject.include_name?).to eq false
subject.privacy_level = BasicLtiOutbound::LTITool::PRIVACY_LEVEL_PUBLIC
expect(subject.include_name?).to eq true
subject.privacy_level = BasicLtiOutbound::LTITool::PRIVACY_LEVEL_NAME_ONLY
expect(subject.include_name?).to eq true
subject.privacy_level = BasicLtiOutbound::LTITool::PRIVACY_LEVEL_EMAIL_ONLY
expect(subject.include_name?).to eq false
subject.privacy_level = BasicLtiOutbound::LTITool::PRIVACY_LEVEL_ANYNOMOUS
expect(subject.include_name?).to eq false
end
end
describe "#include_email?" do
it "returns true IFF the privacy level is public or email only" do
subject.privacy_level = :something
expect(subject.include_email?).to eq false
subject.privacy_level = BasicLtiOutbound::LTITool::PRIVACY_LEVEL_PUBLIC
expect(subject.include_email?).to eq true
subject.privacy_level = BasicLtiOutbound::LTITool::PRIVACY_LEVEL_NAME_ONLY
expect(subject.include_email?).to eq false
subject.privacy_level = BasicLtiOutbound::LTITool::PRIVACY_LEVEL_EMAIL_ONLY
expect(subject.include_email?).to eq true
subject.privacy_level = BasicLtiOutbound::LTITool::PRIVACY_LEVEL_ANYNOMOUS
expect(subject.include_email?).to eq false
end
end
describe "#public?" do
it "returns true IFF the privacy level is public" do
subject.privacy_level = :something
expect(subject.public?).to eq false
subject.privacy_level = BasicLtiOutbound::LTITool::PRIVACY_LEVEL_PUBLIC
expect(subject.public?).to eq true
subject.privacy_level = BasicLtiOutbound::LTITool::PRIVACY_LEVEL_NAME_ONLY
expect(subject.public?).to eq false
subject.privacy_level = BasicLtiOutbound::LTITool::PRIVACY_LEVEL_EMAIL_ONLY
expect(subject.public?).to eq false
subject.privacy_level = BasicLtiOutbound::LTITool::PRIVACY_LEVEL_ANYNOMOUS
expect(subject.public?).to eq false
end
end
describe "#settings" do
it "attribute setter, but returns {} instead of nil" do
expect(subject.settings).to eq({})
subject.settings = 10
expect(subject.settings).to eq 10
end
end
describe "#set_custom_fields" do
it "does not change the given input hash if the settings' custom fields are empty" do
hash = {:a => :b}
subject.settings = {}
subject.set_custom_fields(hash, nil)
expect(hash).to eq({:a => :b})
subject.settings = {:custom_fields => {}}
subject.set_custom_fields(hash, nil)
expect(hash).to eq({:a => :b})
end
it "merges fields from the settings' custom fields into the given hash prefixing them with custom_" do
hash = {:a => :b}
subject.settings = {:custom_fields => {:d => :e}}
subject.set_custom_fields(hash, nil)
expect(hash).to eq({:a => :b, "custom_d" => :e})
end
it "replaces non-word characters from custom field keys" do
hash = {:a => :b}
subject.settings = {:custom_fields => {:'%$#@d()' => :e}}
subject.set_custom_fields(hash, nil)
expect(hash).to eq({:a => :b, "custom_____d__" => :e})
end
it "merges fields from the applicable resource type too" do
hash = {:a => :b}
subject.settings = {:given_resource_type => {:custom_fields => {:'%$#@d()' => :e}}}
subject.set_custom_fields(hash, "given_resource_type")
expect(hash).to eq({:a => :b, "custom_____d__" => :e})
end
end
describe "#selection_width" do
it "returns selection width from settings for a resource type" do
subject.settings = {editor_button: {:selection_width => 100}}
expect(subject.selection_width('editor_button')).to eq 100
end
it 'returns a default value if type is present in setting, but no selection width' do
subject.settings = {editor_button: {}}
expect(subject.selection_width('editor_button')).to eq 800
end
it 'returns a default value if none set' do
expect(subject.selection_width('editor_button')).to eq 800
end
end
describe "#selection_height" do
it "returns selection height from settings for a resource type" do
subject.settings = {editor_button: {:selection_height => 100}}
expect(subject.selection_height('editor_button')).to eq 100
end
it 'returns a default value if none set' do
expect(subject.selection_height('editor_button')).to eq 400
end
end
end

View File

@ -0,0 +1,132 @@
#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require "spec_helper"
describe BasicLtiOutbound::LTIUser do
it_behaves_like "an LTI context"
it_behaves_like "it has an attribute setter and getter for", :avatar_url
it_behaves_like "it has an attribute setter and getter for", :email
it_behaves_like "it has an attribute setter and getter for", :first_name
it_behaves_like "it has an attribute setter and getter for", :last_name
it_behaves_like "it has an attribute setter and getter for", :name
it_behaves_like "it has an attribute setter and getter for", :login_id
it_behaves_like "it has an attribute setter and getter for", :current_enrollments
it_behaves_like "it has an attribute setter and getter for", :concluded_enrollments
it_behaves_like "it has an attribute setter and getter for", :sis_user_id
it_behaves_like "it provides variable mapping", ".login_id", :login_id
it_behaves_like "it provides variable mapping", ".enrollment_state", :enrollment_state
it_behaves_like "it provides variable mapping", ".concluded_roles", :concluded_roles
it_behaves_like "it provides variable mapping", ".full", :full_name
it_behaves_like "it provides variable mapping", ".family", :family_name
it_behaves_like "it provides variable mapping", ".given", :given_name
it_behaves_like "it provides variable mapping", ".timezone", :timezone
let(:teacher_role_active) do
BasicLtiOutbound::LTIRole.new.tap do |role|
role.type = BasicLtiOutbound::LTIRole::INSTRUCTOR
role.state = :active
end
end
let(:teacher_role_inactive) do
BasicLtiOutbound::LTIRole.new.tap do |role|
role.type = BasicLtiOutbound::LTIRole::INSTRUCTOR
role.state = :inactive
end
end
let(:learner_role_active) do
BasicLtiOutbound::LTIRole.new.tap do |role|
role.type = BasicLtiOutbound::LTIRole::LEARNER
role.state = :active
end
end
let(:learner_role_inactive) do
BasicLtiOutbound::LTIRole.new.tap do |role|
role.type = BasicLtiOutbound::LTIRole::LEARNER
role.state = :inactive
end
end
describe "#current_role_types" do
it "provides a string representation of current roles" do
subject.tap do |user|
user.current_enrollments = [teacher_role_active, learner_role_active]
end
expect(subject.current_role_types).to eq("#{BasicLtiOutbound::LTIRole::INSTRUCTOR},#{BasicLtiOutbound::LTIRole::LEARNER}")
end
it "defaults if no roles exist" do
expect(subject.current_role_types).to eq(BasicLtiOutbound::LTIRole::NONE)
end
end
describe "#concluded_role_types" do
it "provides a string representation of concluded roles" do
subject.tap do |user|
user.concluded_enrollments = [teacher_role_active, learner_role_active]
end
expect(subject.concluded_role_types).to eq("#{BasicLtiOutbound::LTIRole::INSTRUCTOR},#{BasicLtiOutbound::LTIRole::LEARNER}")
end
it "defaults if no roles exist" do
expect(subject.concluded_role_types).to eq(BasicLtiOutbound::LTIRole::NONE)
end
end
describe "#enrollment_state" do
it "returns 'active' if any current_enrollments are active" do
subject.current_enrollments = [teacher_role_inactive, learner_role_active]
expect(subject.enrollment_state).to eq BasicLtiOutbound::LTIUser::ACTIVE_STATE
end
it "returns 'inactive' if no current_enrollments are active" do
subject.current_enrollments = [teacher_role_inactive, learner_role_inactive]
expect(subject.enrollment_state).to eq BasicLtiOutbound::LTIUser::INACTIVE_STATE
end
end
describe "constants" do
it "provides enrollment state constants" do
expect(BasicLtiOutbound::LTIUser::ACTIVE_STATE).to eq "active"
expect(BasicLtiOutbound::LTIUser::INACTIVE_STATE).to eq "inactive"
end
end
describe "#learner?" do
it "returns false when current enrollments includes learner role" do
subject.current_enrollments = [teacher_role_active, learner_role_active]
expect(subject.learner?).to eq true
end
it "returns false when current enrollments do not include learner role" do
subject.current_enrollments = [teacher_role_active]
expect(subject.learner?).to eq false
end
end
end

View File

@ -0,0 +1,478 @@
#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require "spec_helper"
describe BasicLtiOutbound::ToolLaunch do
before do
@account = BasicLtiOutbound::LTIAccount.new.tap do |account|
account.domain = "account_domain"
account.lti_guid = "account_lti_guid"
account.name = "account_name"
account.id = "account_id"
account.sis_source_id = "account_sis_source_id"
end
@root_account = BasicLtiOutbound::LTIAccount.new.tap do |account|
account.domain = "root_account_domain"
account.lti_guid = "root_account_lti_guid"
account.name = "root_account_name"
account.id = "root_account_id"
account.sis_source_id = "root_account_sis_source_id"
end
@course = BasicLtiOutbound::LTICourse.new.tap do |course|
course.root_account = @root_account
course.opaque_identifier = "course_opaque_identifier"
course.name = "course_name"
course.id = "course_id"
course.course_code = "course_code"
course.sis_source_id = "course_sis_source_id"
end
@tool = BasicLtiOutbound::LTITool.new.tap do |tool|
tool.name = "tool_name"
tool.context = @course
tool.privacy_level = BasicLtiOutbound::LTITool::PRIVACY_LEVEL_PUBLIC
end
teacher_role = BasicLtiOutbound::LTIRole.new.tap do |role|
role.type = BasicLtiOutbound::LTIRole::INSTRUCTOR
role.state = :active
end
@user = BasicLtiOutbound::LTIUser.new.tap do |user|
user.avatar_url = "avatar_url"
user.current_enrollments = "current_enrollments"
user.email = 'nobody@example.com'
user.first_name = "first_name"
user.id = "user_id"
user.last_name = "last_name"
user.login_id = "user_login_id"
user.name = "user_name"
user.opaque_identifier = "user_opaque_identifier"
user.sis_user_id = "sis_user_id"
user.current_enrollments = [teacher_role]
user.concluded_enrollments = []
user.root_account = @root_account
end
@assignment = BasicLtiOutbound::LTIAssignment.new.tap do |assignment|
assignment.id = 'assignment_id'
assignment.source_id = '123456'
assignment.title = 'assignment1'
assignment.points_possible = 100
assignment.return_types = ['url', 'text']
assignment.allowed_extensions = ['jpg', 'pdf']
end
@tool_launch = BasicLtiOutbound::ToolLaunch.new(:url => 'http://www.yahoo.com',
:tool => @tool,
:user => @user,
:context => @course,
:link_code => '123456',
:return_url => 'http://www.google.com',
:outgoing_email_address => "outgoing_email_address")
end
describe "#generate" do
it "generates correct parameters" do
hash = @tool_launch.generate
expect(hash['lti_message_type']).to eq 'basic-lti-launch-request'
expect(hash['lti_version']).to eq 'LTI-1p0'
expect(hash['resource_link_id']).to eq '123456'
expect(hash['resource_link_title']).to eq "tool_name"
expect(hash['user_id']).to eq "user_opaque_identifier"
expect(hash['user_image']).to eq "avatar_url"
expect(hash['roles']).to eq 'Instructor'
expect(hash['context_id']).to eq "course_opaque_identifier"
expect(hash['context_title']).to eq "course_name"
expect(hash['context_label']).to eq "course_code"
expect(hash['custom_canvas_user_id']).to eq "user_id"
expect(hash['custom_canvas_user_login_id']).to eq "user_login_id"
expect(hash['custom_canvas_course_id']).to eq "course_id"
expect(hash['custom_canvas_api_domain']).to eq "root_account_domain"
expect(hash['lis_course_offering_sourcedid']).to eq 'course_sis_source_id'
expect(hash['lis_person_contact_email_primary']).to eq 'nobody@example.com'
expect(hash['lis_person_name_full']).to eq 'user_name'
expect(hash['lis_person_name_family']).to eq 'last_name'
expect(hash['lis_person_name_given']).to eq 'first_name'
expect(hash['lis_person_sourcedid']).to eq 'sis_user_id'
expect(hash['launch_presentation_locale']).to eq "en" #was I18n.default_locale.to_s
expect(hash['launch_presentation_document_target']).to eq 'iframe'
expect(hash['launch_presentation_return_url']).to eq 'http://www.google.com'
expect(hash['tool_consumer_instance_guid']).to eq "root_account_lti_guid"
expect(hash['tool_consumer_instance_name']).to eq "root_account_name"
expect(hash['tool_consumer_instance_contact_email']).to eq "outgoing_email_address"
expect(hash['tool_consumer_info_product_family_code']).to eq 'canvas'
expect(hash['tool_consumer_info_version']).to eq 'cloud'
expect(hash['oauth_callback']).to eq 'about:blank'
end
it "sets the locale if I18n.localizer exists" do
I18n.localizer = lambda { :es }
hash = @tool_launch.generate
expect(hash['launch_presentation_locale']).to eq 'es'
I18n.localizer = lambda { :en }
end
it "adds account info in launch data for account navigation" do
hash = BasicLtiOutbound::ToolLaunch.new(:url => 'http://www.yahoo.com',
:tool => @tool,
:user => @user,
:context => @account,
:link_code => '123456',
:return_url => 'http://www.google.com').generate
expect(hash['custom_canvas_account_id']).to eq "account_id"
expect(hash['custom_canvas_account_sis_id']).to eq 'account_sis_source_id'
expect(hash['custom_canvas_user_login_id']).to eq "user_login_id"
end
it "adds account and user info in launch data for user profile launch" do
hash = BasicLtiOutbound::ToolLaunch.new(:url => 'http://www.yahoo.com',
:tool => @tool,
:user => @user,
:context => @user,
:link_code => '123456',
:return_url => 'http://www.google.com').generate
expect(hash['custom_canvas_account_id']).to eq "root_account_id"
expect(hash['custom_canvas_account_sis_id']).to eq 'root_account_sis_source_id'
expect(hash['lis_person_sourcedid']).to eq 'sis_user_id'
expect(hash['custom_canvas_user_id']).to eq "user_id"
expect(hash['tool_consumer_instance_guid']).to eq "root_account_lti_guid" #was hash['tool_consumer_instance_guid']).to eq sub_account.root_account.lti_guid
end
it "includes URI query parameters" do
hash = BasicLtiOutbound::ToolLaunch.new(:url => 'http://www.yahoo.com?paramater_a=value_a&parameter_b=value_b',
:tool => @tool,
:user => @user,
:context => @course,
:link_code => '123456',
:return_url => 'http://www.google.com').generate
expect(hash['paramater_a']).to eq 'value_a'
expect(hash['parameter_b']).to eq 'value_b'
end
it "does not allow overwriting other parameters from the URI query string" do
hash = BasicLtiOutbound::ToolLaunch.new(:url => 'http://www.yahoo.com?user_id=ATTEMPT_TO_SET_DATA&oauth_callback=ATTEMPT_TO_SET_DATA',
:tool => @tool,
:user => @user,
:context => @course,
:link_code => '123456',
:return_url => 'http://www.google.com').generate
expect(hash['user_id']).to eq "user_opaque_identifier"
expect(hash['oauth_callback']).to eq 'about:blank'
end
it "includes custom fields" do
@tool.privacy_level = BasicLtiOutbound::LTITool::PRIVACY_LEVEL_ANYNOMOUS
@tool.settings = {:custom_fields => {
'custom_bob' => 'bob',
'custom_fred' => 'fred',
'john' => 'john',
'@$TAA$#$#' => 123}}
hash = @tool_launch.generate
expect(hash.keys.select { |k| k.match(/^custom_/) }.sort).to eq(
['custom___taa____', 'custom_bob', 'custom_canvas_enrollment_state', 'custom_fred', 'custom_john'])
expect(hash['custom_bob']).to eql('bob')
expect(hash['custom_fred']).to eql('fred')
expect(hash['custom_john']).to eql('john')
expect(hash['custom___taa____']).to eql('123')
expect(hash).to_not have_key '@$TAA$#$#'
expect(hash).to_not have_key 'john'
end
it "does not include name and email if anonymous" do
@tool.privacy_level = BasicLtiOutbound::LTITool::PRIVACY_LEVEL_ANYNOMOUS
hash = @tool_launch.generate
expect(hash).to_not have_key 'lis_person_name_given'
expect(hash).to_not have_key 'lis_person_name_family'
expect(hash).to_not have_key 'lis_person_name_full'
expect(hash).to_not have_key 'lis_person_contact_email_primary'
end
it "includes name if name_only" do
@tool.privacy_level = BasicLtiOutbound::LTITool::PRIVACY_LEVEL_NAME_ONLY
hash = @tool_launch.generate
expect(hash['lis_person_name_given']).to eq "first_name"
expect(hash['lis_person_name_family']).to eq "last_name"
expect(hash['lis_person_name_full']).to eq "user_name"
expect(hash['lis_person_contact_email_primary']).to be_nil
end
it "includes email if email_only" do
@tool.privacy_level = BasicLtiOutbound::LTITool::PRIVACY_LEVEL_EMAIL_ONLY
hash = @tool_launch.generate
expect(hash['lis_person_name_given']).to eq nil
expect(hash['lis_person_name_family']).to eq nil
expect(hash['lis_person_name_full']).to eq nil
expect(hash['lis_person_contact_email_primary']).to eq "nobody@example.com"
end
it "includes email if public" do
@tool.privacy_level = BasicLtiOutbound::LTITool::PRIVACY_LEVEL_PUBLIC
hash = @tool_launch.generate
expect(hash['lis_person_name_given']).to eq 'first_name'
expect(hash['lis_person_name_family']).to eq "last_name"
expect(hash['lis_person_name_full']).to eq "user_name"
expect(hash['lis_person_contact_email_primary']).to eq "nobody@example.com"
end
it "includes text if set" do
@launch = BasicLtiOutbound::ToolLaunch.new(:url => 'http://www.yahoo.com',
:tool => @tool,
:user => @user,
:context => @course,
:link_code => '123456',
:return_url => 'http://www.yahoo.com')
html = "<p>this has <a href='#'>a link</a></p>"
@launch.has_selection_html!(html)
hash = @launch.generate
expect(hash['text']).to eq CGI::escape(html)
end
it "gets the correct width and height based on resource type" do
@tool.settings = {editor_button: {:selection_width => 1000, :selection_height => 300, :icon_url => 'www.example.com/icon', :url => 'www.example.com'}}
hash = BasicLtiOutbound::ToolLaunch.new(:url => 'http://www.yahoo.com',
:tool => @tool,
:user => @user,
:context => @course,
:link_code => '123456',
:return_url => 'http://www.yahoo.com',
:resource_type => 'editor_button').generate
expect(hash['launch_presentation_width']).to eq '1000'
expect(hash['launch_presentation_height']).to eq '300'
end
describe "variable substitutions" do
before do
@substitutor = double('Substitutor', substitute!: 'something')
BasicLtiOutbound::VariableSubstitutor.stub(:new).and_return(@substitutor)
end
it "substitutes $Canvas.user" do
@tool_launch.generate
expect(@substitutor).to have_received(:substitute!).with(anything, '$Canvas.user', @user)
end
it "substitutes $Canvas.context" do
@tool_launch.generate
expect(@substitutor).to have_received(:substitute!).with(anything, '$Canvas.context', @course)
end
it "substitutes $Canvas.api" do
@tool_launch.generate
expect(@substitutor).to have_received(:substitute!).with(anything, '$Canvas.api', @root_account)
end
it "substitutes $Canvas.assignment" do
@tool_launch.for_homework_submission!(@assignment)
@tool_launch.generate
expect(@substitutor).to have_received(:substitute!).with(anything, '$Canvas.assignment', @assignment)
end
it "does no substutue $Canvas.assignment if no assignment is given" do
@tool_launch.generate
expect(@substitutor).to_not have_received(:substitute!).with(anything, '$Canvas.assignment', anything)
end
it "substitutes $Canvas.account with root account if context is not account" do
@tool_launch.generate
expect(@substitutor).to have_received(:substitute!).with(anything, '$Canvas.account', @course.root_account)
end
it "substitutes $Canvas.account with account if context is account" do
tool_launch = BasicLtiOutbound::ToolLaunch.new(:url => 'http://www.yahoo.com',
:tool => @tool,
:user => @user,
:context => @account,
:link_code => '123456',
:return_url => 'http://www.google.com',
:outgoing_email_address => "outgoing_email_address")
tool_launch.generate
expect(@substitutor).to have_received(:substitute!).with(anything, '$Canvas.account', @account)
end
it "substitutes $Canvas.membership" do
@tool_launch.generate
expect(@substitutor).to have_received(:substitute!).with(anything, '$Canvas.membership', @user)
end
it "substitutes $Canvas.enrollment" do
@tool_launch.generate
expect(@substitutor).to have_received(:substitute!).with(anything, '$Canvas.enrollment', @user)
end
it "substitutes $Person.name" do
@tool_launch.generate
expect(@substitutor).to have_received(:substitute!).with(anything, '$Person.name', @user)
end
it "substitutes $Person.address" do
@tool_launch.generate
expect(@substitutor).to have_received(:substitute!).with(anything, '$Person.address', @user)
end
end
end
describe "#for_assignment!" do
it "includes assignment outcome service params for student" do
student_role = BasicLtiOutbound::LTIRole.new.tap do |role|
role.type = BasicLtiOutbound::LTIRole::LEARNER
role.state = :active
end
@user.current_enrollments = [student_role]
@tool_launch.for_assignment!(@assignment, '/my/test/url', '/my/other/test/url')
hash = @tool_launch.generate
expect(hash['lis_result_sourcedid']).to eq '123456'
expect(hash['lis_outcome_service_url']).to eq "/my/test/url"
expect(hash['ext_ims_lis_basic_outcome_url']).to eq "/my/other/test/url"
expect(hash['ext_outcome_data_values_accepted']).to eq 'url,text'
expect(hash['custom_canvas_assignment_title']).to eq "assignment1"
expect(hash['custom_canvas_assignment_points_possible']).to eq "100"
expect(hash['custom_canvas_assignment_id']).to eq "assignment_id"
end
it "includes assignment outcome service params for teacher" do
@tool_launch.for_assignment!(@assignment, '/my/test/url', '/my/other/test/url')
hash = @tool_launch.generate
expect(hash['lis_result_sourcedid']).to be_nil
expect(hash['lis_outcome_service_url']).to eq "/my/test/url"
expect(hash['ext_ims_lis_basic_outcome_url']).to eq "/my/other/test/url"
expect(hash['ext_outcome_data_values_accepted']).to eq 'url,text'
expect(hash['custom_canvas_assignment_title']).to eq "assignment1"
expect(hash['custom_canvas_assignment_points_possible']).to eq "100"
end
end
describe "#for_homework_submission!" do
it "includes content keys if present" do
@tool_launch.for_homework_submission!(@assignment)
hash = @tool_launch.generate
expect(hash['ext_content_return_types']).to eq 'url,text'
expect(hash['ext_content_file_extensions']).to eq 'jpg,pdf'
expect(hash['custom_canvas_assignment_id']).to eq 'assignment_id'
end
it "excludes file_extensions if not present" do
@assignment.allowed_extensions = nil
@tool_launch.for_homework_submission!(@assignment)
hash = @tool_launch.generate
expect(hash['ext_content_file_extensions']).to eq nil
end
end
#TODO: do not test private methods
describe ".generate_params" do
def explicit_signature_settings(timestamp, nonce)
BasicLtiOutbound::ToolLaunch.instance_variable_set(:"@timestamp", timestamp)
BasicLtiOutbound::ToolLaunch.instance_variable_set(:"@nonce", nonce)
end
it "generate a correct signature" do
explicit_signature_settings('1251600739', 'c8350c0e47782d16d2fa48b2090c1d8f')
hash = BasicLtiOutbound::ToolLaunch.send(:generate_params, {
:resource_link_id => '120988f929-274612',
:user_id => '292832126',
:roles => 'Instructor',
:lis_person_name_full => 'Jane Q. Public',
:lis_person_contact_email_primary => 'user@school.edu',
:lis_person_sourced_id => 'school.edu:user',
:context_id => '456434513',
:context_title => 'Design of Personal Environments',
:context_label => 'SI182',
:lti_version => 'LTI-1p0',
:lti_message_type => 'basic-lti-launch-request',
:tool_consumer_instance_guid => 'lmsng.school.edu',
:tool_consumer_instance_description => 'University of School (LMSng)',
:basiclti_submit => 'Launch Endpoint with BasicLTI Data'
}, 'http://dr-chuck.com/ims/php-simple/tool.php', '12345', 'secret')
expect(hash['oauth_signature']).to eql('TPFPK4u3NwmtLt0nDMP1G1zG30U=')
end
it "generate a correct signature with URL query parameters" do
explicit_signature_settings('1251600739', 'c8350c0e47782d16d2fa48b2090c1d8f')
hash = BasicLtiOutbound::ToolLaunch.send(:generate_params, {
:resource_link_id => '120988f929-274612',
:user_id => '292832126',
:roles => 'Instructor',
:lis_person_name_full => 'Jane Q. Public',
:lis_person_contact_email_primary => 'user@school.edu',
:lis_person_sourced_id => 'school.edu:user',
:context_id => '456434513',
:context_title => 'Design of Personal Environments',
:context_label => 'SI182',
:lti_version => 'LTI-1p0',
:lti_message_type => 'basic-lti-launch-request',
:tool_consumer_instance_guid => 'lmsng.school.edu',
:tool_consumer_instance_description => 'University of School (LMSng)',
:basiclti_submit => 'Launch Endpoint with BasicLTI Data'
}, 'http://dr-chuck.com/ims/php-simple/tool.php?a=1&b=2&c=3%20%26a', '12345', 'secret')
expect(hash['oauth_signature']).to eql('uF7LooyefQN5aocx7UlYQ4tQM5k=')
expect(hash['c']).to eq "3 &a"
end
it "generate a correct signature with a non-standard port" do
#signatures generated using http://oauth.googlecode.com/svn/code/javascript/example/signature.html
explicit_signature_settings('1251600739', 'c8350c0e47782d16d2fa48b2090c1d8f')
hash = BasicLtiOutbound::ToolLaunch.send(:generate_params, {
}, 'http://dr-chuck.com:123/ims/php-simple/tool.php', '12345', 'secret')
expect(hash['oauth_signature']).to eql('ghEdPHwN4iJmsM3Nr4AndDx2Kx8=')
hash = BasicLtiOutbound::ToolLaunch.send(:generate_params, {
}, 'http://dr-chuck.com/ims/php-simple/tool.php', '12345', 'secret')
expect(hash['oauth_signature']).to eql('WoSpvCr2HEsLzao6Do0eukxwAsk=')
hash = BasicLtiOutbound::ToolLaunch.send(:generate_params, {
}, 'http://dr-chuck.com:80/ims/php-simple/tool.php', '12345', 'secret')
expect(hash['oauth_signature']).to eql('WoSpvCr2HEsLzao6Do0eukxwAsk=')
hash = BasicLtiOutbound::ToolLaunch.send(:generate_params, {
}, 'http://dr-chuck.com:443/ims/php-simple/tool.php', '12345', 'secret')
expect(hash['oauth_signature']).to eql('KqAV7eIS/+iWIDpvCyDfY8ZpmT4=')
hash = BasicLtiOutbound::ToolLaunch.send(:generate_params, {
}, 'https://dr-chuck.com/ims/php-simple/tool.php', '12345', 'secret')
expect(hash['oauth_signature']).to eql('wFRB/1ZXi/91dop6GwahfboWPvQ=')
hash = BasicLtiOutbound::ToolLaunch.send(:generate_params, {
}, 'https://dr-chuck.com:443/ims/php-simple/tool.php', '12345', 'secret')
expect(hash['oauth_signature']).to eql('wFRB/1ZXi/91dop6GwahfboWPvQ=')
hash = BasicLtiOutbound::ToolLaunch.send(:generate_params, {
}, 'https://dr-chuck.com:80/ims/php-simple/tool.php', '12345', 'secret')
expect(hash['oauth_signature']).to eql('X8Aq2HXSHnr6u/6z/G9zI5aDoR0=')
end
end
end

View File

@ -0,0 +1,46 @@
#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require "spec_helper"
describe BasicLtiOutbound::VariableSubstitutor do
let(:account) {
BasicLtiOutbound::LTIAccount.new.tap do |account|
account.domain = 'my.domain'
end
}
it "substitutes variable" do
params = {'domain' => '$Canvas.api.domain'}
subject.substitute!(params, '$Canvas.api', account)
expect(params).to eq({'domain' => 'my.domain'})
end
it "does not replace invalid mappings" do
params = {'domain' => '$Canvas.api.wrong'}
subject.substitute!(params, '$Canvas.api', account)
expect(params).to eq({'domain' => '$Canvas.api.wrong'})
end
it "does not replace nil mappings" do
account.domain = nil
params = {'domain' => '$Canvas.api.domain'}
subject.substitute!(params, '$Canvas.api', account)
expect(params).to eq({'domain' => '$Canvas.api.domain'})
end
end

View File

@ -0,0 +1,9 @@
shared_examples_for "an LTI context" do
it_behaves_like "it has an attribute setter and getter for", :root_account
it_behaves_like "it has an attribute setter and getter for", :opaque_identifier
it_behaves_like "it has an attribute setter and getter for", :id
it_behaves_like "it has an attribute setter and getter for", :sis_source_id
it_behaves_like "it provides variable mapping", ".id", :id
it_behaves_like "it provides variable mapping", ".sisSourceId", :sis_source_id
end

View File

@ -0,0 +1,9 @@
shared_examples_for "it has an attribute setter and getter for" do |attribute|
it "the attribute '#{attribute}'" do
obj = described_class.new
expect(obj.send(attribute)).to eq nil
obj.send("#{attribute}=", 10)
expect(obj.send(attribute)).to eq 10
end
end

View File

@ -0,0 +1,10 @@
shared_examples_for "it provides variable mapping" do |key, method_name|
it "maps #{key} to ##{method_name}" do
object = described_class.new
#object.send(:"#{method_name}=", 99)
#object.variable_substitution_mapping(key).should == 99
#expect(object.has_variable_mapping?(key)).to eq(true)
expect(object).to receive(method_name)
object.variable_substitution_mapping(key)
end
end

View File

@ -0,0 +1,23 @@
# This file was generated by the `rspec --init` command. Conventionally, all
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
# Require this file using `require "spec_helper"` to ensure that it is only
# loaded once.
#
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
require "basic_lti_outbound"
Dir.glob("#{File.dirname(__FILE__).gsub(/\\/, "/")}/shared_examples/*.rb").each { |file| require file }
Dir.glob("#{File.dirname(__FILE__).gsub(/\\/, "/")}/support/*.rb").each { |file| require file }
RSpec.configure do |config|
config.treat_symbols_as_metadata_keys_with_true_values = true
config.run_all_when_everything_filtered = true
config.filter_run :focus
# Run specs in random order to surface order dependencies. If you find an
# order dependency and want to debug it, you can fix the order by providing
# the seed, which is printed after each run.
# --seed 1234
config.order = 'random'
end

View File

@ -0,0 +1,18 @@
#Any including Application will need to perform the override in order for this gem to function as expected.
module I18n
class << self
attr_writer :localizer
# Public: If a localizer has been set, use it to set the locale and then
# delete it.
#
# Returns nothing.
def set_locale_with_localizer
if @localizer
self.locale = @localizer.call
@localizer = nil
end
end
end
end

View File

@ -8,6 +8,7 @@ module BasicLTI
end
class Assignment < AbstractSubstitutor
# $Canvas.assignment.id
def id
assignment.id if assignment
end