Files
codeocean/app/controllers/concerns/lti.rb
Tom Staubitz 41a61a8507 Fixed the destroy session logic.
1. an exercise_id is provided ==> only the LtiParameter object for the current user, consumer, and exercise is deleted.
2. no exercise_id is provided ==> external user and consumer are removed from the session, all LtiParameter objects for this user and consumer are deleted.

This enables users to have several tabs with exercises open and submitting the results to the tool consumer.
When an exercise has been submitted, the user cannot use the back button to get back to CodeOcean and work on the submitted or any other exercise.
For now a warning has been added to the info text to tell users not to do this. (As the LtiParameters have been deleted, the points can no more be submitted to the consumer.)
@TODO disable/redirect back button?
2016-12-31 17:21:46 +01:00

157 lines
6.1 KiB
Ruby

require 'oauth/request_proxy/rack_request'
module Lti
extend ActiveSupport::Concern
include LtiHelper
MAXIMUM_SCORE = 1
MAXIMUM_SESSION_AGE = 60.minutes
SESSION_PARAMETERS = %w(launch_presentation_return_url lis_outcome_service_url lis_result_sourcedid)
def build_tool_provider(options = {})
if options[:consumer] && options[:parameters]
IMS::LTI::ToolProvider.new(options[:consumer].oauth_key, options[:consumer].oauth_secret, options[:parameters])
end
end
private :build_tool_provider
# exercise_id.nil? ==> the user has logged out. All session data is to be destroyed
# exercise_id.exists? ==> the user has submitted the results of an exercise to the consumer.
# Only the lti_parameters are deleted.
def clear_lti_session_data(exercise_id = nil)
#Todo replace session with lti_parameter /done
if (exercise_id.nil?)
LtiParameter.destroy_all(consumers_id: session[:consumer_id], external_user_id: session[:external_user_external_id])
session.delete(:consumer_id)
session.delete(:external_user_id)
else
LtiParameter.destroy_all(consumers_id: session[:consumer_id],
external_user_id: session[:external_user_external_id],
exercises_id: exercise_id)
end
end
private :clear_lti_session_data
def consumer_return_url(provider, options = {})
consumer_return_url = provider.try(:launch_presentation_return_url) || params[:launch_presentation_return_url]
consumer_return_url += "?#{options.to_query}" if consumer_return_url && options.present?
consumer_return_url
end
def external_user_email(provider)
provider.lis_person_contact_email_primary
end
private :external_user_email
def external_user_name(provider)
if provider.lis_person_name_full
provider.lis_person_name_full
elsif provider.lis_person_name_given && provider.lis_person_name_family
"#{provider.lis_person_name_given} #{provider.lis_person_name_family}"
else
provider.lis_person_name_given || provider.lis_person_name_family
end
end
private :external_user_name
def refuse_lti_launch(options = {})
return_to_consumer(lti_errorlog: options[:message], lti_errormsg: t('sessions.oauth.failure'))
end
private :refuse_lti_launch
def require_oauth_parameters
refuse_lti_launch(message: t('sessions.oauth.missing_parameters')) unless params[:oauth_consumer_key] && params[:oauth_signature]
end
private :require_oauth_parameters
def require_unique_oauth_nonce
refuse_lti_launch(message: t('sessions.oauth.used_nonce')) if NonceStore.has?(params[:oauth_nonce])
end
private :require_unique_oauth_nonce
def require_valid_consumer_key
@consumer = Consumer.find_by(oauth_key: params[:oauth_consumer_key])
refuse_lti_launch(message: t('sessions.oauth.invalid_consumer')) unless @consumer
end
private :require_valid_consumer_key
def require_valid_exercise_token
@exercise = Exercise.find_by(token: params[:custom_token])
refuse_lti_launch(message: t('sessions.oauth.invalid_exercise_token')) unless @exercise
end
private :require_valid_exercise_token
def require_valid_oauth_signature
@provider = build_tool_provider(consumer: @consumer, parameters: params)
refuse_lti_launch(message: t('sessions.oauth.invalid_signature')) unless @provider.valid_request?(request)
end
private :require_valid_oauth_signature
def return_to_consumer(options = {})
consumer_return_url = @provider.try(:launch_presentation_return_url) || params[:launch_presentation_return_url]
if consumer_return_url
consumer_return_url += "?#{options.to_query}" if options.present?
redirect_to(consumer_return_url)
else
flash[:danger] = options[:lti_errormsg]
flash[:info] = options[:lti_msg]
redirect_to(:root)
end
end
private :return_to_consumer
def send_score(exercise_id, score)
::NewRelic::Agent.add_custom_parameters({ score: score, session: session })
fail(Error, "Score #{score} must be between 0 and #{MAXIMUM_SCORE}!") unless (0..MAXIMUM_SCORE).include?(score)
#Todo replace session with lti_parameter /done
lti_parameter = LtiParameter.where(consumers_id: session[:consumer_id],
external_user_id: session[:external_user_external_id],
exercises_id: exercise_id).first
# lti_parameters = JSON.parse(lti_parameter.lti_parameters)
consumer = Consumer.find_by(id: session[:consumer_id])
provider = build_tool_provider(consumer: consumer, parameters: lti_parameter.lti_parameters)
# provider = build_tool_provider(consumer: Consumer.find_by(id: session[:consumer_id]), parameters: session[:lti_parameters])
if provider.nil?
{status: 'error'}
elsif provider.outcome_service?
response = provider.post_replace_result!(score)
{code: response.response_code, message: response.post_response.body, status: response.code_major}
else
{status: 'unsupported'}
end
end
private :send_score
def set_current_user
@current_user = ExternalUser.find_or_create_by(consumer_id: @consumer.id, external_id: @provider.user_id)
@current_user.update(email: external_user_email(@provider), name: external_user_name(@provider))
end
private :set_current_user
def store_lti_session_data(options = {})
exercise = Exercise.where(token: options[:parameters][:custom_token]).first
exercise_id = exercise.id unless exercise.nil?
lti_parameters = LtiParameter.find_or_create_by(consumers_id: options[:consumer].id,
external_user_id: options[:parameters][:user_id].to_s,
exercises_id: exercise_id)
lti_parameters.lti_parameters = options[:parameters].slice(*SESSION_PARAMETERS).to_json
lti_parameters.save!
session[:consumer_id] = options[:consumer].id
session[:external_user_external_id] = @current_user.external_id
session[:external_user_id] = @current_user.id
end
private :store_lti_session_data
def store_nonce(nonce)
NonceStore.add(nonce)
end
private :store_nonce
class Error < RuntimeError
end
end