Skip to content

Commit

Permalink
Add API to MarkUs. Add possibility to push test results and view them…
Browse files Browse the repository at this point in the history
…/download them. See review 300. Closes ticket MarkUsProject#541
  • Loading branch information
Severin Gehwolf authored and Severin Gehwolf committed Jan 10, 2010
1 parent 0341f9b commit ba31d27
Show file tree
Hide file tree
Showing 43 changed files with 1,147 additions and 23 deletions.
74 changes: 74 additions & 0 deletions app/controllers/api/main_api_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
require 'base64'

#=== Description
# Scripting API handlers for MarkUs
module Api

#===Description
# This is the parent class of all API controllers.
# Shared functionality of all API controllers
# should go here.
class MainApiController < ActionController::Base

before_filter :authenticate

#=== Description
# Dummy action (for authentication testing)
# No public route matches this action.
def index
render :file => "#{RAILS_ROOT}/public/200.xml", :status => 200
end

private
#=== Description
# Auth handler for the MarkUs API. It uses
# the Authorization HTTP header to determine
# the user who issued the request. With the Authorization
# HTTP header comes a Base 64 encoded MD5 digest of the
# user's private key.
def authenticate
auth_token = parse_auth_token(request.headers["HTTP_AUTHORIZATION"])
# pretend resource not found if missing or wrong authentication
# is provided
if auth_token.nil?
render :file => "#{RAILS_ROOT}/public/403.xml", :status => 403
return
end
# Find user by api_key_md5
@current_user = User.find_by_api_key_md5(auth_token)
if @current_user.nil?
# Key does not exist, so bail out
render :file => "#{RAILS_ROOT}/public/403.xml", :status => 403
return
elsif @current_user.student?
# API is available for TAs and Admins only
render :file => "#{RAILS_ROOT}/public/403.xml", :status => 403
return
end
end


#=== Description
# Helper method for parsing the authentication token
def parse_auth_token(token)
return nil if token.nil?
if !(token =~ /MarkUsAuth ([^\s,]+)/).nil?
ret_token = $1
begin
ret_token = Base64.decode64(ret_token).strip
# we expect a MD5 sum string of length 32
return nil unless ret_token.length == 32
# now we are good, so it seems to be a valid
# token
return ret_token
rescue Exception
return nil
end
else
return nil
end
end

end

end # end Api module
225 changes: 225 additions & 0 deletions app/controllers/api/test_results_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
module Api

#=== Description
# Allows for pushing of test results into MarkUs (e.g. from automated test runs).
# Uses Rails' RESTful routes (check 'rake routes' for the configured routes)
class TestResultsController < MainApiController

#=== Description
# Triggered by a HTTP POST request to /api/test_results(.:format).
# Creates a new TestResult instance. Requires the following parameters:
# group_name: Name of the group to which the test result should be associated to
# assignment: Short identifier of the assignment
# filename: Filename of the test result
# file_content: Content of the test results
#=== Returns
# An XML response, indicating the success/failure for the request
def create
if !request.post?
# pretend this URL does not exist
render :file => "#{RAILS_ROOT}/public/404.html", :status => 404
return
end
if !has_required_http_params_including_file_content?(params)
# incomplete/invalid HTTP params
render :file => "#{RAILS_ROOT}/public/422.xml", :status => 422
return
end
# check if there's a valid submission
submission = Submission.get_submission_by_group_and_assignment(params[:group_name],
params[:assignment])
if submission.nil?
# no such submission
render :file => "#{RAILS_ROOT}/public/422.xml", :status => 422
return
end
# Request seems good. Check if filename already exists.
# If it does, update it instead of creating a new one.
new_test_result = submission.test_results.find_by_filename(params[:filename])
if new_test_result.nil?
if TestResult.create(:filename => params[:filename],
:file_content => params[:file_content],
:submission_id => submission.id)
# All good, so return a success response
render :file => "#{RAILS_ROOT}/public/200.xml", :status => 200
return
else
# Some other error occurred
render :file => "#{RAILS_ROOT}/public/500.xml", :status => 500
return
end
else
new_test_result.file_content = params[:file_content]
if new_test_result.save
# All good, so return a success response
render :file => "#{RAILS_ROOT}/public/200.xml", :status => 200
return
else
# Some other error occurred
render :file => "#{RAILS_ROOT}/public/500.xml", :status => 500
return
end
end
end

