transferred Code Ocean from original repository to GitHub

This commit is contained in:
Hauke Klement
2015-01-22 09:51:49 +01:00
commit 4cbf9970b1
683 changed files with 11979 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
class ApplicationController < ActionController::Base
include ApplicationHelper
include Pundit
MEMBER_ACTIONS = [:destroy, :edit, :show, :update]
after_action :verify_authorized, except: [:help, :welcome]
before_action :set_locale
protect_from_forgery(with: :exception)
rescue_from Pundit::NotAuthorizedError, with: :render_not_authorized
def current_user
@current_user ||= ExternalUser.find_by(id: session[:external_user_id]) || login_from_session || login_from_other_sources
end
def help
end
def render_not_authorized
flash[:danger] = t('application.not_authorized')
redirect_to(:root)
end
private :render_not_authorized
def set_locale
session[:locale] = params[:locale] if params[:locale]
I18n.locale = session[:locale] || I18n.default_locale
end
private :set_locale
def welcome
end
end

View File

@@ -0,0 +1,39 @@
module CodeOcean
class FilesController < ApplicationController
include FileParameters
def authorize!
authorize(@file)
end
private :authorize!
def create
@file = CodeOcean::File.new(file_params)
authorize!
respond_to do |format|
if @file.save
format.html { redirect_to(implement_exercise_path(@file.context.exercise, tab: 2), notice: t('shared.object_created', model: File.model_name.human)) }
format.json { render(:show, location: @file, status: :created) }
else
format.html { render(:new) }
format.json { render(json: @file.errors, status: :unprocessable_entity) }
end
end
end
def destroy
@file = CodeOcean::File.find(params[:id])
authorize!
@file.destroy
respond_to do |format|
format.html { redirect_to(@file.context, notice: t('shared.object_destroyed', model: File.model_name.human)) }
format.json { head(:no_content) }
end
end
def file_params
params[:code_ocean_file].permit(file_attributes).merge(context_type: 'Submission', role: 'user_defined_file')
end
private :file_params
end
end

View File

View 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

View 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

View 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

View 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

View File

@@ -0,0 +1,69 @@
class ConsumersController < ApplicationController
before_action :set_consumer, only: MEMBER_ACTIONS
def authorize!
authorize(@consumer || @consumers)
end
private :authorize!
def create
@consumer = Consumer.new(consumer_params)
authorize!
respond_to do |format|
if @consumer.save
format.html { redirect_to(@consumer, notice: t('shared.object_created', model: Consumer.model_name.human)) }
format.json { render(:show, location: @consumer, status: :created) }
else
format.html { render(:new) }
format.json { render(json: @consumer.errors, status: :unprocessable_entity) }
end
end
end
def destroy
@consumer.destroy
respond_to do |format|
format.html { redirect_to(consumers_url, notice: t('shared.object_destroyed', model: Consumer.model_name.human)) }
format.json { head(:no_content) }
end
end
def edit
end
def consumer_params
params[:consumer].permit(:name, :oauth_key, :oauth_secret)
end
private :consumer_params
def index
@consumers = Consumer.all
authorize!
end
def new
@consumer = Consumer.new(oauth_key: SecureRandom.hex, oauth_secret: SecureRandom.hex)
authorize!
end
def set_consumer
@consumer = Consumer.find(params[:id])
authorize!
end
private :set_consumer
def show
end
def update
respond_to do |format|
if @consumer.update(consumer_params)
format.html { redirect_to(@consumer, notice: t('shared.object_updated', model: Consumer.model_name.human)) }
format.json { render(:show, location: @consumer, status: :ok) }
else
format.html { render(:edit) }
format.json { render(json: @consumer.errors, status: :unprocessable_entity) }
end
end
end
end

View File

