Merge pull request #84 from openHPI/lti_param_model

Lti param model
This commit is contained in:
rteusner
2017-01-19 14:38:47 +01:00
committed by GitHub
48 changed files with 1985 additions and 233 deletions

View File

@ -6,6 +6,7 @@ services:
addons:
code_climate:
repo_token: 53a2c2608c848714e96f6a1fc0365dcfdfec051f7827d50cea965ea625f49734
postgresql: "9.5"
before_install:
- export DISPLAY=:99.0

View File

@ -21,7 +21,7 @@ gem 'pg', platform: :ruby
gem 'pry-byebug'
gem 'puma', '~> 2.15.3'
gem 'pundit'
gem 'rails', '~> 4.1.13'
gem 'rails', '4.2.5'
gem 'rails-i18n', '~> 4.0.0'
gem 'ransack'
gem 'rubytree'

View File

@ -2,40 +2,49 @@ GEM
remote: https://rubygems.org/
specs:
ZenTest (4.11.0)
actionmailer (4.1.14.1)
actionpack (= 4.1.14.1)
actionview (= 4.1.14.1)
actionmailer (4.2.5)
actionpack (= 4.2.5)
actionview (= 4.2.5)
activejob (= 4.2.5)
mail (~> 2.5, >= 2.5.4)
actionpack (4.1.14.1)
actionview (= 4.1.14.1)
activesupport (= 4.1.14.1)
rack (~> 1.5.2)
rails-dom-testing (~> 1.0, >= 1.0.5)
actionpack (4.2.5)
actionview (= 4.2.5)
activesupport (= 4.2.5)
rack (~> 1.6)
rack-test (~> 0.6.2)
actionview (4.1.14.1)
activesupport (= 4.1.14.1)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (4.2.5)
activesupport (= 4.2.5)
builder (~> 3.1)
erubis (~> 2.7.0)
activemodel (4.1.14.1)
activesupport (= 4.1.14.1)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
activejob (4.2.5)
activesupport (= 4.2.5)
globalid (>= 0.3.0)
activemodel (4.2.5)
activesupport (= 4.2.5)
builder (~> 3.1)
activerecord (4.1.14.1)
activemodel (= 4.1.14.1)
activesupport (= 4.1.14.1)
arel (~> 5.0.0)
activerecord (4.2.5)
activemodel (= 4.2.5)
activesupport (= 4.2.5)
arel (~> 6.0)
activerecord-jdbc-adapter (1.3.19)
activerecord (>= 2.2)
activerecord-jdbcpostgresql-adapter (1.3.19)
activerecord-jdbc-adapter (~> 1.3.19)
jdbc-postgres (>= 9.1)
activesupport (4.1.14.1)
i18n (~> 0.6, >= 0.6.9)
activesupport (4.2.5)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
thread_safe (~> 0.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
addressable (2.4.0)
arel (5.0.1.20140414130214)
ast (2.2.0)
arel (6.0.4)
ast (2.3.0)
autotest-rails (4.2.1)
ZenTest (~> 4.5)
bcrypt (3.1.10)
@ -129,6 +138,8 @@ GEM
ffi (1.9.10)
ffi (1.9.10-java)
forgery (0.6.0)
globalid (0.3.7)
activesupport (>= 4.1.0)
highline (1.7.8)
hike (1.2.3)
http-cookie (1.0.2)
@ -151,13 +162,15 @@ GEM
json (1.8.3-java)
jwt (1.5.1)
kramdown (1.9.0)
mail (2.6.3)
mime-types (>= 1.16, < 3)
loofah (2.0.3)
nokogiri (>= 1.5.9)
mail (2.6.4)
mime-types (>= 1.16, < 4)
method_source (0.8.2)
mime-types (2.99)
mini_portile2 (2.0.0)
minitest (5.9.0)
multi_json (1.11.2)
mime-types (2.99.3)
mini_portile2 (2.1.0)
minitest (5.10.1)
multi_json (1.12.1)
multi_xml (0.5.5)
multipart-post (2.0.0)
net-scp (1.2.1)
@ -165,9 +178,9 @@ GEM
net-ssh (3.0.2)
netrc (0.10.3)
newrelic_rpm (3.14.3.313)
nokogiri (1.6.7.2)
mini_portile2 (~> 2.0.0.rc2)
nokogiri (1.6.7.2-java)
nokogiri (1.7.0.1)
mini_portile2 (~> 2.1.0)
nokogiri (1.7.0.1-java)
nyan-cat-formatter (0.11)
rspec (>= 2.99, >= 2.14.2, < 4)
oauth (0.4.7)
@ -179,7 +192,7 @@ GEM
rack (>= 1.2, < 3)
pagedown-rails (1.1.4)
railties (> 3.1)
parser (2.3.0.6)
parser (2.3.3.1)
ast (~> 2.2)
pg (0.18.4)
polyamorous (1.3.0)
@ -201,31 +214,40 @@ GEM
puma (2.15.3-java)
pundit (1.1.0)
activesupport (>= 3.0.0)
rack (1.5.5)
rack (1.6.5)
rack-mini-profiler (0.10.1)
rack (>= 1.2.0)
rack-test (0.6.3)
rack (>= 1.0)
rails (4.1.14.1)
actionmailer (= 4.1.14.1)
actionpack (= 4.1.14.1)
actionview (= 4.1.14.1)
activemodel (= 4.1.14.1)
activerecord (= 4.1.14.1)
activesupport (= 4.1.14.1)
rails (4.2.5)
actionmailer (= 4.2.5)
actionpack (= 4.2.5)
actionview (= 4.2.5)
activejob (= 4.2.5)
activemodel (= 4.2.5)
activerecord (= 4.2.5)
activesupport (= 4.2.5)
bundler (>= 1.3.0, < 2.0)
railties (= 4.1.14.1)
sprockets-rails (~> 2.0)
railties (= 4.2.5)
sprockets-rails
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
rails-dom-testing (1.0.8)
activesupport (>= 4.2.0.beta, < 5.0)
nokogiri (~> 1.6)
rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.3)
loofah (~> 2.0)
rails-i18n (4.0.8)
i18n (~> 0.7)
railties (~> 4.0)
railties (4.1.14.1)
actionpack (= 4.1.14.1)
activesupport (= 4.1.14.1)
railties (4.2.5)
actionpack (= 4.2.5)
activesupport (= 4.2.5)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rainbow (2.1.0)
rake (10.5.0)
rainbow (2.2.1)
rake (12.0.0)
ransack (1.7.0)
actionpack (>= 3.0)
activerecord (>= 3.0)
@ -244,7 +266,7 @@ GEM
rspec-mocks (~> 3.4.0)
rspec-autotest (1.0.0)
rspec-core (>= 2.99.0.beta1, < 4.0.0)
rspec-core (3.4.2)
rspec-core (3.4.4)
rspec-support (~> 3.4.0)
rspec-expectations (3.4.0)
diff-lcs (>= 1.2.0, < 2.0)
@ -261,14 +283,15 @@ GEM
rspec-mocks (~> 3.4.0)
rspec-support (~> 3.4.0)
rspec-support (3.4.1)
rubocop (0.37.2)
parser (>= 2.3.0.4, < 3.0)
rubocop (0.46.0)
parser (>= 2.3.1.1, < 3.0)
powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.7)
unicode-display_width (~> 0.3)
rubocop-rspec (1.4.0)
ruby-progressbar (1.7.5)
unicode-display_width (~> 1.0, >= 1.0.1)
rubocop-rspec (1.9.0)
rubocop (>= 0.42.0)
ruby-progressbar (1.8.1)
rubytree (0.9.7)
json (~> 1.8)
structured_warnings (~> 0.2)
@ -317,7 +340,7 @@ GEM
net-ssh (>= 2.8.0)
structured_warnings (0.2.0)
temple (0.7.6)
thor (0.19.1)
thor (0.19.4)
thread_safe (0.3.5)
thread_safe (0.3.5-java)
tilt (1.4.1)
@ -335,7 +358,7 @@ GEM
unf_ext
unf (0.1.4-java)
unf_ext (0.0.7.1)
unicode-display_width (0.3.1)
unicode-display_width (1.1.2)
web-console (2.3.0)
activemodel (>= 4.0)
binding_of_caller (>= 0.7.2)
@ -396,7 +419,7 @@ DEPENDENCIES
puma (~> 2.15.3)
pundit
rack-mini-profiler
rails (~> 4.1.13)
rails (= 4.2.5)
rails-i18n (~> 4.0.0)
rake
ransack

