Add support for signed URLs used by the render_file function

This commit is contained in:
Sebastian Serth
2022-09-23 11:26:56 +02:00
parent 5881795d5f
commit 16c00ec136
16 changed files with 229 additions and 31 deletions

View File

@ -7,6 +7,7 @@ class ApplicationController < ActionController::Base
MEMBER_ACTIONS = %i[destroy edit show update].freeze
RENDER_HOST = CodeOcean::Config.new(:code_ocean).read[:render_host]
before_action :deny_access_from_render_host
after_action :verify_authorized, except: %i[welcome]
around_action :mnemosyne_trace
around_action :switch_locale
@ -68,7 +69,7 @@ class ApplicationController < ActionController::Base
id: current_user.id,
type: current_user.class.name,
username: current_user.displayname,
consumer: current_user.consumer.name
consumer: current_user.consumer&.name
)
end
private :set_sentry_context
@ -95,10 +96,13 @@ class ApplicationController < ActionController::Base
def render_error(message, status)
set_sentry_context
respond_to do |format|
format.html do
format.any do
# Prevent redirect loop
if request.url == request.referer
redirect_to :root, alert: message
# Redirect to main domain if the request originated from our render_host
elsif request.path == '/' && request.host == RENDER_HOST
redirect_to Rails.application.config.action_mailer.default_url_options
else
redirect_back fallback_location: :root, allow_other_host: false, alert: message
end
@ -116,6 +120,10 @@ class ApplicationController < ActionController::Base
end
private :switch_locale
def deny_access_from_render_host
raise Pundit::NotAuthorizedError if RENDER_HOST.present? && request.host == RENDER_HOST
end
def welcome
# Show root page
end

View File

@ -5,6 +5,12 @@ module CodeOcean
include CommonBehavior
include FileParameters
# Overwrite the CSP header and some default actions for the :render_protected_upload action
content_security_policy false, only: :render_protected_upload
skip_before_action :deny_access_from_render_host, only: :render_protected_upload
skip_before_action :verify_authenticity_token, only: :render_protected_upload
before_action :require_user!, except: :render_protected_upload
def authorize!
authorize(@file)
end
@ -19,6 +25,18 @@ module CodeOcean
send_file(real_location, type: @file.native_file.content_type, filename: @file.name_with_extension, disposition: 'attachment')
end
def render_protected_upload
# Set @current_user with a new *learner* for Pundit checks
@current_user = ExternalUser.new
@file = authorize AuthenticatedUrlHelper.retrieve!(CodeOcean::File, request)
raise Pundit::NotAuthorizedError unless @file.name_with_extension == params[:filename]
real_location = Pathname(@file.native_file.current_path).realpath
send_file(real_location, type: @file.native_file.content_type, filename: @file.name_with_extension)
end
def create
@file = CodeOcean::File.new(file_params)
if @file.file_template_id

View File

@ -7,22 +7,17 @@ class SubmissionsController < ApplicationController
include SubmissionParameters
include Tubesock::Hijack
before_action :require_user!
before_action :set_submission, only: %i[download download_file render_file run score show statistics test]
before_action :set_submission, only: %i[download download_file run score show statistics test]
before_action :set_testrun, only: %i[run score test]
before_action :set_files, only: %i[download show]
before_action :set_files_and_specific_file, only: %i[download_file render_file run test]
before_action :set_files_and_specific_file, only: %i[download_file run test]
before_action :set_content_type_nosniff, only: %i[download download_file render_file]
# Overwrite the CSP header for the :render_file action
content_security_policy only: :render_file do |policy|
policy.img_src :none
policy.script_src :none
policy.font_src :none
policy.style_src :none
policy.connect_src :none
policy.form_action :none
end
# Overwrite the CSP header and some default actions for the :render_file action
content_security_policy false, only: :render_file
skip_before_action :deny_access_from_render_host, only: :render_file
skip_before_action :verify_authenticity_token, only: :render_file
before_action :require_user!, except: :render_file
def create
@submission = Submission.new(submission_params)
@ -80,10 +75,28 @@ class SubmissionsController < ApplicationController
end
def render_file
# If a file should not be downloaded, it should not be rendered either
raise Pundit::NotAuthorizedError if @embed_options[:disable_download]
# Set @current_user with a new *learner* for Pundit checks
@current_user = ExternalUser.new
send_data(@file.read, filename: @file.name_with_extension, disposition: 'inline')
@submission = authorize AuthenticatedUrlHelper.retrieve!(Submission, request, cookies)
# Throws an exception if the file is not found
set_files_and_specific_file
# Allows access to other files of the same submission, e.g., a linked JS or CSS file where we cannot expect a token in the URL
cookie_name = AuthenticatedUrlHelper.cookie_name_for(:render_file_token)
if params[AuthenticatedUrlHelper.query_parameter].present?
cookies[cookie_name] = AuthenticatedUrlHelper.prepare_short_living_cookie(request.url)
cookies.commit!
end
# Finally grant access and send the file
if @file.native_file?
url = render_protected_upload_url(id: @file.id, filename: @file.name_with_extension)
redirect_to AuthenticatedUrlHelper.sign(url, @file)
else
send_data(@file.content, filename: @file.name_with_extension, disposition: 'inline')
end
end
# rubocop:disable Metrics/CyclomaticComplexity
@ -372,7 +385,7 @@ class SubmissionsController < ApplicationController
# @file contains the specific file requested for run / test / render / ...
set_files
@file = @files.detect {|file| file.filepath == sanitize_filename }
head :not_found unless @file
raise ActiveRecord::RecordNotFound unless @file
end
def set_files