@@ -0,0 +1,43 @@
class ErrorsController < ApplicationController
before_action :set_execution_environment
def authorize!
authorize(@error || Error.where(execution_environment_id: @execution_environment.id))
end
private :authorize!
def create
@error = Error.new(error_params)
authorize!
hint = Whistleblower.new(execution_environment: @error.execution_environment).generate_hint(@error.message)
respond_to do |format|
format.json do
if hint
render(json: {hint: hint})
else
render(nothing: true, status: @error.save ? :created : :unprocessable_entity)
end
end
end
end
def error_params
params[:error].permit(:message).merge(execution_environment_id: @execution_environment.id)
end
private :error_params
def index
authorize!
@errors = Error.for_execution_environment(@execution_environment)
end
def set_execution_environment
@execution_environment = ExecutionEnvironment.find(params[:execution_environment_id])
end
private :set_execution_environment
def show
@error = Error.find(params[:id])
authorize!
end
end

View File

@@ -0,0 +1,98 @@
class ExecutionEnvironmentsController < ApplicationController
before_action :set_docker_images, only: [:create, :edit, :new, :update]
before_action :set_execution_environment, only: MEMBER_ACTIONS + [:execute_command, :shell]
before_action :set_testing_framework_adapters, only: [:create, :edit, :new, :update]
def authorize!
authorize(@execution_environment || @execution_environments)
end
private :authorize!
def create
@execution_environment = ExecutionEnvironment.new(execution_environment_params)
authorize!
respond_to do |format|
if @execution_environment.save
format.html { redirect_to(@execution_environment, notice: t('shared.object_created', model: ExecutionEnvironment.model_name.human)) }
format.json { render(:show, location: @execution_environment, status: :created) }
else
format.html { render(:new) }
format.json { render(json: @execution_environment.errors, status: :unprocessable_entity) }
end
end
end
def destroy
@execution_environment.destroy
respond_to do |format|
format.html { redirect_to(execution_environments_url, notice: t('shared.object_destroyed', model: ExecutionEnvironment.model_name.human)) }
format.json { head(:no_content) }
end
end
def edit
end
def execute_command
@docker_client = DockerClient.new(execution_environment: @execution_environment, user: current_user)
render(json: @docker_client.execute_command(params[:command]))
end
def execution_environment_params
params[:execution_environment].permit(:docker_image, :exposed_ports, :editor_mode, :file_extension, :help, :indent_size, :name, :permitted_execution_time, :run_command, :test_command, :testing_framework).merge(user_id: current_user.id, user_type: current_user.class.name)
end
private :execution_environment_params
def index
@execution_environments = ExecutionEnvironment.all.order(:name)
authorize!
end
def new
@execution_environment = ExecutionEnvironment.new
authorize!
end
def set_docker_images
@docker_images = DockerClient.image_tags.sort
rescue DockerClient::Error => error
@docker_images = []
flash[:warning] = error.message
end
private :set_docker_images
def set_execution_environment
@execution_environment = ExecutionEnvironment.find(params[:id])
authorize!
end
private :set_execution_environment
def set_testing_framework_adapters
Rails.application.eager_load!
@testing_framework_adapters = TestingFrameworkAdapter.descendants.sort_by(&:framework_name).map do |klass|
[klass.framework_name, klass.name]
end
end
private :set_testing_framework_adapters
def shell
end
def show
if @execution_environment.testing_framework?
@testing_framework_adapter = Kernel.const_get(@execution_environment.testing_framework)
end
end
def update
respond_to do |format|
if @execution_environment.update(execution_environment_params)
format.html { redirect_to(@execution_environment, notice: t('shared.object_updated', model: ExecutionEnvironment.model_name.human)) }
format.json { render(:show, location: @execution_environment, status: :ok) }
else
format.html { render(:edit) }
format.json { render(json: @execution_environment.errors, status: :unprocessable_entity) }
end
end
end
end

View File