View File

@ -2,8 +2,11 @@ CodeOceanEditorWebsocket = {
websocket: null,
createSocketUrl: function(url) {
var rel_url_root = '<%= (defined? config.relative_url_root) && config.relative_url_root != nil && config.relative_url_root != "" ? config.relative_url_root : "" %>';
return '<%= DockerClient.config['ws_client_protocol'] %>' + window.location.hostname + ':' + rel_url_root + window.location.port + url;
var sockURL = new URL(window.location);
sockURL.pathname = url;
sockURL.protocol = '<%= DockerClient.config['ws_client_protocol'] %>';
return sockURL.toString();
},
initializeSocket: function(url) {

View File

@ -59,6 +59,8 @@ CodeOceanEditorCodePilot = {
}
};
//Request for comments does currently not work on staging platform (no relative root_url used here).
//To fix this rely on ruby routes
CodeOceanEditorRequestForComments = {
requestComments: function () {
var user_id = $('#editor').data('user-id');

View File

@ -10,7 +10,6 @@ class ApplicationController < ActionController::Base
rescue_from Pundit::NotAuthorizedError, with: :render_not_authorized
def current_user
::NewRelic::Agent.add_custom_parameters({ external_user_id: session[:external_user_id], session_user_id: session[:user_id] })
@current_user ||= ExternalUser.find_by(id: session[:external_user_id]) || login_from_session || login_from_other_sources
end

View File

@ -2,6 +2,7 @@ require 'oauth/request_proxy/rack_request'
module Lti
extend ActiveSupport::Concern
include LtiHelper
MAXIMUM_SCORE = 1
MAXIMUM_SESSION_AGE = 60.minutes
@ -14,10 +15,18 @@ module Lti
end
private :build_tool_provider
def clear_lti_session_data
# 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, user_id = nil, consumer_id = nil)
if (exercise_id.nil?)
session.delete(:consumer_id)
session.delete(:external_user_id)
session.delete(:lti_parameters)
else
LtiParameter.destroy_all(consumers_id: consumer_id,
external_users_id: user_id,
exercises_id: exercise_id)
end
end
private :clear_lti_session_data
@ -43,11 +52,6 @@ module Lti
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
@ -94,10 +98,19 @@ module Lti
end
private :return_to_consumer
def send_score(score)
def send_score(exercise_id, score, user_id)
::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)
provider = build_tool_provider(consumer: Consumer.find_by(id: session[:consumer_id]), parameters: session[:lti_parameters])
if session[:consumer_id]
lti_parameter = LtiParameter.where(consumers_id: session[:consumer_id],
external_users_id: user_id,
exercises_id: exercise_id).first
consumer = Consumer.find_by(id: session[:consumer_id])
provider = build_tool_provider(consumer: consumer, parameters: lti_parameter.lti_parameters)
end
if provider.nil?
{status: 'error'}
elsif provider.outcome_service?
@ -116,9 +129,19 @@ module Lti
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?
current_user = ExternalUser.find_or_create_by(consumer_id: options[:consumer].id, external_id: options[:parameters][:user_id].to_s)
lti_parameters = LtiParameter.find_or_create_by(consumers_id: options[:consumer].id,
external_users_id: current_user.id,
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_id] = @current_user.id
session[:lti_parameters] = options[:parameters].slice(*SESSION_PARAMETERS)
session[:external_user_id] = current_user.id
end
private :store_lti_session_data

View File

@ -157,7 +157,14 @@ class ExercisesController < ApplicationController
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])))
lti_parameter = LtiParameter.where(consumers_id: session[:consumer_id],
external_users_id: @submission.user_id,
exercises_id: @submission.exercise_id).first
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: lti_parameter.lti_parameters)))
respond_to do |format|
format.html { redirect_to(path) }
format.json { render(json: {redirect: path}) }
@ -221,7 +228,8 @@ class ExercisesController < ApplicationController
def submit
@submission = Submission.create(submission_params)
score_submission(@submission)
if lti_outcome_service?
current_user = ExternalUser.find(@submission.user_id)
if !current_user.nil? && lti_outcome_service?(@submission.exercise_id, current_user.id, current_user.consumer_id)
transmit_lti_score
else
redirect_after_submit
@ -230,7 +238,8 @@ class ExercisesController < ApplicationController
def transmit_lti_score
::NewRelic::Agent.add_custom_parameters({ submission: @submission.id, normalized_score: @submission.normalized_score })
response = send_score(@submission.normalized_score)
response = send_score(@submission.exercise_id, @submission.normalized_score, @submission.user_id)
if response[:status] == 'success'
redirect_after_submit
else

View File

@ -21,7 +21,9 @@ class SessionsController < ApplicationController
set_current_user
store_lti_session_data(consumer: @consumer, parameters: params)
store_nonce(params[:oauth_nonce])
redirect_to(implement_exercise_path(@exercise), notice: t("sessions.create_through_lti.session_#{lti_outcome_service? ? 'with' : 'without'}_outcome", consumer: @consumer))
redirect_to(implement_exercise_path(@exercise),
notice: t("sessions.create_through_lti.session_#{lti_outcome_service?(@exercise.id, @current_user.id , @consumer.id) ? 'with' : 'without'}_outcome",
consumer: @consumer))
end
def destroy
@ -34,9 +36,8 @@ class SessionsController < ApplicationController
end
def destroy_through_lti
@consumer = Consumer.find_by(id: params[:consumer_id])
@submission = Submission.find(params[:submission_id])
clear_lti_session_data
clear_lti_session_data(@submission.exercise_id, @submission.user_id, params[:consumer_id])
end
def new

View File

@ -1,4 +1,6 @@
module ExerciseHelper
include LtiHelper
def embedding_parameters(exercise)
"locale=#{I18n.locale}&token=#{exercise.token}"
end

10
app/helpers/lti_helper.rb Normal file
View File

@ -0,0 +1,10 @@
module LtiHelper
def lti_outcome_service?(exercise_id, external_user_id, consumer_id)
return false if external_user_id == '' || consumer_id == ''
lti_parameters = LtiParameter.where(consumers_id: consumer_id,
external_users_id: external_user_id,
exercises_id: exercise_id).lis_outcome_service_url?
!lti_parameters.nil? && lti_parameters.size > 0
end
end

View File

@ -0,0 +1,9 @@
class LtiParameter < ActiveRecord::Base
belongs_to :consumer, foreign_key: "consumers_id"
belongs_to :exercise, foreign_key: "exercises_id"
belongs_to :external_user, foreign_key: "external_users_id"
scope :lis_outcome_service_url?, -> {
where("lti_parameters.lti_parameters ? 'lis_outcome_service_url'")
}
end

View File

