use the new ims lti message authenticator

the new gem created an lti message authenticator that fixes a bug
around content-item-selection message signature validation.

fixes PLAT-1639

test plan:

the test tool content item edit should still work
the o365 content-item edit should work with it

Change-Id: I5ebe868b31518861d32bf1b02244c21cceb55eb6
Reviewed-on: https://gerrit.instructure.com/84481
Reviewed-by: Andrew Butterfield <abutterfield@instructure.com>
Tested-by: Jenkins
QA-Review: August Thornton <august@instructure.com>
Product-Review: Nathan Mills <nathanm@instructure.com>
This commit is contained in:
Nathan Mills 2016-07-06 17:09:32 -06:00
parent 1af7eb10ec
commit 1ff9e81d6d
3 changed files with 58 additions and 12 deletions

View File

@ -54,7 +54,7 @@ gem 'i18n', '0.7.0'
gem 'i18nema', '0.0.8'
gem 'i18nliner', '0.0.12'
gem 'icalendar', '1.5.4', require: false
gem 'ims-lti', '2.0.0.beta.41', require: 'ims'
gem 'ims-lti', '2.1.0.beta.3', require: 'ims'
gem 'json', '1.8.2'
gem 'oj', '2.14.1'
gem 'jwt', '1.2.1', require: false

View File

@ -17,39 +17,47 @@
#
module Lti
class MessageAuthenticator
attr_reader :message
CACHE_KEY_PREFIX = 'lti_nonce_'
NONCE_EXPIRATION = 10.minutes
def initialize(launch_url, params)
@message = IMS::LTI::Models::Messages::Message.generate(params)
@version = @message.lti_version
@message.launch_url = launch_url
@params = params.with_indifferent_access
@launch_url = launch_url
@version = @params[:lti_version]
@nonce = @params[:oauth_nonce]
@oauth_consumer_key = @params[:oauth_consumer_key]
end
def valid?
@valid ||= begin
valid = message.valid_signature?(shared_secret)
valid &&= message.oauth_timestamp.to_i > NONCE_EXPIRATION.ago.to_i
valid = lti_message_authenticator.valid_signature?
valid &&= @params[:oauth_timestamp].to_i > NONCE_EXPIRATION.ago.to_i
valid &&= !Rails.cache.exist?(cache_key)
Rails.cache.write(cache_key, message.oauth_consumer_key, expires_in: NONCE_EXPIRATION) if valid
Rails.cache.write(cache_key, 'OK', expires_in: NONCE_EXPIRATION) if valid
valid
end
end
def message
lti_message_authenticator.message
end
private
def lti_message_authenticator
@lti_message_authenticator ||= IMS::LTI::Services::MessageAuthenticator.new(@launch_url, @params, shared_secret)
end
def shared_secret
@shared_secret ||=
if @version.strip == 'LTI-1p0'
tool = ContextExternalTool.where(consumer_key: message.oauth_consumer_key).first
tool = ContextExternalTool.where(consumer_key: @params[:oauth_consumer_key]).first
tool && tool.shared_secret
end
end
def cache_key
CACHE_KEY_PREFIX+@message.oauth_nonce
"#{CACHE_KEY_PREFIX}_#{@oauth_consumer_key}_#{@nonce}"
end
end

View File

@ -22,7 +22,7 @@ module Lti
let(:launch_url) {'http://test.com/test'}
let(:course) {Course.create!}
let(:tool) do
let!(:tool) do
course.context_external_tools.create!(
{
name: 'test tool',
@ -61,6 +61,44 @@ module Lti
expect(subject.valid?).to be true
end
context 'content-item unique json serialization' do
let(:launch_url) {"http://test.com/test"}
let(:secret) {'secret'}
let(:signed_params) {
{
:oauth_callback=>"about:blank",
:oauth_consumer_key=>"key",
:oauth_nonce=>"89fc77055d2a051de296fc5d99987a20",
:oauth_signature_method=>"HMAC-SHA1",
:oauth_timestamp=>"1467842103",
:oauth_version=>"1.0",
:oauth_signature=>"TL8PLA/V43D21+JkGg8i9Cj+Dqg=",
"lti_message_type"=>"ContentItemSelection",
"lti_version"=>"LTI-1p0",
"content_items"=>"{\"@graph\":[{\"windowTarget\":\"\",\"text\":\"Arch Linux\",\"title\":\"Its your "+
"computer\",\"url\":\"http://lti-tool-provider-example.dev/messages/blti\""+
",\"thumbnail\":{\"height\":128,\"width\":128,\"@id\""+
":\"http://www.runeaudio.com/assets/img/banner-archlinux.png\"}"+
",\"placementAdvice\":{\"displayHeight\":600,\"displayWidth\":800"+
",\"presentationDocumentTarget\":\"iframe\"},\"mediaType\""+
":\"application/vnd.ims.lti.v1.ltilink\",\"@type\":\"LtiLinkItem\",\"@id\""+
":\"http://lti-tool-provider-example.dev/messages/blti\"}],\"@context\""+
":\"http://purl.imsglobal.org/ctx/lti/v1/ContentItem\"}",
"lti_msg"=>"",
"lti_log"=>"",
"lti_errormsg"=>"",
"lti_errorlog"=>""
}
}
it "validates the message" do
message_authenticator = MessageAuthenticator.new(launch_url, signed_params)
Timecop.freeze(Time.at(signed_params[:oauth_timestamp].to_i)) do
expect(message_authenticator.valid?).to eq true
end
end
end
it "returns the same value if called multiple times" do
enable_cache do
expect(2.times.map { |_| subject.valid? }).to eq [true, true]