@@ -0,0 +1,152 @@
class ExercisesController < ApplicationController
include Lti
include SubmissionParameters
include SubmissionScoring
before_action :handle_file_uploads, only: [:create, :update]
before_action :set_execution_environments, only: [:create, :edit, :new, :update]
before_action :set_exercise, only: MEMBER_ACTIONS + [:clone, :implement, :run, :statistics, :submit]
before_action :set_file_types, only: [:create, :edit, :new, :update]
def authorize!
authorize(@exercise || @exercises)
end
private :authorize!
def clone
exercise = @exercise.duplicate(public: false, user: current_user)
if exercise.save
redirect_to(exercise, notice: t('shared.object_cloned', model: Exercise.model_name.human))
else
flash[:danger] = t('shared.message_failure')
redirect_to(exercises_path)
end
end
def create
@exercise = Exercise.new(exercise_params)
authorize!
respond_to do |format|
if @exercise.save
format.html { redirect_to(@exercise, notice: t('shared.object_created', model: Exercise.model_name.human)) }
format.json { render(:show, location: @exercise, status: :created) }
else
format.html { render(:new) }
format.json { render(json: @exercise.errors, status: :unprocessable_entity) }
end
end
end
def destroy
@exercise.destroy
respond_to do |format|
format.html { redirect_to(exercises_url, notice: t('shared.object_destroyed', model: Exercise.model_name.human)) }
format.json { head(:no_content) }
end
end
def edit
end
def exercise_params
params[:exercise].permit(:description, :execution_environment_id, :file_id, :instructions, :public, :title, files_attributes: file_attributes).merge(user_id: current_user.id, user_type: current_user.class.name)
end
private :exercise_params
def handle_file_uploads
exercise_params[:files_attributes].try(:each) do |index, file_attributes|
if file_attributes[:content].respond_to?(:read)
file_params = params[:exercise][:files_attributes][index]
if FileType.find_by(id: file_attributes[:file_type_id]).try(:binary?)
file_params[:content] = nil
file_params[:native_file] = file_attributes[:content]
else
file_params[:content] = file_attributes[:content].read
end
end
end
end
private :handle_file_uploads
def implement
if Submission.exists?(exercise_id: @exercise.id, user_id: current_user.id)
@submission = Submission.where(exercise_id: @exercise.id, user_id: current_user.id).order('created_at DESC').first
@files = @submission.collect_files.select(&:visible)
else
@files = @exercise.files.visible
end
@files = @files.sort_by(&:name_with_extension)
end
def index
@search = policy_scope(Exercise).search(params[:q])
@exercises = @search.result.order(:title)
authorize!
end
def redirect_to_lti_return_path
path = lti_return_path(consumer_id: session[:consumer_id], submission_id: @submission.id, url: consumer_return_url(build_tool_provider(consumer: Consumer.find_by(id: session[:consumer_id]), parameters: session[:lti_parameters])))
respond_to do |format|
format.html { redirect_to(path) }
format.json { render(json: {redirect: path}) }
end
end
private :redirect_to_lti_return_path
def new
@exercise = Exercise.new
authorize!
end
def set_execution_environments
@execution_environments = ExecutionEnvironment.all.order(:name)
end
private :set_execution_environments
def set_exercise
@exercise = Exercise.find(params[:id])
authorize!
end
private :set_exercise
def set_file_types
@file_types = FileType.all.order(:name)
end
private :set_file_types
def show
end
def statistics
end
def submit
@submission = Submission.create(submission_params)
score_submission(@submission)
if lti_outcome_service?
response = send_score(@submission.normalized_score)
if response[:status] == 'success'
redirect_to_lti_return_path
else
respond_to do |format|
format.html { redirect_to(implement_exercise_path(@submission.exercise)) }
format.json { render(json: {message: I18n.t('exercises.submit.failure')}, status: 503) }
end
end
else
redirect_to_lti_return_path
end
end
def update
respond_to do |format|
if @exercise.update(exercise_params)
format.html { redirect_to(@exercise, notice: t('shared.object_updated', model: Exercise.model_name.human)) }
format.json { render(:show, location: @exercise, status: :ok) }
else
format.html { render(:edit) }
format.json { render(json: @exercise.errors, status: :unprocessable_entity) }
end
end
end
end

View File

@@ -0,0 +1,16 @@
class ExternalUsersController < ApplicationController
def authorize!
authorize(@user || @users)
end
private :authorize!
def index
@users = ExternalUser.all
authorize!
end
def show
@user = ExternalUser.find(params[:id])
authorize!
end
end

View File