@ -28,17 +28,6 @@ class Submission < ActiveRecord::Base
ancestors.merge(descendants).values
end
[:download_file, :render, :run, :test].each do |action|
filename = FILENAME_URL_PLACEHOLDER.gsub(/\W/, '')
define_method("#{action}_url") do
Rails.application.routes.url_helpers.send(:"#{action}_submission_path", self, filename).sub(filename, FILENAME_URL_PLACEHOLDER)
end
end
def download_url
Rails.application.routes.url_helpers.send(:download_submission_path, self)
end
def main_file
collect_files.detect(&:main_file?)
end
@ -56,12 +45,6 @@ class Submission < ActiveRecord::Base
(normalized_score * 100).round
end
[:score, :stop].each do |action|
define_method("#{action}_url") do
Rails.application.routes.url_helpers.send(:"#{action}_submission_path", self)
end
end
def siblings
user.submissions.where(exercise_id: exercise_id)
end

View File

@ -1,11 +1,11 @@
- external_user_id = @current_user.respond_to?(:external_id) ? @current_user.external_id : '' #'tests' #(@current_user.uuid.present? ? @current_user.uuid : '')
#editor.row data-exercise-id=exercise.id data-message-depleted=t('exercises.editor.depleted') data-message-timeout=t('exercises.editor.timeout', permitted_execution_time: @exercise.execution_environment.permitted_execution_time) data-errors-url=execution_environment_errors_path(exercise.execution_environment) data-submissions-url=submissions_path data-user-id=@current_user.id data-user-external-id=external_user_id
- external_user_external_id = @current_user.respond_to?(:external_id) ? @current_user.external_id : '' #'tests' #(@current_user.uuid.present? ? @current_user.uuid : '')
- external_user_id = @current_user.respond_to?(:external_id) ? @current_user.id : '' #'tests' #(@current_user.uuid.present? ? @current_user.uuid : '')
- consumer_id = @current_user.respond_to?(:external_id) ? @current_user.consumer_id : '' #'tests' #(@current_user.uuid.present? ? @current_user.uuid : '')
#editor.row data-exercise-id=exercise.id data-message-depleted=t('exercises.editor.depleted') data-message-timeout=t('exercises.editor.timeout', permitted_execution_time: @exercise.execution_environment.permitted_execution_time) data-errors-url=execution_environment_errors_path(exercise.execution_environment) data-submissions-url=submissions_path data-user-id=@current_user.id data-user-external-id=external_user_external_id
div id="sidebar" class=(@exercise.hide_file_tree ? 'sidebar-col-collapsed' : 'sidebar-col') = render('editor_file_tree', exercise: @exercise, files: @files)
div id='output_sidebar' class='output-col-collapsed' = render('exercises/editor_output')
div id='output_sidebar' class='output-col-collapsed' = render('exercises/editor_output', external_user_id: external_user_id, consumer_id: consumer_id )
div id='frames' class='editor-col'
#editor-buttons.btn-group.enforce-bottom-margin
// = render('editor_button', data: {:'data-message-success' => t('submissions.create.success'), :'data-placement' => 'top', :'data-tooltip' => true}, icon: 'fa fa-save', id: 'save', label: t('exercises.editor.save'), title: t('.tooltips.save'))
// .btn-group
= render('editor_button', disabled: true, icon: 'fa fa-ban', id: 'dummy', label: t('exercises.editor.dummy'))
= render('editor_button', icon: 'fa fa-desktop', id: 'render', label: t('exercises.editor.render'))
= render('editor_button', data: {:'data-message-failure' => t('exercises.editor.run_failure'), :'data-message-network' => t('exercises.editor.network'), :'data-message-success' => t('exercises.editor.run_success'), :'data-placement' => 'top', :'data-tooltip' => true}, icon: 'fa fa-play', id: 'run', label: t('exercises.editor.run'), title: t('shared.tooltips.shortcut', shortcut: 'ALT + r'))

View File

@ -27,7 +27,7 @@ div id='output_sidebar_uncollapsed' class='hidden col-sm-12 enforce-bottom-margi
.progress-bar role='progressbar'
br
- if session[:lti_parameters].try(:has_key?, 'lis_outcome_service_url')
- if lti_outcome_service?(@exercise.id, external_user_id, consumer_id)
p.text-center = render('editor_button', classes: 'btn-lg btn-success', data: {:'data-url' => submit_exercise_path(@exercise)}, icon: 'fa fa-send', id: 'submit', label: t('exercises.editor.submit'))
- else
p.text-center = render('editor_button', classes: 'btn-lg btn-warning-outline', data: {:'data-placement' => 'bottom', :'data-tooltip' => true}, icon: 'fa fa-clock-o', id: 'submit_outdated', label: t('exercises.editor.exercise_deadline_passed'), title: t('exercises.editor.tooltips.exercise_deadline_passed'))

View File

@ -3,6 +3,7 @@ h1 = t('.headline')
p
== t(".success_#{params[:outcome] ? 'with' : 'without'}_outcome", consumer: @consumer)
==< t(".finished_#{@consumer ? 'with' : 'without'}_consumer", consumer: @consumer, url: params[:url])
==< t(".do_not_use_backbutton", consumer: @consumer)
h2 = t('shared.statistics')

View File

@ -1 +1,8 @@
json.extract! @submission, :download_url, :download_file_url, :id, :score_url, :render_url, :run_url, :stop_url, :test_url, :files
json.extract! @submission, :id, :files
json.download_url download_submission_path(@submission)
json.score_url score_submission_path(@submission)
json.stop_url stop_submission_path(@submission)
json.download_file_url download_file_submission_path(@submission, 'a.').gsub(/a\.$/, '{filename}')
json.render_url render_submission_path(@submission, 'a.').gsub(/a\.$/, '{filename}')
json.run_url run_submission_path(@submission, 'a.').gsub(/a\.$/, '{filename}')
json.test_url test_submission_path(@submission, 'a.').gsub(/a\.$/, '{filename}')

29
bin/setup Executable file
View File

@ -0,0 +1,29 @@
#!/usr/bin/env ruby
require 'pathname'
# path to your application root.
APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
Dir.chdir APP_ROOT do
# This script is a starting point to setup your application.
# Add necessary setup steps to this file:
puts "== Installing dependencies =="
system "gem install bundler --conservative"
system "bundle check || bundle install"
# puts "\n== Copying sample files =="
# unless File.exist?("config/database.yml")
# system "cp config/database.yml.sample config/database.yml"
# end
puts "\n== Preparing database =="
system "bin/rake db:setup"
puts "\n== Removing old logs and tempfiles =="
system "rm -f log/*"
system "rm -rf tmp/cache"
puts "\n== Restarting application server =="
system "touch tmp/restart.txt"
end

View File

@ -1,13 +1,6 @@
require File.expand_path('../boot', __FILE__)
# Pick the frameworks you want:
require "active_model/railtie"
require "active_record/railtie"
require "action_controller/railtie"
require "action_mailer/railtie"
require "action_view/railtie"
require "sprockets/railtie"
# require "rails/test_unit/railtie"
require 'rails/all'
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
@ -21,17 +14,22 @@ module CodeOcean
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
config.time_zone = 'Berlin'
config.time_zone = 'UTC'
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de
config.i18n.available_locales = [:de, :en]
# Do not swallow errors in after_commit/after_rollback callbacks.
config.active_record.raise_in_transactional_callbacks = true
config.autoload_paths << Rails.root.join('lib')
config.eager_load_paths << Rails.root.join('lib')
config.assets.precompile += %w( markdown-buttons.png )
config.active_record.schema_format = :sql
case (RUBY_ENGINE)
when 'ruby'
# ...
@ -43,5 +41,3 @@ module CodeOcean
end
end
end
Rails.application.config.assets.precompile += %w( markdown-buttons.png )

View File

@ -1,4 +1,3 @@
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
require 'bundler/setup' # Set up gems listed in the Gemfile.

View File

