save context_id on lti launch

fixes: PS-1538

**test plan
configure and launch lti tool, upon lti tool launch the
lti_context_id for user should be set, and if course launch
the lti_context_id on course object, if account launch, then
lti_context_id on account.  Once these are set, api calls to the
corresponding object can be made using the syntax lti_context_id:id

Change-Id: Icdf02e4f99691be417c024adb2a2751ba2aa9335
Reviewed-on: https://gerrit.instructure.com/35380
Reviewed-by: Brad Humphrey <brad@instructure.com>
Reviewed-by: Rob Orton <rob@instructure.com>
Tested-by: Jenkins <jenkins@instructure.com>
Product-Review: Adam Phillipps <adam@instructure.com>
QA-Review: Adam Phillipps <adam@instructure.com>
This commit is contained in:
Brandon Broschinsky 2014-05-22 00:55:43 -04:00
parent 0708f5bbb7
commit de263055ce
9 changed files with 105 additions and 14 deletions

View File

@ -29,7 +29,7 @@ class Account < ActiveRecord::Base
:enable_user_notes, :allowed_services, :turnitin_pledge, :turnitin_comments,
:turnitin_account_id, :allow_sis_import, :sis_source_id, :equella_endpoint,
:settings, :uuid, :default_locale, :default_user_storage_quota, :turnitin_host,
:created_by_id, :lti_guid, :default_group_storage_quota
:created_by_id, :lti_guid, :default_group_storage_quota, :lti_context_id
]
EXPORTABLE_ASSOCIATIONS = [

View File

@ -541,14 +541,36 @@ class ContextExternalTool < ActiveRecord::Base
def self.opaque_identifier_for(asset, shard)
shard.activate do
str = asset.asset_string.to_s
raise "Empty value" if str.blank?
Canvas::Security.hmac_sha1(str, shard.settings[:encryption_key])
lti_context_id = context_id_for(asset, shard)
set_asset_context_id(asset, lti_context_id)
end
end
private
def self.set_asset_context_id(asset, context_id)
lti_context_id = context_id
if asset.respond_to?('lti_context_id')
if asset.new_record?
asset.lti_context_id = context_id
else
asset.reload unless asset.lti_context_id?
unless asset.lti_context_id
asset.lti_context_id = context_id
asset.save!
end
lti_context_id = asset.lti_context_id
end
end
lti_context_id
end
def self.context_id_for(asset, shard)
str = asset.asset_string.to_s
raise "Empty value" if str.blank?
Canvas::Security.hmac_sha1(str, shard.settings[:encryption_key])
end
def tool_setting(setting, hash, *keys)
if !hash || !hash.is_a?(Hash)
settings.delete setting

View File

@ -74,7 +74,7 @@ class Course < ActiveRecord::Base
:created_at, :updated_at, :show_public_context_messages, :syllabus_body, :allow_student_forum_attachments, :default_wiki_editing_roles, :wiki_id, :allow_student_organized_groups,
:course_code, :default_view, :root_account_id, :enrollment_term_id, :sis_source_id, :sis_batch_id, :show_all_discussion_entries, :open_enrollment, :storage_quota,
:tab_configuration, :allow_wiki_comments, :turnitin_comments, :self_enrollment, :license, :indexed, :restrict_enrollments_to_course_dates, :template_course_id,
:locale, :settings, :replacement_course_id, :public_description, :self_enrollment_code, :self_enrollment_limit, :abstract_course_id, :course_account_associations
:locale, :settings, :replacement_course_id, :public_description, :self_enrollment_code, :self_enrollment_limit, :abstract_course_id, :course_account_associations, :lti_context_id
]
EXPORTABLE_ASSOCIATIONS = [

View File

@ -33,7 +33,7 @@ class User < ActiveRecord::Base
:id, :name, :sortable_name, :workflow_state, :time_zone, :uuid, :created_at, :updated_at, :visibility, :avatar_image_url, :avatar_image_source, :avatar_image_updated_at,
:phone, :school_name, :school_position, :short_name, :deleted_at, :show_user_services, :gender, :page_views_count, :unread_inbox_items_count, :reminder_time_for_due_dates,
:reminder_time_for_grading, :storage_quota, :visible_inbox_types, :last_user_note, :subscribe_to_emails, :features_used, :preferences, :avatar_state, :locale, :browser_locale,
:unread_conversations_count, :public, :birthdate, :otp_communication_channel_id, :initial_enrollment_type, :crocodoc_id, :last_logged_out
:unread_conversations_count, :public, :birthdate, :otp_communication_channel_id, :initial_enrollment_type, :crocodoc_id, :last_logged_out, :lti_context_id
]
EXPORTABLE_ASSOCIATIONS = [

View File

@ -0,0 +1,15 @@
class AddLtiContextIdToAccountsCoursesUsers < ActiveRecord::Migration
tag :predeploy
def self.up
add_column :accounts, :lti_context_id, :string
add_column :courses, :lti_context_id, :string
add_column :users, :lti_context_id, :string
end
def self.down
remove_column :accounts, :lti_context_id
remove_column :courses, :lti_context_id
remove_column :users, :lti_context_id
end
end

View File

@ -0,0 +1,16 @@
class AddUniqueIndexOnLtiContextId < ActiveRecord::Migration
tag :postdeploy
disable_ddl_transaction!
def self.up
add_index :accounts, :lti_context_id, :unique => true, algorithm: :concurrently
add_index :courses, :lti_context_id, :unique => true, algorithm: :concurrently
add_index :users, :lti_context_id, :unique => true, algorithm: :concurrently
end
def self.down
remove_index :accounts, :lti_context_id
remove_index :courses, :lti_context_id
remove_index :users, :lti_context_id
end
end

View File

@ -63,7 +63,6 @@ module Api
end
end
end
find_params = Api.sis_find_params_for_collection(collection, ids, options[:account] || @domain_root_account, @current_user)
return [] if find_params == :not_found
find_params[:limit] = options[:limit] unless options[:limit].nil?
@ -91,7 +90,7 @@ module Api
SIS_MAPPINGS = {
'courses' =>
{ :lookups => { 'sis_course_id' => 'sis_source_id', 'id' => 'id', 'sis_integration_id' => 'integration_id' },
{ :lookups => { 'sis_course_id' => 'sis_source_id', 'id' => 'id', 'sis_integration_id' => 'integration_id', 'lti_context_id' => 'lti_context_id' },
:is_not_scoped_to_account => ['id'].to_set,
:scope => 'root_account_id' },
'enrollment_terms' =>
@ -99,13 +98,13 @@ module Api
:is_not_scoped_to_account => ['id'].to_set,
:scope => 'root_account_id' },
'users' =>
{ :lookups => { 'sis_user_id' => 'pseudonyms.sis_user_id', 'sis_login_id' => 'pseudonyms.unique_id', 'id' => 'users.id', 'sis_integration_id' => 'pseudonyms.integration_id' },
:is_not_scoped_to_account => ['users.id'].to_set,
{ :lookups => { 'sis_user_id' => 'pseudonyms.sis_user_id', 'sis_login_id' => 'pseudonyms.unique_id', 'id' => 'users.id', 'sis_integration_id' => 'pseudonyms.integration_id', 'lti_context_id' => 'users.lti_context_id', 'lti_user_id' => 'users.lti_context_id' },
:is_not_scoped_to_account => ['users.id', 'users.lti_context_id'].to_set,
:scope => 'pseudonyms.account_id',
:joins => [:pseudonym] },
'accounts' =>
{ :lookups => { 'sis_account_id' => 'sis_source_id', 'id' => 'id', 'sis_integration_id' => 'integration_id' },
:is_not_scoped_to_account => ['id'].to_set,
{ :lookups => { 'sis_account_id' => 'sis_source_id', 'id' => 'id', 'sis_integration_id' => 'integration_id', 'lti_context_id' => 'lti_context_id' },
:is_not_scoped_to_account => ['id', 'lti_context_id'].to_set,
:scope => 'root_account_id' },
'course_sections' =>
{ :lookups => { 'sis_section_id' => 'sis_source_id', 'id' => 'id' , 'sis_integration_id' => 'integration_id' },
@ -127,10 +126,10 @@ module Api
# returns column_name, column_value
return lookups['id'], id if id.is_a?(Numeric) || id.is_a?(ActiveRecord::Base)
id = id.to_s.strip
if id =~ %r{\Ahex:(sis_[\w_]+):(([0-9A-Fa-f]{2})+)\z}
if id =~ %r{\Ahex:(lti_[\w_]+|sis_[\w_]+):(([0-9A-Fa-f]{2})+)\z}
sis_column = $1
sis_id = [$2].pack('H*')
elsif id =~ %r{\A(sis_[\w_]+):(.+)\z}
elsif id =~ %r{\A(lti_[\w_]+|sis_[\w_]+):(.+)\z}
sis_column = $1
sis_id = $2
elsif id =~ ID_REGEX

View File

@ -152,6 +152,26 @@ describe Api do
end
end
end
it "should find user by lti_context_id" do
@user.lti_context_id = Canvas::Security.hmac_sha1(@user.asset_string.to_s, 'key')
@user.save!
@api.api_find(User, "lti_context_id:#{@user.lti_context_id}").should == @user
end
it "should find course by lti_context_id" do
lti_course = course
lti_course.lti_context_id = Canvas::Security.hmac_sha1(lti_course.asset_string.to_s, 'key')
lti_course.save!
@api.api_find(Course, "lti_context_id:#{lti_course.lti_context_id}").should == lti_course
end
it "should find account by lti_context_id" do
account = Account.create!(name: 'account')
account.lti_context_id = Canvas::Security.hmac_sha1(account.asset_string.to_s, 'key')
account.save!
@api.api_find(Account, "lti_context_id:#{account.lti_context_id}").should == account
end
end
context 'api_find_all' do

View File

@ -689,4 +689,23 @@ describe ContextExternalTool do
expect { ContextExternalTool.find_for("horseshoes", @course, :course_navigation) }.to raise_error(ActiveRecord::RecordNotFound)
end
end
describe "opaque_identifier_for" do
it "should create lti_context_id for asset" do
@course.lti_context_id.should == nil
@tool = @course.context_external_tools.create!(:name => "a", :domain => "google.com", :consumer_key => '12345', :shared_secret => 'secret')
context_id = @tool.opaque_identifier_for(@course)
@course.reload
@course.lti_context_id.should == context_id
end
it "should not create new lti_context for asset if exists" do
@course.lti_context_id = 'dummy_context_id'
@course.save!
@tool = @course.context_external_tools.create!(:name => "a", :domain => "google.com", :consumer_key => '12345', :shared_secret => 'secret')
context_id = @tool.opaque_identifier_for(@course)
@course.reload
@course.lti_context_id.should == 'dummy_context_id'
end
end
end