transferred Code Ocean from original repository to GitHub
This commit is contained in:
0
app/controllers/concerns/.keep
Normal file
0
app/controllers/concerns/.keep
Normal file
6
app/controllers/concerns/file_parameters.rb
Normal file
6
app/controllers/concerns/file_parameters.rb
Normal file
@@ -0,0 +1,6 @@
|
||||
module FileParameters
|
||||
def file_attributes
|
||||
%w[content context_id feedback_message file_id file_type_id hidden id name native_file path read_only role weight]
|
||||
end
|
||||
private :file_attributes
|
||||
end
|
131
app/controllers/concerns/lti.rb
Normal file
131
app/controllers/concerns/lti.rb
Normal file
@@ -0,0 +1,131 @@
|
||||
require 'oauth/request_proxy/rack_request'
|
||||
|
||||
module Lti
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
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
|
||||
|
||||
def clear_lti_session_data
|
||||
session.delete(:consumer_id)
|
||||
session.delete(:external_user_id)
|
||||
session.delete(:lti_parameters)
|
||||
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 lti_outcome_service?
|
||||
session[:lti_parameters].try(:has_key?, 'lis_outcome_service_url')
|
||||
end
|
||||
private :lti_outcome_service?
|
||||
|
||||
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(score)
|
||||
raise Error.new("Score #{score} must be between 0 and #{MAXIMUM_SCORE}!") unless (0..MAXIMUM_SCORE).include?(score)
|
||||
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 = {})
|
||||
session[:consumer_id] = options[:consumer].id
|
||||
session[:external_user_id] = @current_user.id
|
||||
session[:lti_parameters] = options[:parameters].slice(*SESSION_PARAMETERS)
|
||||
end
|
||||
private :store_lti_session_data
|
||||
|
||||
def store_nonce(nonce)
|
||||
NonceStore.add(nonce)
|
||||
end
|
||||
private :store_nonce
|
||||
|
||||
class Error < RuntimeError
|
||||
end
|
||||
end
|
20
app/controllers/concerns/submission_parameters.rb
Normal file
20
app/controllers/concerns/submission_parameters.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
module SubmissionParameters
|
||||
include FileParameters
|
||||
|
||||
def reject_illegal_file_attributes!(submission_params)
|
||||
if exercise = Exercise.find_by(id: submission_params[:exercise_id])
|
||||
submission_params[:files_attributes].try(:reject!) do |index, file_attributes|
|
||||
file = CodeOcean::File.find_by(id: file_attributes[:file_id])
|
||||
file.nil? || file.hidden || file.read_only
|
||||
end
|
||||
end
|
||||
end
|
||||
private :reject_illegal_file_attributes!
|
||||
|
||||
def submission_params
|
||||
submission_params = params[:submission].permit(:cause, :exercise_id, files_attributes: file_attributes).merge(user_id: current_user.id, user_type: current_user.class.name)
|
||||
reject_illegal_file_attributes!(submission_params)
|
||||
submission_params
|
||||
end
|
||||
private :submission_params
|
||||
end
|
19
app/controllers/concerns/submission_scoring.rb
Normal file
19
app/controllers/concerns/submission_scoring.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
module SubmissionScoring
|
||||
def execute_test_files(submission)
|
||||
submission.collect_files.select(&:teacher_defined_test?).map do |file|
|
||||
output = @docker_client.execute_test_command(submission, file.name_with_extension)
|
||||
output.merge!(@assessor.assess(output))
|
||||
output.merge!(filename: file.name_with_extension, message: output[:score] == Assessor::MAXIMUM_SCORE ? I18n.t('exercises.implement.default_feedback') : file.feedback_message, weight: file.weight)
|
||||
end
|
||||
end
|
||||
private :execute_test_files
|
||||
|
||||
def score_submission(submission)
|
||||
@assessor = Assessor.new(execution_environment: submission.execution_environment)
|
||||
@docker_client = DockerClient.new(execution_environment: submission.execution_environment, user: current_user)
|
||||
outputs = execute_test_files(submission)
|
||||
score = outputs.map { |output| output[:score] * output[:weight] }.reduce(:+)
|
||||
submission.update(score: score)
|
||||
outputs
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user