@ -2,6 +2,7 @@ Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
config.web_console.whitelisted_ips = '192.168.0.0/16'
# In the development environment your application's code is reloaded on
# every request. This slows down response time but is perfect for development
# since you don't have to restart the web server when you make code changes.
@ -14,7 +15,7 @@ Rails.application.configure do
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
config.action_mailer.perform_deliveries = true
config.action_mailer.perform_deliveries = false
# Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false
@ -25,12 +26,21 @@ Rails.application.configure do
# Raise an error on page load if there are pending migrations.
config.active_record.migration_error = :page_load
# Debug mode disables concatenation and preprocessing of assets.
# This option may cause significant delays in view rendering with a large
# number of complex assets.
config.assets.debug = true
# Asset digests allow you to set far-future HTTP expiration dates on all assets,
# yet still be able to expire them through the digest params.
config.assets.digest = true
# Adds additional error checking when serving assets at runtime.
# Checks for improperly declared sprockets dependencies.
# Raises helpful error messages.
config.assets.raise_runtime_errors = true
# Raise errors for missing translations.
# Raises error for missing translations
config.action_view.raise_on_missing_translations = true
BetterErrors::Middleware.allow_ip! ENV['TRUSTED_IP'] if ENV['TRUSTED_IP']

View File

@ -16,11 +16,13 @@ Rails.application.configure do
# Enable Rack::Cache to put a simple HTTP cache in front of your application
# Add `rack-cache` to your Gemfile before enabling this.
# For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid.
# For large-scale production use, consider using a caching reverse proxy like
# NGINX, varnish or squid.
# config.action_dispatch.rack_cache = true
# Disable Rails's static asset server (Apache or nginx will already do this).
config.serve_static_assets = false
# Disable serving static files from the `/public` folder by default since
# Apache or NGINX already handles this.
config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present?
# Compress JavaScripts and CSS.
config.assets.js_compressor = :uglifier
@ -29,20 +31,21 @@ Rails.application.configure do
# Do not fallback to assets pipeline if a precompiled asset is missed.
config.assets.compile = false
# Generate digests for assets URLs.
# Asset digests allow you to set far-future HTTP expiration dates on all assets,
# yet still be able to expire them through the digest params.
config.assets.digest = true
# Version of your assets, change this if you want to expire all your assets.
config.assets.version = '1.0'
# `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
# Specifies the header that your server uses for sending files.
# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
# config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
# Set to :debug to see everything in the log.
# Use the lowest log level to ensure availability of diagnostic information
# when problems arise.
config.log_level = :error
# Prepend all log lines with the following tags.
@ -55,11 +58,7 @@ Rails.application.configure do
# config.cache_store = :mem_cache_store
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.action_controller.asset_host = "http://assets.example.com"
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
# config.assets.precompile += %w( search.js )
# config.action_controller.asset_host = 'http://assets.example.com'
# Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
@ -72,9 +71,6 @@ Rails.application.configure do
# Send deprecation notices to registered listeners.
config.active_support.deprecation = :notify
# Disable automatic flushing of the log to improve performance.
# config.autoflush_log = false
# Use default logging formatter so that PID and timestamp are not suppressed.
config.log_formatter = ::Logger::Formatter.new

View File

@ -37,7 +37,7 @@ Rails.application.configure do
# config.action_dispatch.rack_cache = true
# Disable Rails's static asset server (Apache or nginx will already do this).
config.serve_static_assets = false
config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present?
# Compress JavaScripts and CSS.
# config.assets.js_compressor = :uglifier

View File

@ -12,8 +12,8 @@ Rails.application.configure do
# preloads Rails for running tests, you may have to set it to true.
config.eager_load = false
# Configure static asset server for tests with Cache-Control for performance.
config.serve_static_assets = true
# Configure static file server for tests with Cache-Control for performance.
config.serve_static_files = true
config.static_cache_control = 'public, max-age=3600'
# Show full error reports and disable caching.
@ -31,6 +31,9 @@ Rails.application.configure do
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
# Randomize the order test cases are executed.
config.active_support.test_order = :random
# Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr

View File

@ -0,0 +1,12 @@
# Be sure to restart your server when you modify this file.
# Version of your assets, change this if you want to expire all your assets.
Rails.application.config.assets.version = '1.0'
# Add additional assets to the asset load path
# Rails.application.config.assets.paths << Emoji.images_path
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
# Rails.application.config.assets.precompile += %w( search.js )
Rails.application.config.assets.precompile += %w( markdown-buttons.png )

View File

@ -1,3 +1,3 @@
# Be sure to restart your server when you modify this file.
Rails.application.config.session_store :cookie_store, key: '_hands-on-programming_session'
Rails.application.config.session_store :cookie_store, key: '_code_ocean_session'

View File

@ -217,10 +217,10 @@ de:
submit: Code zur Bewertung abgeben
test: Testen
timeout: 'Ausführung gestoppt. Ihr Code hat die erlaubte Ausführungszeit von %{permitted_execution_time} Sekunden überschritten.'
exercise_deadline_passed: 'Die Abgabefrist für diese Aufgabe ist bereits abgelaufen.'
exercise_deadline_passed: 'Das Ergebnis kann nicht übertragen werden.'
tooltips:
save: Ihr Code wird automatisch gespeichert, wann immer Sie eine Datei herunterladen, ausführen oder testen. Explizites Speichern ist also selten notwendig.
exercise_deadline_passed: 'Die hier erzielten Punkte können nur bis zum Ablauf der Abgabefrist an die E-Learning-Plattform übertragen werden.'
exercise_deadline_passed: 'Entweder ist die Abgabefrist bereits abgelaufen oder Sie haben die Aufgabe nicht direkt über die E-Learning Plattform gestartet. (Möglicherweise haben Sie den Zurück Button Ihres Browsers benutzt nachdem Sie Ihre Aufgabe abgegeben haben?)'
request_for_comments_sent: "Kommentaranfrage gesendet."
editor_file_tree:
file_root: Dateien
@ -386,6 +386,7 @@ de:
score: Ihre Punktzahl
success_with_outcome: 'Ihr Code wurde erfolgreich bewertet. Ihre Bewertung wurde an %{consumer} übermittelt.'
success_without_outcome: Ihr Code wurde erfolgreich bewertet.
do_not_use_backbutton: Benutzen Sie nicht den "Zurück" Button des Browsers, um zu CodeOcean zurück zu kehren. Übungen müssen immer aus dem %{consumer} Kontext gestartet werden.
new:
forgot_password: Passwort vergessen?
headline: Anmelden

View File

@ -1,3 +1,24 @@
# Files in the config/locales directory are used for internationalization
# and are automatically loaded by Rails. If you want to use locales other
# than English, add the necessary files in this directory.
#
# To use the locales, use `I18n.t`:
#
# I18n.t 'hello'
#
# In views, this is aliased to just `t`:
#
# <%= t('hello') %>
#
# To use a different locale, set it with `I18n.locale`:
#
# I18n.locale = :es
#
# This would use the information in config/locales/es.yml.
#
# To learn more, please read the Rails Internationalization guide
# available at http://guides.rubyonrails.org/i18n.html.
en:
activerecord:
attributes:
@ -217,10 +238,10 @@ en:
submit: Submit Code For Assessment
test: Test
timeout: 'Execution stopped. Your code exceeded the permitted execution time of %{permitted_execution_time} seconds.'
exercise_deadline_passed: 'The deadline for this exercise has already passed'
exercise_deadline_passed: 'The score cannot be submitted.'
tooltips:
save: Your code is automatically saved whenever you download, run, or test it. Therefore, explicitly saving is rarely necessary.
exercise_deadline_passed: 'The results for this exercise can only be submitted to the e-learning platform before the deadline has passed.'
exercise_deadline_passed: 'Either the deadline has already passed or you did not directly access this page from the e-learning platform. (Did you use the Back button of your browser after submitting the score?)'
request_for_comments_sent: "Request for comments sent."
editor_file_tree:
file_root: Files
@ -386,6 +407,7 @@ en:
score: Your Score
success_with_outcome: 'Your code has been successfully assessed. Your grade has been transmitted to %{consumer}.'
success_without_outcome: Your code has been successfully assessed.
do_not_use_backbutton: Never use the browser's "Back" button to get back to CodeOcean. Always start an exercise from within %{consumer}.
new:
forgot_password: Forgot password?
headline: Sign In
@ -466,3 +488,4 @@ en:
previous_label: '&#8592; Previous Page'
file_template:
no_template_label: "Empty File"