#=== Description
# Triggered by a HTTP DELETE request to /api/test_results(.:format).
# Deletes a TestResult instance. Requires the following parameters:
# group_name: Name of the group to which the test result should be associated to
# assignment: Short identifier of the assignment
# filename: Filename of the test result to be deleted
#=== Returns
# An XML response, indicating the success/failure for the request
def destroy
if !request.delete?
# pretend this URL does not exist
render :file => "#{RAILS_ROOT}/public/404.html", :status => 404
return
end
if !has_required_http_params?(params)
# incomplete/invalid HTTP params
render :file => "#{RAILS_ROOT}/public/422.xml", :status => 422
return
end
# check if there's a valid submission
submission = Submission.get_submission_by_group_and_assignment(params[:group_name],
params[:assignment])
if submission.nil?
# no such submission
render :file => "#{RAILS_ROOT}/public/422.xml", :status => 422
return
end
# request seems good
test_result = submission.test_results.find_by_filename(params[:filename])
if !test_result.nil?
if test_result.destroy
# Everything went fine; report success
render :file => "#{RAILS_ROOT}/public/200.xml", :status => 200
return
else
# Some other error occurred
render :file => "#{RAILS_ROOT}/public/500.xml", :status => 500
return
end
end
# The test result in question does not exist
render :file => "#{RAILS_ROOT}/public/404.xml", :status => 404
return
end

#=== Description
# Triggered by a HTTP PUT request to /api/test_results(.:format).
# Updates (overwrites) a TestResult instance. Requires the following parameters:
# group_name: Name of the group to which the test result should be associated to
# assignment: Short identifier of the assignment
# filename: Filename of the test result, which content should be updated
# file_content: New content of the test result
#=== Returns
# An XML response, indicating the success/failure for the request
def update
if !request.put?
# pretend this URL does not exist
render :file => "#{RAILS_ROOT}/public/404.html", :status => 404
return
end
if !has_required_http_params_including_file_content?(params)
# incomplete/invalid HTTP params
render :file => "#{RAILS_ROOT}/public/422.xml", :status => 422
return
end
# check if there's a valid submission
submission = Submission.get_submission_by_group_and_assignment(params[:group_name],
params[:assignment])
if submission.nil?
# no such submission
render :file => "#{RAILS_ROOT}/public/422.xml", :status => 422
return
end
# request seems good
test_result = submission.test_results.find_by_filename(params[:filename])
if !test_result.nil?
if test_result.update_file_content(params[:file_content])
# Everything went fine; report success
render :file => "#{RAILS_ROOT}/public/200.xml", :status => 200
return
else
# Some other error occurred
render :file => "#{RAILS_ROOT}/public/500.xml", :status => 500
return
end
end
# The test result in question does not exist
render :file => "#{RAILS_ROOT}/public/404.xml", :status => 404
return
end

#=== Description
# Triggered by a HTTP GET request to /api/test_results(.:format).
# Shows a TestResult instance. Requires the following parameters:
# group_name: Name of the group to which the test result should be associated to
# assignment: Short identifier of the assignment
# filename: New filename of the test result
#=== Returns
# The content of the test result file in question
def show
if !request.get?
# pretend this URL does not exist
render :file => "#{RAILS_ROOT}/public/404.html", :status => 404
return
end
if !has_required_http_params?(params)
# incomplete/invalid HTTP params
render :file => "#{RAILS_ROOT}/public/422.xml", :status => 422
return
end
# check if there's a valid submission
submission = Submission.get_submission_by_group_and_assignment(params[:group_name],
params[:assignment])
if submission.nil?
# no such submission
render :file => "#{RAILS_ROOT}/public/422.xml", :status => 422
return
end
# request seems good
test_result = submission.test_results.find_by_filename(params[:filename])
if !test_result.nil?
# Everything went fine; send file_content
send_data test_result.file_content, :disposition => 'inline',
:filename => test_result.filename
return
end
# The test result in question does not exist
render :file => "#{RAILS_ROOT}/public/404.xml", :status => 404
return
end