@@ -0,0 +1,78 @@
class FileTypesController < ApplicationController
before_action :set_editor_modes, only: [:create, :edit, :new, :update]
before_action :set_file_type, only: MEMBER_ACTIONS
def authorize!
authorize(@file_type || @file_types)
end
private :authorize!
def create
@file_type = FileType.new(file_type_params)
authorize!
respond_to do |format|
if @file_type.save
format.html { redirect_to(@file_type, notice: t('shared.object_created', model: FileType.model_name.human)) }
format.json { render(:show, location: @file_type, status: :created) }
else
format.html { render(:new) }
format.json { render(json: @file_type.errors, status: :unprocessable_entity) }
end
end
end
def destroy
@file_type.destroy
respond_to do |format|
format.html { redirect_to(file_types_url, notice: t('shared.object_destroyed', model: FileType.model_name.human)) }
format.json { head(:no_content) }
end
end
def edit
end
def file_type_params
params[:file_type].permit(:binary, :editor_mode, :executable, :file_extension, :name, :indent_size, :renderable).merge(user_id: current_user.id, user_type: current_user.class.name)
end
private :file_type_params
def index
@file_types = FileType.all.order(:name)
authorize!
end
def new
@file_type = FileType.new
authorize!
end
def set_editor_modes
@editor_modes = Dir.glob('vendor/assets/javascripts/ace/mode-*.js').map do |filename|
name = filename.gsub(/\w+\/|mode-|.js$/, '')
[name, "ace/mode/#{name}"]
end
end
private :set_editor_modes
def set_file_type
@file_type = FileType.find(params[:id])
authorize!
end
private :set_file_type
def show
end
def update
respond_to do |format|
if @file_type.update(file_type_params)
format.html { redirect_to(@file_type, notice: t('shared.object_updated', model: FileType.model_name.human)) }
format.json { render(:show, location: @file_type, status: :ok) }
else
format.html { render(:edit) }
format.json { render(json: @file_type.errors, status: :unprocessable_entity) }
end
end
end
end

View File

@@ -0,0 +1,75 @@
class HintsController < ApplicationController
before_action :set_execution_environment
before_action :set_hint, only: MEMBER_ACTIONS
def authorize!
authorize(@hint || @hints)
end
private :authorize!
def create
@hint = Hint.new(hint_params)
authorize!
respond_to do |format|
if @hint.save
format.html { redirect_to(execution_environment_hint_path(@execution_environment, @hint.id), notice: t('shared.object_created', model: Hint.model_name.human)) }
format.json { render(:show, location: @hint, status: :created) }
else
format.html { render(:new) }
format.json { render(json: @hint.errors, status: :unprocessable_entity) }
end
end
end
def destroy
@hint.destroy
respond_to do |format|
format.html { redirect_to(execution_environment_hints_path(@execution_environment), notice: t('shared.object_destroyed', model: Hint.model_name.human)) }
format.json { head(:no_content) }
end
end
def edit
end
def hint_params
params[:hint].permit(:locale, :message, :name, :regular_expression).merge(execution_environment_id: @execution_environment.id)
end
private :hint_params
def index
@hints = Hint.where(execution_environment_id: @execution_environment.id).order(:name)
authorize!
end
def new
@hint = Hint.new
authorize!
end
def set_execution_environment
@execution_environment = ExecutionEnvironment.find(params[:execution_environment_id])
end
private :set_execution_environment
def set_hint
@hint = Hint.find(params[:id])
authorize!
end
private :set_hint
def show
end
def update
respond_to do |format|
if @hint.update(hint_params)
format.html { redirect_to(execution_environment_hint_path(params[:execution_environment_id], @hint.id), notice: t('shared.object_updated', model: Hint.model_name.human)) }
format.json { render(:show, location: @hint, status: :ok) }
else
format.html { render(:edit) }
format.json { render(json: @hint.errors, status: :unprocessable_entity) }
end
end
end
end

View File