View File

@ -0,0 +1,11 @@
class CreateLtiParameters < ActiveRecord::Migration
def change
create_table :lti_parameters do |t|
t.belongs_to :external_users
t.belongs_to :consumers
t.belongs_to :exercises
t.jsonb :lti_parameters, null: false, default: '{}'
t.timestamps
end
end
end

View File

@ -11,13 +11,13 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160907123009) do
ActiveRecord::Schema.define(version: 20161214144837) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "code_harbor_links", force: true do |t|
t.string "oauth2token"
create_table "code_harbor_links", force: :cascade do |t|
t.string "oauth2token", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
t.integer "user_id"
@ -25,10 +25,10 @@ ActiveRecord::Schema.define(version: 20160907123009) do
add_index "code_harbor_links", ["user_id"], name: "index_code_harbor_links_on_user_id", using: :btree
create_table "comments", force: true do |t|
create_table "comments", force: :cascade do |t|
t.integer "user_id"
t.integer "file_id"
t.string "user_type"
t.string "user_type", limit: 255
t.integer "row"
t.integer "column"
t.text "text"
@ -39,15 +39,15 @@ ActiveRecord::Schema.define(version: 20160907123009) do
add_index "comments", ["file_id"], name: "index_comments_on_file_id", using: :btree
add_index "comments", ["user_id"], name: "index_comments_on_user_id", using: :btree
create_table "consumers", force: true do |t|
t.string "name"
create_table "consumers", force: :cascade do |t|
t.string "name", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
t.string "oauth_key"
t.string "oauth_secret"
t.string "oauth_key", limit: 255
t.string "oauth_secret", limit: 255
end
create_table "errors", force: true do |t|
create_table "errors", force: :cascade do |t|
t.integer "execution_environment_id"
t.text "message"
t.datetime "created_at"
@ -57,123 +57,123 @@ ActiveRecord::Schema.define(version: 20160907123009) do
add_index "errors", ["submission_id"], name: "index_errors_on_submission_id", using: :btree
create_table "execution_environments", force: true do |t|
t.string "docker_image"
t.string "name"
create_table "execution_environments", force: :cascade do |t|
t.string "docker_image", limit: 255
t.string "name", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
t.string "run_command"
t.string "test_command"
t.string "testing_framework"
t.string "run_command", limit: 255
t.string "test_command", limit: 255
t.string "testing_framework", limit: 255
t.text "help"
t.string "exposed_ports"
t.string "exposed_ports", limit: 255
t.integer "permitted_execution_time"
t.integer "user_id"
t.string "user_type"
t.string "user_type", limit: 255
t.integer "pool_size"
t.integer "file_type_id"
t.integer "memory_limit"
t.boolean "network_enabled"
end
create_table "exercises", force: true do |t|
create_table "exercises", force: :cascade do |t|
t.text "description"
t.integer "execution_environment_id"
t.string "title"
t.string "title", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
t.integer "user_id"
t.text "instructions"
t.boolean "public"
t.string "user_type"
t.string "token"
t.string "user_type", limit: 255
t.string "token", limit: 255
t.boolean "hide_file_tree"
t.boolean "allow_file_creation"
t.boolean "allow_auto_completion", default: false
end
create_table "external_users", force: true do |t|
create_table "external_users", force: :cascade do |t|
t.integer "consumer_id"
t.string "email"
t.string "external_id"
t.string "name"
t.string "email", limit: 255
t.string "external_id", limit: 255
t.string "name", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "file_templates", force: true do |t|
t.string "name"
create_table "file_templates", force: :cascade do |t|
t.string "name", limit: 255
t.text "content"
t.integer "file_type_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "file_types", force: true do |t|
t.string "editor_mode"
t.string "file_extension"
create_table "file_types", force: :cascade do |t|
t.string "editor_mode", limit: 255
t.string "file_extension", limit: 255
t.integer "indent_size"
t.string "name"
t.string "name", limit: 255
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "executable"
t.boolean "renderable"
t.string "user_type"
t.string "user_type", limit: 255
t.boolean "binary"
end
create_table "files", force: true do |t|
create_table "files", force: :cascade do |t|
t.text "content"
t.integer "context_id"
t.string "context_type"
t.string "context_type", limit: 255
t.integer "file_id"
t.integer "file_type_id"
t.boolean "hidden"
t.string "name"
t.string "name", limit: 255
t.boolean "read_only"
t.datetime "created_at"
t.datetime "updated_at"
t.string "native_file"
t.string "role"
t.string "hashed_content"
t.string "feedback_message"
t.string "native_file", limit: 255
t.string "role", limit: 255
t.string "hashed_content", limit: 255
t.string "feedback_message", limit: 255
t.float "weight"
t.string "path"
t.string "path", limit: 255
t.integer "file_template_id"
end
add_index "files", ["context_id", "context_type"], name: "index_files_on_context_id_and_context_type", using: :btree
create_table "hints", force: true do |t|
create_table "hints", force: :cascade do |t|
t.integer "execution_environment_id"
t.string "locale"
t.string "locale", limit: 255
t.text "message"
t.string "name"
t.string "regular_expression"
t.string "name", limit: 255
t.string "regular_expression", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "internal_users", force: true do |t|
create_table "internal_users", force: :cascade do |t|
t.integer "consumer_id"
t.string "email"
t.string "name"
t.string "role"
t.string "email", limit: 255
t.string "name", limit: 255
t.string "role", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
t.string "crypted_password"
t.string "salt"
t.string "crypted_password", limit: 255
t.string "salt", limit: 255
t.integer "failed_logins_count", default: 0
t.datetime "lock_expires_at"
t.string "unlock_token"
t.string "remember_me_token"
t.string "unlock_token", limit: 255
t.string "remember_me_token", limit: 255
t.datetime "remember_me_token_expires_at"
t.string "reset_password_token"
t.string "reset_password_token", limit: 255
t.datetime "reset_password_token_expires_at"
t.datetime "reset_password_email_sent_at"
t.string "activation_state"
t.string "activation_token"
t.string "activation_state", limit: 255
t.string "activation_token", limit: 255
t.datetime "activation_token_expires_at"
end
@ -182,29 +182,38 @@ ActiveRecord::Schema.define(version: 20160907123009) do
add_index "internal_users", ["remember_me_token"], name: "index_internal_users_on_remember_me_token", using: :btree
add_index "internal_users", ["reset_password_token"], name: "index_internal_users_on_reset_password_token", using: :btree
create_table "request_for_comments", force: true do |t|
create_table "lti_parameters", force: :cascade do |t|
t.string "external_user_id"
t.integer "consumers_id"
t.integer "exercises_id"
t.jsonb "lti_parameters", default: {}, null: false
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "request_for_comments", force: :cascade do |t|
t.integer "user_id", null: false
t.integer "exercise_id", null: false
t.integer "file_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.string "user_type"
t.string "user_type", limit: 255
t.text "question"
t.boolean "solved"
t.integer "submission_id"
end
create_table "submissions", force: true do |t|
create_table "submissions", force: :cascade do |t|
t.integer "exercise_id"
t.float "score"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
t.string "cause"
t.string "user_type"
t.string "cause", limit: 255
t.string "user_type", limit: 255
end
create_table "testruns", force: true do |t|
create_table "testruns", force: :cascade do |t|
t.boolean "passed"
t.text "output"
t.integer "file_id"

