diff --git a/app/controllers/application.rb b/app/controllers/application.rb --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -5,6 +5,18 @@ # Pick a unique cookie name to distinguish our session data from others' session :session_key => '_grader_session_id' + SINGLE_USER_MODE_CONF_KEY = 'system.single_user_mode' + + def authorization_by_roles(allowed_roles) + return false unless authenticate + user = User.find(session[:user_id]) + unless user.roles.detect { |role| allowed_roles.member?(role.name) } + flash[:notice] = 'You are not authorized to view the page you requested' + redirect_to :controller => 'main', :action => 'login' + return false + end + end + protected def authenticate unless session[:user_id] @@ -13,7 +25,7 @@ end # check if run in single user mode - if defined?(SINGLE_USER_MODE) and (SINGLE_USER_MODE) + if (Configuration[SINGLE_USER_MODE_CONF_KEY]) user = User.find(session[:user_id]) if user==nil or user.login != 'root' redirect_to :controller => 'main', :action => 'login' @@ -30,7 +42,7 @@ unless user.roles.detect { |role| role.rights.detect{ |right| right.controller == self.class.controller_name and - (right.action == 'all' or right.action == action_name) + (right.action == 'all' or right.action == action_name) } } flash[:notice] = 'You are not authorized to view the page you requested' diff --git a/app/controllers/configurations_controller.rb b/app/controllers/configurations_controller.rb new file mode 100644 --- /dev/null +++ b/app/controllers/configurations_controller.rb @@ -0,0 +1,20 @@ +class ConfigurationsController < ApplicationController + + before_filter :authenticate + before_filter { |controller| controller.authorization_by_roles(['admin'])} + + in_place_edit_for :configuration, :key + in_place_edit_for :configuration, :type + in_place_edit_for :configuration, :value + + def index + @configurations = Configuration.find(:all, + :order => '`key`') + end + + def reload + Configuration.reload + redirect_to :action => 'index' + end + +end diff --git a/app/controllers/main_controller.rb b/app/controllers/main_controller.rb --- a/app/controllers/main_controller.rb +++ b/app/controllers/main_controller.rb @@ -11,7 +11,13 @@ end def login + saved_notice = flash[:notice] reset_session + flash[:notice] = saved_notice + + @title = Configuration['ui.front.title'] + @welcome = Configuration['ui.front.welcome_message'] + render :action => 'login', :layout => 'empty' end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -19,6 +19,11 @@ # general options append_to menu_items, '[Settings]', 'users', 'index' + + if (user!=nil) and (user.admin?) + append_to menu_items, '[Site config]', 'configurations', 'index' + end + append_to menu_items, '[Log out]', 'main', 'login' menu_items diff --git a/app/helpers/configurations_helper.rb b/app/helpers/configurations_helper.rb new file mode 100644 --- /dev/null +++ b/app/helpers/configurations_helper.rb @@ -0,0 +1,2 @@ +module ConfigurationsHelper +end diff --git a/app/models/configuration.rb b/app/models/configuration.rb new file mode 100644 --- /dev/null +++ b/app/models/configuration.rb @@ -0,0 +1,43 @@ +class Configuration < ActiveRecord::Base + + @@configurations = nil + + def self.get(key) + if @@configurations == nil + self.read_config + end + return @@configurations[key] + end + + def self.[](key) + self.get(key) + end + + def self.reload + self.read_config + end + + def self.clear + @@configurations = nil + end + + protected + def self.read_config + @@configurations = {} + Configuration.find(:all).each do |conf| + key = conf.key + val = conf.value + case conf.value_type + when 'string' + @@configurations[key] = val + + when 'integer' + @@configurations[key] = val.to_i + + when 'boolean' + @@configurations[key] = (val=='true') + end + end + end + +end diff --git a/app/views/configurations/index.html.haml b/app/views/configurations/index.html.haml new file mode 100644 --- /dev/null +++ b/app/views/configurations/index.html.haml @@ -0,0 +1,26 @@ +- content_for :head do + = javascript_include_tag :defaults + +%h1 Grader configuration + +%table + %tr + %th Key + %th Type + %th Value + + - @configurations.each do |conf| + - @configuration = conf + %tr + %td + = in_place_editor_field :configuration, :key, {}, :rows=>1 + %td + = in_place_editor_field :configuration, :value_type, {}, :rows=>1 + %td + = in_place_editor_field :configuration, :value, {}, :rows=>1 + +%br/ += link_to '[Reload configuration]', :action => 'reload' +%br/ +Your config is saved, but it does not automatically take effect. +You must reload. diff --git a/app/views/main/login.rhtml b/app/views/main/login.rhtml --- a/app/views/main/login.rhtml +++ b/app/views/main/login.rhtml @@ -1,6 +1,6 @@ -