@@ -0,0 +1,131 @@
class InternalUsersController < ApplicationController
before_action :require_activation_token, only: :activate
before_action :require_reset_password_token, only: :reset_password
before_action :set_user, only: MEMBER_ACTIONS
skip_before_action :verify_authenticity_token, only: :activate
skip_after_action :verify_authorized, only: [:activate, :forgot_password, :reset_password]
def activate
if request.patch? || request.put?
respond_to do |format|
if @user.update(params[:internal_user].permit(:password, :password_confirmation))
@user.activate!
format.html { redirect_to(sign_in_path, notice: t('.success')) }
format.json { render(nothing: true, status: :ok) }
else
format.html { render(:activate) }
format.json { render(json: @user.errors, status: :unprocessable_entity) }
end
end
end
end
def authorize!
authorize(@user || @users)
end
private :authorize!
def create
@user = InternalUser.new(internal_user_params)
authorize!
@user.send(:setup_activation)
respond_to do |format|
if @user.save
@user.send(:send_activation_needed_email!)
format.html { redirect_to(@user, notice: t('shared.object_created', model: InternalUser.model_name.human)) }
format.json { render(:show, location: @user, status: :created) }
else
format.html { render(:new) }
format.json { render(json: @user.errors, status: :unprocessable_entity) }
end
end
end
def destroy
@user.destroy
respond_to do |format|
format.html { redirect_to(internal_users_url, notice: t('shared.object_destroyed', model: InternalUser.model_name.human)) }
format.json { head(:no_content) }
end
end
def edit
end
def forgot_password
if request.get? && current_user
flash[:warning] = t('shared.already_signed_in')
redirect_to(:root)
elsif request.post?
if params[:email].present?
InternalUser.find_by(email: params[:email]).try(:deliver_reset_password_instructions!)
flash[:notice] = t('.success')
redirect_to(:root)
end
end
end
def index
@search = InternalUser.search(params[:q])
@users = @search.result.order(:name)
authorize!
end
def internal_user_params
params[:internal_user].permit(:consumer_id, :email, :name, :role)
end
private :internal_user_params
def new
@user = InternalUser.new
authorize!
end
def require_activation_token
@user = InternalUser.load_from_activation_token(params[:token] || params[:internal_user].try(:[], :activation_token))
render_not_authorized unless @user
end
private :require_activation_token
def require_reset_password_token
@user = InternalUser.load_from_reset_password_token(params[:token] || params[:internal_user].try(:[], :reset_password_token))
render_not_authorized unless @user
end
private :require_reset_password_token
def reset_password
if request.patch? || request.put?
respond_to do |format|
if @user.update(params[:internal_user].permit(:password, :password_confirmation))
@user.change_password!(params[:internal_user][:password])
format.html { redirect_to(sign_in_path, notice: t('.success')) }
format.json { render(nothing: true, status: :ok) }
else
format.html { render(:reset_password) }
format.json { render(json: @user.errors, status: :unprocessable_entity) }
end
end
end
end
def set_user
@user = InternalUser.find(params[:id])
authorize!
end
private :set_user
def show
end
def update
respond_to do |format|
if @user.update(internal_user_params)
format.html { redirect_to(@user, notice: t('shared.object_updated', model: InternalUser.model_name.human)) }
format.json { render(:show, location: @user, status: :ok) }
else
format.html { render(:edit) }
format.json { render(json: @user.errors, status: :unprocessable_entity) }
end
end
end
end

View File

@@ -0,0 +1,49 @@
class SessionsController < ApplicationController
include Lti
[:require_oauth_parameters, :require_valid_consumer_key, :require_valid_oauth_signature, :require_valid_exercise_token].each do |method_name|
before_action(method_name, only: :create_through_lti)
end
skip_after_action :verify_authorized
skip_before_action :verify_authenticity_token, only: :create_through_lti
def create
if user = login(params[:email], params[:password], params[:remember_me])
redirect_back_or_to(:root, notice: t('.success'))
else
flash.now[:danger] = t('.failure')
render(:new)
end
end
def create_through_lti
set_current_user
store_lti_session_data(consumer: @consumer, parameters: params)
store_nonce(params[:oauth_nonce])
flash[:notice] = I18n.t("sessions.create_through_lti.session_#{lti_outcome_service? ? 'with' : 'without'}_outcome", consumer: @consumer)
redirect_to(implement_exercise_path(@exercise.id))
end
def destroy
if current_user.external?
clear_lti_session_data
else
logout
end
redirect_to(:root, notice: t('.success'))
end
def destroy_through_lti
@consumer = Consumer.find_by(id: params[:consumer_id])
@submission = Submission.find(params[:submission_id])
clear_lti_session_data
end
def new
if current_user
flash[:warning] = t('shared.already_signed_in')
redirect_to(:root)
end
end
end