1122
db/structure.sql Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,69 @@
# update apt-get
echo "Update apt-get..."
sudo apt-get update
# upgrade all packages
echo "Upgrade packages..."
sudo apt-get upgrade
#install postgres
if [ ! -f /etc/apt/sources.list.d/pgdg.list ]
then
echo "Add Postgres sources..."
cd /etc/apt/sources.list.d
sudo touch pgdg.list
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ jessie-pgdg main" > pgdg.list'
sudo wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
sudo apt-get update
echo "Done"
else
echo "Postgres sources already added"
fi
sudo apt-get -y --force-yes install postgresql-9.5
# drop postgres access control
if [ -f /etc/postgresql/9.5/main/pg_hba.conf ]
then
if ! sudo -u postgres grep -q CodeOcean /etc/postgresql/9.5/main/pg_hba.conf
then
echo "Drop Postgres access control..."
sudo -u postgres sh -c 'cat >/etc/postgresql/9.5/main/pg_hba.conf <<EOF
#CodeOcean: drop access control
local all all trust
host all all 127.0.0.1/32 trust
host all all ::1/128 trust
EOF'
echo "Done"
echo "Restart Postgres..."
echo sudo service postgresql restart
echo "Done"
else
echo "Postgres access control already dropped"
fi
else
echo "Postgres installation failed"
fi
# create development database
# TODO: extract databasename to variable
if ! (sudo -u postgres psql -l | grep -q codeocean-development)
then
echo "Create database codeocean-development..."
sudo -u postgres createdb codeocean-development || true
sudo -u postgres psql -d codeocean-development -U postgres -c "CREATE USER root;"
sudo -u postgres psql -d codeocean-development -U postgres -c 'GRANT ALL PRIVILEGES ON DATABASE "codeocean-development" to root';
sudo -u postgres psql -d codeocean-development -U postgres -c "CREATE USER debian;"
sudo -u postgres psql -d codeocean-development -U postgres -c 'GRANT ALL PRIVILEGES ON DATABASE "codeocean-development" to debian';
sudo -u postgres psql -d codeocean-development -U postgres -c "CREATE USER codeocean;"
sudo -u postgres psql -d codeocean-development -U postgres -c 'GRANT ALL PRIVILEGES ON DATABASE "codeocean-development" to codeocean';
sudo -u postgres psql -d codeocean-development -U postgres -c 'ALTER DATABASE "codeocean-development" OWNER TO codeocean';
sudo -u postgres psql -d codeocean-development -U postgres -c 'ALTER USER "codeocean" CREATEDB';
echo "Done"
else
echo "Database codeocean-development already exists"
fi
# TODO: create test database

View File

@ -0,0 +1,86 @@
#install docker
if [ ! -f /etc/apt/sources.list.d/backports.list ]
then
#get sources for dependencies
echo "Get apt-get sources for some docker dependencies..."
cd /etc/apt/sources.list.d
sudo touch backports.list
sudo sh -c 'echo "deb http://http.debian.net/debian jessie-backports main" > backports.list'
sudo apt-get update
echo "Done"
#just in case there is some old stuff
echo "Remove legacy stuff...Just in case..."
sudo apt-get purge "lxc-docker*"
sudo apt-get purge "docker.io*"
sudo apt-get update
#install docker dependencies
echo "Install dependencies..."
sudo apt-get install -y --force-yes apt-transport-https ca-certificates gnupg2
echo "Done"
else
echo "Docker dependencies already added."
fi
if [ ! -f /etc/apt/sources.list.d/docker.list ]
then
# get docker sources
echo "Add apt-get sources for Docker..."
sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
cd /etc/apt/sources.list.d
sudo touch docker.list
sudo sh -c 'echo "deb https://apt.dockerproject.org/repo debian-jessie main" > docker.list'
sudo apt-cache policy docker-engine
sudo apt-get update
echo "Done"
else
echo "Docker apt-get sources already added."
fi
if [ ! -f /etc/systemd/system/docker.service.d/docker.conf ]
then
echo "Install Docker Engine..."
sudo apt-get install -y --force-yes docker-engine
echo "Done"
echo "Start Docker..."
sudo service docker start
echo "Done"
echo "Run Hello World..."
sudo docker run hello-world
echo "Done"
#set some docker options
echo "Configure Docker..."
sudo mkdir /etc/systemd/system/docker.service.d
cd /etc/systemd/system/docker.service.d
sudo touch docker.conf
sudo sh -c 'cat >>/etc/systemd/system/docker.service.d/docker.conf <<EOF
# code_ocean: enable TCP
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H fd:// -D -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock"
EOF'
sudo systemctl daemon-reload
sudo service docker restart
# enable to run docker without sudo
sudo gpasswd -a ${USER} docker
newgrp docker
sudo service docker restart
echo "Done"
else
echo "Docker already installed"
fi
if ! (docker images | grep -q co_execenv_python)
then
echo "Pull Docker images..."
# get docker images
docker pull openhpi/co_execenv_python
docker pull openhpi/co_execenv_java
docker pull openhpi/co_execenv_java_antlr
echo "Done"
else
echo "Docker images already pulled"
fi

View File

@ -0,0 +1,67 @@
if [ ! -f /etc/apt/sources.list.d/nonfree.list ]
then
echo "Get additional sources for apt-get"
cd /etc/apt/sources.list.d
sudo touch nonfree.list
sudo sh -c 'echo "deb http://http.debian.net/debian jessie main non-free contrib" > nonfree.list'
sudo sh -c 'echo "deb-src http://http.debian.net/debian jessie main non-free contrib" >> nonfree.list'
sudo sh -c 'echo "deb http://http.debian.net/debian jessie-updates main contrib non-free" >> nonfree.list'
sudo sh -c 'echo "deb-src http://http.debian.net/debian jessie-updates main contrib non-free" >> nonfree.list'
sudo apt-get update
else
# install utilities
echo "Additional apt-get sources already added"
fi
# install utilities
echo "Install some utils..."
sudo apt-get install -y --force-yes screen
sudo apt-get install -y --force-yes htop
echo "Done"
# install dependencies
echo "Install some libraries..."
sudo apt-get install -y --force-yes git-core curl zlib1g-dev build-essential libssl-dev libreadline-dev
sudo apt-get install -y --force-yes libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev
sudo apt-get install -y --force-yes python-software-properties libffi-dev
sudo apt-get install -y --force-yes libgdbm-dev libncurses5-dev automake libtool bison libffi-dev
sudo apt-get install -y --force-yes libpq-dev
echo "Done"
# get the clock in sync
echo "Install clock synchronization..."
sudo apt-get install -y --force-yes ntp ntpdate
echo "Done"
echo "Install NodeJS..."
# install nodejs
sudo apt-get install -y --force-yes nodejs
echo "Done"
if ! (ruby -v | grep -q 2.3.3)
then
# install rvm
echo "Install RVM..."
gpg2 --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
\curl -sSL https://get.rvm.io | bash -s stable --ruby
source /home/debian/.rvm/scripts/rvm
echo "Done"
# install ruby
echo "Install Ruby 2.3.3..."
rvm install 2.3.3
rvm use 2.3.3 --default
ruby -v
exec bash
echo "Done"
else
echo "RVM and Ruby are already installed"
fi
# install guest additions - required for sharing a folder
echo "Install prerequisites for guest additions..."
sudo apt-get install -y --force-yes dkms build-essential linux-headers-amd64
echo "Done"
echo "Please follow the instructions:"
echo "Insert Guest Additions CD image. VM: Devices=>Insert Guest Additions CD image"
echo "Install Guest Additions"

