
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?
157 lines
6.1 KiB
Ruby
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
|