View File

@@ -0,0 +1,145 @@
class SubmissionsController < ApplicationController
include ActionController::Live
include Lti
include SubmissionParameters
include SubmissionScoring
around_action :with_server_sent_events, only: :run
before_action :set_submission, only: [:download_file, :render_file, :run, :score, :show, :statistics, :stop, :test]
before_action :set_docker_client, only: [:run, :test]
before_action :set_files, only: [:download_file, :render_file, :show]
before_action :set_file, only: [:download_file, :render_file]
before_action :set_mime_type, only: [:download_file, :render_file]
skip_before_action :verify_authenticity_token, only: [:download_file, :render_file]
def authorize!
authorize(@submission || @submissions)
end
private :authorize!
def create
@submission = Submission.new(submission_params)
authorize!
respond_to do |format|
format.json do
if @submission.save
render(:show, location: @submission, status: :created)
else
render(nothing: true, status: :unprocessable_entity)
end
end
end
end
def download_file
if @file.native_file?
send_file(@file.native_file.path)
else
send_data(@file.content, filename: @file.name_with_extension)
end
end
def index
@search = Submission.search(params[:q])
@submissions = @search.result.paginate(page: params[:page])
authorize!
end
def render_file
if @file.native_file?
send_file(@file.native_file.path, disposition: 'inline')
else
render(text: @file.content)
end
end
def run
container_id = nil
stderr = ''
output = @docker_client.execute_run_command(@submission, params[:filename]) do |stream, chunk|
unless container_id
container_id = @docker_client.container_id
@server_sent_event.write({id: container_id, ports: @docker_client.assigned_ports}, event: 'info')
end
@server_sent_event.write({stream => chunk}, event: 'output')
stderr += chunk if stream == :stderr
end
@server_sent_event.write(output, event: 'status')
if stderr.present?
if hint = Whistleblower.new(execution_environment: @submission.execution_environment).generate_hint(stderr)
@server_sent_event.write(hint, event: 'hint')
else
store_error(stderr)
end
end
end
def score
render(json: score_submission(@submission))
end
def set_docker_client
@docker_client = DockerClient.new(execution_environment: @submission.execution_environment, user: current_user)
end
private :set_docker_client
def set_file
@file = @files.detect { |file| file.name_with_extension == params[:filename] }
render(nothing: true, status: 404) unless @file
end
private :set_file
def set_files
@files = @submission.collect_files.select(&:visible)
end
private :set_files
def set_mime_type
@mime_type = Mime::Type.lookup_by_extension(@file.file_type.file_extension.gsub(/^\./, ''))
response.headers['Content-Type'] = @mime_type.to_s
end
private :set_mime_type
def set_submission
@submission = Submission.find(params[:id])
authorize!
end
private :set_submission
def show
end
def statistics
end
def stop
container = Docker::Container.get(params[:container_id])
DockerClient.destroy_container(container)
rescue Docker::Error::NotFoundError
ensure
render(nothing: true)
end
def store_error(stderr)
::Error.create(execution_environment_id: @submission.exercise.execution_environment_id, message: stderr)
end
private :store_error
def test
output = @docker_client.execute_test_command(@submission, params[:filename])
render(json: [output])
end
def with_server_sent_events
response.headers['Content-Type'] = 'text/event-stream'
@server_sent_event = SSE.new(response.stream)
@server_sent_event.write(nil, event: 'start')
yield
@server_sent_event.write({code: 200}, event: 'close')
rescue
@server_sent_event.write({code: 500}, event: 'close')
ensure
@server_sent_event.close
end
private :with_server_sent_events
end