View File

@ -0,0 +1,10 @@
# Mount Guest Additions and run the installer
echo "Mount Guest Additions and run the installer..."
sudo mount /dev/sr0 /media/cdrom
cd /media/cdrom
sudo sh ./VBoxLinuxAdditions.run
echo "Done"
echo "Please follow the instructions:"
echo "Create Shared Folder. VM: Devices=>VM: Devices=>Shared Folders=>Shared Folders Settings"
echo "Name: codeocean, Path: path to your local codeocaen repository on the host machine."

View File

@ -0,0 +1,7 @@
echo "Mount Shared Folder..."
mkdir /home/debian/codeocean_host
sudo mount -t vboxsf -o rw,uid=1000,gid=1000 codeocean /home/debian/codeocean_host
# Enable automount during startup
sudo sh -c 'echo "sudo mount -t vboxsf -o rw,uid=1000,gid=1000 codeocean /home/debian/codeocean_host" >> /home/debian/.bashrc '
echo "Done"

View File

@ -0,0 +1,28 @@
############# codeocean install ###########################
cd /home/debian/codeocean_host
#install rails and bundler
echo "Install Rails..."
gem install rails
echo "Done"
echo "Install Bundler..."
gem install bundler
echo "Done"
# install required gems
bundle install
# copy config files
for f in action_mailer.yml database.yml secrets.yml sendmail.yml smtp.yml code_ocean.yml
do
if [ ! -f config/$f ]
then
cp config/$f.example config/$f
fi
done
# Manual Task:
# if necessary adjust db config
echo "Check if settings in database.yml correspond with your database setup."
cat /home/debian/codeocean_host/config/database.yml

View File

@ -0,0 +1,8 @@
# create, migrate, and seed database tables
cd /home/debian/codeocean_host
export RAILS_ENV=development
echo "load, seed, migrate"
rake db:schema:load
rake db:seed
rake db:migrate

View File