private

# Helper method to check for required HTTP parameters
def has_required_http_params?(param_hash)
# Note: The blank? method is a Rails extension.
# Specific keys have to be present, and their values
# must not be blank.
if !param_hash[:filename].blank? &&
!param_hash[:assignment].blank? &&
!param_hash[:group_name].blank?
return true
else
return false
end
end

# Helper method to check for required HTTP parameters including the
# file_content parameter
def has_required_http_params_including_file_content?(param_hash)
if has_required_http_params?(param_hash)
if !param_hash[:file_content].blank?
return true
end
end
return false
end

end # end TestResultsController

end
1 change: 1 addition & 0 deletions app/controllers/main_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def login
uri = session[:redirect_uri]
session[:redirect_uri] = nil
refresh_timeout
current_user.set_api_key # set api key in DB for user if not yet set
# redirect to last visited page or to main page
redirect_to( uri || { :action => 'index' } )
else
Expand Down
25 changes: 23 additions & 2 deletions app/controllers/results_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ class ResultsController < ApplicationController
before_filter :authorize_only_for_admin, :except => [:codeviewer, :edit, :update_mark, :view_marks,
:create, :add_extra_mark, :next_grouping, :update_overall_comment, :expand_criteria,
:collapse_criteria, :remove_extra_mark, :expand_unmarked_criteria, :update_marking_state,
:download, :note_message]
:download, :note_message, :render_test_result]
before_filter :authorize_for_ta_and_admin, :only => [:edit, :update_mark, :create, :add_extra_mark,
:download, :next_grouping, :update_overall_comment, :expand_criteria,
:collapse_criteria, :remove_extra_mark, :expand_unmarked_criteria,
:update_marking_state, :note_message]
before_filter :authorize_for_user, :only => [:codeviewer]
before_filter :authorize_for_user, :only => [:codeviewer, :render_test_result]
before_filter :authorize_for_student, :only => [:view_marks]

def note_message
Expand All @@ -29,6 +29,7 @@ def edit
@grouping = @result.submission.grouping
@group = @grouping.group
@files = @submission.submission_files
@test_result_files = @submission.test_results
@first_file = @files.first
@extra_marks_points = @result.extra_marks.points
@extra_marks_percentage = @result.extra_marks.percentage
Expand Down Expand Up @@ -161,6 +162,25 @@ def codeviewer
@code_type = @file.get_file_type
render :action => 'results/common/codeviewer'
end

#=== Description
# Action called via Rails' remote_function from the test_result_window partial
# Prepares test result and updates content in window.
def render_test_result
@assignment = Assignment.find(params[:id])
@test_result = TestResult.find(params[:test_result_id])

# Students can use this action only, when marks have been released
if current_user.student? &&
(@test_result.submission.grouping.membership_status(current_user).nil? ||
@test_result.submission.result.released_to_students == false)
render :partial => 'shared/handle_error',
:locals => {:error => I18n.t('test_result.error.no_access', :test_result_id => @test_result.id)}
return
end

render :action => 'results/render_test_result', :layout => "plain"
end

def update_mark
result_mark = Mark.find(params[:mark_id])
Expand Down Expand Up @@ -212,6 +232,7 @@ def view_marks
@annotation_categories = @assignment.annotation_categories
@group = @grouping.group
@files = @submission.submission_files
@test_result_files = @submission.test_results
@first_file = @files.first
@extra_marks_points = @result.extra_marks.points
@extra_marks_percentage = @result.extra_marks.percentage
Expand Down
Loading

0 comments on commit ba31d27

Please sign in to comment.