Grader

+

<%= @title %>

-Welcome back!
+<%= @welcome %>
Please login to see the problem list.

<% if flash[:notice] %> diff --git a/config/environment.rb.SAMPLE b/config/environment.rb.SAMPLE --- a/config/environment.rb.SAMPLE +++ b/config/environment.rb.SAMPLE @@ -62,6 +62,3 @@ # These are where inputs and outputs of test requests are stored TEST_REQUEST_INPUT_FILE_DIR = RAILS_ROOT + '/data/test_request/input' TEST_REQUEST_OUTPUT_FILE_DIR = RAILS_ROOT + '/data/test_request/output' - -# Uncomment this for single user mode (only root is allowed to log in) -# SINGLE_USER_MODE = true diff --git a/db/migrate/022_create_configurations.rb b/db/migrate/022_create_configurations.rb new file mode 100644 --- /dev/null +++ b/db/migrate/022_create_configurations.rb @@ -0,0 +1,37 @@ +class CreateConfigurations < ActiveRecord::Migration + def self.up + create_table :configurations do |t| + t.column :key, :string + t.column :value_type, :string + t.column :value, :string + t.timestamps + end + + Configuration.reset_column_information + + Configuration.create(:key => 'system.single_user_mode', + :value_type => 'boolean', + :value => 'false') + + Configuration.create(:key => 'ui.front.title', + :value_type => 'string', + :value => 'Grader') + + Configuration.create(:key => 'ui.front.welcome_message', + :value_type => 'string', + :value => 'Welcome!') + + Configuration.create(:key => 'ui.show_score', + :value_type => 'boolean', + :value => 'true') + + Configuration.create(:key => 'contest.time_limit', + :value_type => 'string', + :value => 'unlimited') + + end + + def self.down + drop_table :configurations + end +end diff --git a/db/schema.rb b/db/schema.rb --- a/db/schema.rb +++ b/db/schema.rb @@ -9,7 +9,15 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 21) do +ActiveRecord::Schema.define(:version => 22) do + + create_table "configurations", :force => true do |t| + t.string "key" + t.string "value_type" + t.string "value" + t.datetime "created_at" + t.datetime "updated_at" + end create_table "grader_processes", :force => true do |t| t.string "host", :limit => 20 diff --git a/spec/controllers/main_controller_spec.rb b/spec/controllers/main_controller_spec.rb --- a/spec/controllers/main_controller_spec.rb +++ b/spec/controllers/main_controller_spec.rb @@ -14,27 +14,27 @@ :source => 'sample source', :compiler_message => 'none') @user = mock(User, :id => 1, :login => 'john') - Submission.should_receive(:find).with(@user.id.to_s).and_return(@submission) + Submission.should_receive(:find).with(@submission.id.to_s).and_return(@submission) end it "should let user sees her own source" do - get 'source', {:id => 1}, {:user_id => 1} + get 'source', {:id => @submission.id}, {:user_id => 1} response.should be_success end it "should let user sees her own compiler message" do - get 'compiler_msg', {:id => 1}, {:user_id => 1} + get 'compiler_msg', {:id => @submission.id}, {:user_id => 1} response.should be_success end it "should not let user sees other user's source" do - get 'source', {:id => 1}, {:user_id => 2} + get 'source', {:id => @submission.id}, {:user_id => 2} flash[:notice].should =~ /[Ee]rror/ response.should redirect_to(:action => 'list') end it "should not let user sees other user's compiler message" do - get 'compiler_msg', {:id => 1}, {:user_id => 2} + get 'compiler_msg', {:id => @submission.id}, {:user_id => 2} flash[:notice].should =~ /[Ee]rror/ response.should redirect_to(:action => 'list') end diff --git a/spec/models/configuration_spec.rb b/spec/models/configuration_spec.rb new file mode 100644 --- /dev/null +++ b/spec/models/configuration_spec.rb @@ -0,0 +1,90 @@ + +require File.dirname(__FILE__) + '/../spec_helper' + +describe Configuration do + + before(:each) do + @int_config = mock(Configuration, + :id => 1, + :key => 'mode', + :value_type => 'integer', + :value => '30') + + @string_config = mock(Configuration, + :id => 2, + :key => 'title', + :value_type => 'string', + :value => 'Hello') + + @boolean_config = mock(Configuration, + :id => 3, + :key => 'single_user_mode', + :value_type => 'boolean', + :value => 'true') + end + + it "should retrieve int config" do + Configuration.should_receive(:find).once.with(:all). + and_return([@int_config, @string_config, @boolean_config]) + + Configuration.clear + val = Configuration.get('mode') + val.should == 30 + end + + it "should retrieve boolean config" do + Configuration.should_receive(:find).once.with(:all). + and_return([@int_config, @string_config, @boolean_config]) + + Configuration.clear + val = Configuration.get('single_user_mode') + val.should == true + end + + it "should retrieve string config" do + Configuration.should_receive(:find).once.with(:all). + and_return([@int_config, @string_config, @boolean_config]) + + Configuration.clear + val = Configuration.get('title') + val.should == "Hello" + end + + it "should retrieve config with []" do + Configuration.should_receive(:find).once.with(:all). + and_return([@int_config, @string_config, @boolean_config]) + + Configuration.clear + val = Configuration['title'] + val.should == "Hello" + end + + it "should read config table once" do + Configuration.should_receive(:find).once.with(:all). + and_return([@int_config, @string_config, @boolean_config]) + + Configuration.clear + val = Configuration.get('title') + val.should == "Hello" + val = Configuration.get('single_user_mode') + val.should == true + val = Configuration.get('mode') + val.should == 30 + end + + it "should be able to reload config" do + Configuration.should_receive(:find).twice.with(:all). + and_return([@int_config, @string_config, @boolean_config], + [mock(Configuration, + :key => 'title', :value_type => 'string', + :value => 'Goodbye')]) + + Configuration.clear + val = Configuration.get('title') + val.should == "Hello" + Configuration.reload + val = Configuration.get('title') + val.should == "Goodbye" + end + +end diff --git a/test/fixtures/configurations.yml b/test/fixtures/configurations.yml new file mode 100644 --- /dev/null +++ b/test/fixtures/configurations.yml @@ -0,0 +1,7 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html + +# one: +# column: value +# +# two: +# column: value diff --git a/test/functional/configurations_controller_test.rb b/test/functional/configurations_controller_test.rb new file mode 100644 --- /dev/null +++ b/test/functional/configurations_controller_test.rb @@ -0,0 +1,8 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class ConfigurationsControllerTest < ActionController::TestCase + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/test/unit/configuration_test.rb b/test/unit/configuration_test.rb new file mode 100644 --- /dev/null +++ b/test/unit/configuration_test.rb @@ -0,0 +1,8 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class ConfigurationTest < ActiveSupport::TestCase + # Replace this with your real tests. + def test_truth + assert true + end +end