@ -0,0 +1,165 @@
# Prerequisites:
# 1 Download Debian iso image. http://cdimage.debian.org/debian-cd/8.6.0/amd64/iso-cd/debian-8.6.0-amd64-netinst.iso
# 2 Create Debian VM in VirtualBox:
# - without GUI
# - without webserver (we do not want an apache2 but an nginx server)
# - with ssh ()
# 2 Create 2 users
# - debian/debian
# - root/root
# Manual preparation:
# Login as root
su
# install sudo
apt-get install -y sudo
# add user debian to sudoers and enable this user to sudo without password (do not do this on a production machine)
# or change the line after finishing the installation
cd /etc/sudoers.d
touch debian
echo "debian ALL=(ALL) NOPASSWD:ALL" >> debian
# echo "debian ALL=(ALL:ALL) ALL" >> debian # production systems
# return to no-root user again
exit
# Running the following directly on the VM command line is inconvenient
# Therefore enable login via ssh from Host
# The best way to login to a guest Linux VirtualBox VM is port forwarding.
# By default, you should have one interface already which is using NAT.
# Then go to the Network settings and click the Port Forwarding button. Add a new Rule:
# Protocol TCP Host port 3022, guest port 22, name ssh, other left blank.
# That's all! Please be sure you don't forget to install an SSH server:
# To SSH into the guest VM, write:
# ssh -p 3022 user@127.0.0.1
# http://stackoverflow.com/questions/5906441/how-to-ssh-to-a-virtualbox-guest-externally-through-a-host
#=======================================================================================================
# Install postgres
# run script:
debian_installer/setup_debian_1_install_postgres.sh
# Install docker
# run script:
debian_installer/setup_debian_2_install_docker.sh
# Install dependencies, utils, rvm, ruby, node
# run script:
debian_installer/setup_debian_3_install_depencies_and_utils.sh
##################################local installation on VirtualBox only##################
# Before running the next script, the Guest Additions CD image needs to be inserted via VBox GUI
# Devices=>Insert Guest Additions CD image"
# When that is done run the next script
debian_installer/setup_debian_4_install_guest_additions.sh
# Before running the next script, a Shared Folder has to be created via VBox GUI
# Devices=>Shared Folders=>Shared Folders Settings
# Folder Name: codeocean, Folder Path: path to your local codeocean repository on the host machine.
# Automount, Make Permanent
# When that is done run the next script
debian_installer/setup_debian_5_mount_shared_folder.sh
##################################local installation on VirtualBox only##################
# Install rails and bundler
# run script:
debian_installer/setup_debian_6_setup_codeocean.sh
# Create, seed, and migrate database tables
# run script:
debian_installer/setup_debian_7_create_tables.sh
# Add Port Forwarding for Rails server:
# Protocol TCP Host port 3030, guest port 3000, name CodeOcean, other left blank.
# That's all!
# Start Puma server on VM (since we upgraded to rails 4.2.5, it is necessary to specify the address here as well. Otherwise, we can't connect from the host machine)
# rails s -b 0.0.0.0 -p 8080
# To connect to Ruby app use
#http://127.0.0.1:3030
#The following is required so that CodeOcean can connect back to openHPI local
# Setup a second networking interface
# 1. Host-only vboxnet0 (ip-address: 192.168.59.104)
# 2. NAT with all the portforwarding stuff as described above
# Edit /etc/network/interfaces in Guest machine:
# 1. check for available interfaces:
# ls /sys/class/net ===> docker0 eth0 eth1 lo
# 2. edit network configuration:
# sudoedit /etc/network/interfaces
# and add the following lines:
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).
source /etc/network/interfaces.d/*
# The loopback network interface
auto lo
iface lo inet loopback
# The primary network interface
# allow-hotplug eth0
# iface eth0 inet dhcp
#Host-only interface
auto eth0
iface eth0 inet static
address 192.168.59.104
netmask 255.255.255.0
network 192.168.59.0
broadcast 192.168.59.255
#NAT interface
auto eth1
iface eth1 inet dhcp
# See also:
# http://askubuntu.com/questions/293816/in-virtualbox-how-do-i-set-up-host-only-virtual-machines-that-can-access-the-in
# !!!!!Attention!!!!!!!!
# Start openHPI Local as:
# http://{host.ip}:3000/
# e.g. http://192.168.178.33:3000/
# set LTI Provider in course as:
# http://192.168.59.104:3030/lti/launch
# Access VBox with static IP and port-forwarding
# SSH:
# ssh -p 3022 debian@192.168.59.104
# CodeOcean:
# http://192.168.59.104:3030
#TODO production:
# require passwd for sudo again.
# cd /etc/sudoers.d
# echo "debian ALL=(ALL:ALL) ALL" > debian
#TODO production: Install nginx
# install nginx
# echo "Install NGINX..."
# sudo apt-get install -y --force-yes nginx
# echo "Done"

View File

@ -325,7 +325,7 @@ class DockerClient
end
def self.image_tags
Docker::Image.all.map { |image| image.info['RepoTags'] }.flatten.reject { |tag| tag.present? && tag.include?('<none>') }
Docker::Image.all.map { |image| image.info['RepoTags'] }.flatten.reject { |tag| tag.nil? || tag.include?('<none>') }
end
def initialize(options = {})

View File

@ -19,7 +19,6 @@ describe Lti do
it 'clears the session' do
expect(controller.session).to receive(:delete).with(:consumer_id)
expect(controller.session).to receive(:delete).with(:external_user_id)
expect(controller.session).to receive(:delete).with(:lti_parameters)
controller.send(:clear_lti_session_data)
end
end
@ -106,24 +105,25 @@ describe Lti do
describe '#send_score' do
let(:consumer) { FactoryGirl.create(:consumer) }
let(:score) { 0.5 }
let(:submission) { FactoryGirl.create(:submission) }
let!(:lti_parameter) { FactoryGirl.create(:lti_parameter)}
context 'with an invalid score' do
it 'raises an exception' do
expect { controller.send(:send_score, Lti::MAXIMUM_SCORE * 2) }.to raise_error(Lti::Error)
expect { controller.send(:send_score, submission.exercise_id, Lti::MAXIMUM_SCORE * 2, submission.user_id) }.to raise_error(Lti::Error)
end
end
context 'with an valid score' do
context 'with a tool provider' do
context 'with a tool consumer' do
before(:each) do
controller.session[:consumer_id] = consumer.id
controller.session[:lti_parameters] = {}
end
context 'when grading is not supported' do
it 'returns a corresponding status' do
expect_any_instance_of(IMS::LTI::ToolProvider).to receive(:outcome_service?).and_return(false)
expect(controller.send(:send_score, score)[:status]).to eq('unsupported')
expect(controller.send(:send_score, submission.exercise_id, score, submission.user_id)[:status]).to eq('unsupported')
end
end
@ -140,11 +140,11 @@ describe Lti do
end
it 'sends the score' do
controller.send(:send_score, score)
controller.send(:send_score, submission.exercise_id, score, submission.user_id)
end
it 'returns code, message, and status' do
result = controller.send(:send_score, score)
result = controller.send(:send_score, submission.exercise_id, score, submission.user_id)
expect(result[:code]).to eq(response.response_code)
expect(result[:message]).to eq(response.body)
expect(result[:status]).to eq(response.code_major)
@ -152,10 +152,9 @@ describe Lti do
end
end
context 'without a tool provider' do
context 'without a tool consumer' do
it 'returns a corresponding status' do
expect(controller).to receive(:build_tool_provider).and_return(nil)
expect(controller.send(:send_score, score)[:status]).to eq('error')
expect(controller.send(:send_score, submission.exercise_id, score, submission.user_id)[:status]).to eq('error')
end
end
end
@ -163,17 +162,18 @@ describe Lti do
describe '#store_lti_session_data' do
let(:parameters) { {} }
before(:each) { controller.instance_variable_set(:@current_user, FactoryGirl.create(:external_user)) }
after(:each) { controller.send(:store_lti_session_data, consumer: FactoryGirl.build(:consumer), parameters: parameters) }
it 'stores data in the session' do
controller.instance_variable_set(:@current_user, FactoryGirl.create(:external_user))
expect(controller.session).to receive(:[]=).with(:consumer_id, anything)
expect(controller.session).to receive(:[]=).with(:external_user_id, anything)
expect(controller.session).to receive(:[]=).with(:lti_parameters, kind_of(Hash))
controller.send(:store_lti_session_data, consumer: FactoryGirl.build(:consumer), parameters: parameters)
end
it 'stores only selected tuples' do
expect(parameters).to receive(:slice).with(*Lti::SESSION_PARAMETERS)
it 'it creates an LtiParameter Object' do
before_count = LtiParameter.count
controller.send(:store_lti_session_data, consumer: FactoryGirl.build(:consumer), parameters: parameters)
expect(LtiParameter.count).to eq(before_count + 1)
end
end

View File

@ -230,11 +230,14 @@ describe ExercisesController do
describe 'POST #submit' do
let(:output) { {} }
let(:request) { post :submit, format: :json, id: exercise.id, submission: {cause: 'submit', exercise_id: exercise.id} }
let!(:external_user) { FactoryGirl.create(:external_user) }
let!(:lti_parameter) { FactoryGirl.create(:lti_parameter, external_user: external_user, exercise: exercise) }
before(:each) do
allow_any_instance_of(Submission).to receive(:normalized_score).and_return(1)
expect(controller).to receive(:collect_test_results).and_return([{score: 1, weight: 1}])
expect(controller).to receive(:score_submission).and_call_original
controller.session[:consumer_id] = external_user.consumer_id
end
context 'when LTI outcomes are supported' do

View File

@ -93,6 +93,7 @@ describe SessionsController do
end
it 'stores LTI parameters in the session' do
#Todo replace session with lti_parameter /should be done already
expect(controller).to receive(:store_lti_session_data)
request
end
@ -159,6 +160,7 @@ describe SessionsController do
end
it 'clears the session' do
#Todo replace session with lti_parameter /should be done already
expect(controller).to receive(:clear_lti_session_data)
delete :destroy
end
@ -174,13 +176,16 @@ describe SessionsController do
let(:submission) { FactoryGirl.create(:submission, exercise: FactoryGirl.create(:dummy)) }
before(:each) do
#Todo replace session with lti_parameter
session[:consumer_id] = consumer.id
session[:lti_parameters] = {}
#Todo create LtiParameter Object
# session[:lti_parameters] = {}
end
before(:each) { request.call }
it 'clears the session' do
#Todo replace session with lti_parameter /should be done already
expect(controller).to receive(:clear_lti_session_data)
request.call
end

View File

@ -32,7 +32,7 @@ FactoryGirl.define do
factory :java, class: ExecutionEnvironment do
created_by_teacher
default_memory_limit
docker_image 'hklement/ubuntu-java:latest'
docker_image 'openhpi/co_execenv_java:latest'
association :file_type, factory: :dot_java
help
name 'Java 8'
@ -78,7 +78,7 @@ FactoryGirl.define do
factory :python, class: ExecutionEnvironment do
created_by_teacher
default_memory_limit
docker_image 'hklement/ubuntu-python:latest'
docker_image 'openhpi/co_execenv_python:latest'
association :file_type, factory: :dot_py
help
name 'Python 3.4'

View File

@ -0,0 +1,20 @@
FactoryGirl.define do
LTI_PARAMETERS = {
lis_result_sourcedid: "c2db0c7c-4411-4b27-a52b-ddfc3dc32065",
lis_outcome_service_url: "http://172.16.54.235:3000/courses/0132156a-9afb-434d-83cc-704780104105/sections/21c6c6f4-1fb6-43b4-af3c-04fdc098879e/items/999b1fe6-d4b6-47b7-a577-ea2b4b1041ec/tool_grading",
launch_presentation_return_url: "http://172.16.54.235:3000/courses/0132156a-9afb-434d-83cc-704780104105/sections/21c6c6f4-1fb6-43b4-af3c-04fdc098879e/items/999b1fe6-d4b6-47b7-a577-ea2b4b1041ec/tool_return"
}
factory :lti_parameter do
association :consumer
association :exercise, factory: :math
association :external_user
lti_parameters LTI_PARAMETERS
trait :without_outcome_service_url do
lti_parameters LTI_PARAMETERS.except(:lis_outcome_service_url)
end
end
end

View File

@ -2,7 +2,7 @@ FactoryGirl.define do
factory :submission do
cause 'save'
created_by_external_user
association :exercise, factory: :fibonacci
association :exercise, factory: :math
after(:create) do |submission|
submission.exercise.files.editable.visible.each do |file|

View File

@ -3,8 +3,8 @@ require 'seeds_helper'
describe DockerClient, docker: true do
let(:command) { 'whoami' }
let(:docker_client) { described_class.new(execution_environment: FactoryGirl.build(:ruby), user: FactoryGirl.build(:admin)) }
let(:execution_environment) { FactoryGirl.build(:ruby) }
let(:docker_client) { described_class.new(execution_environment: FactoryGirl.build(:java), user: FactoryGirl.build(:admin)) }
let(:execution_environment) { FactoryGirl.build(:java) }
let(:image) { double }
let(:submission) { FactoryGirl.create(:submission) }
let(:workspace_path) { '/tmp' }