diff --git a/app/assets/javascripts/shell.js b/app/assets/javascripts/shell.js index 73ba02ae..509fa6df 100644 --- a/app/assets/javascripts/shell.js +++ b/app/assets/javascripts/shell.js @@ -8,7 +8,8 @@ $(document).on('turbolinks:load', function () { const executeCommand = function (command) { $.ajax({ data: { - command: command + command: command, + sudo: $('#sudo').is(':checked') }, method: 'POST', url: $('#shell').data('url') @@ -92,6 +93,12 @@ $(document).on('turbolinks:load', function () { const command = $('#command') command.focus(); command.on('keypress', handleKeyPress); + + const sudo = $('#sudo'); + sudo.on('change', function () { + sudo.parent().toggleClass('text-muted') + command.focus(); + }); } }) ; diff --git a/app/controllers/execution_environments_controller.rb b/app/controllers/execution_environments_controller.rb index 05e58acb..0f400196 100644 --- a/app/controllers/execution_environments_controller.rb +++ b/app/controllers/execution_environments_controller.rb @@ -29,7 +29,8 @@ class ExecutionEnvironmentsController < ApplicationController def execute_command runner = Runner.for(current_user, @execution_environment) - output = runner.execute_command(params[:command], raise_exception: false) + sudo = ActiveModel::Type::Boolean.new.cast(params[:sudo]) + output = runner.execute_command(params[:command], privileged_execution: sudo, raise_exception: false) render json: output.except(:messages) end diff --git a/app/models/runner.rb b/app/models/runner.rb index 767cd8a9..1de054d6 100644 --- a/app/models/runner.rb +++ b/app/models/runner.rb @@ -52,7 +52,7 @@ class Runner < ApplicationRecord @strategy.copy_files(files) end - def attach_to_execution(command, &block) + def attach_to_execution(command, privileged_execution: false, &block) Rails.logger.debug { "#{Time.zone.now.getutc.inspect}: Starting execution with Runner #{id} for #{user_type} #{user_id}." } starting_time = Time.zone.now begin @@ -62,7 +62,7 @@ class Runner < ApplicationRecord # initializing its Runner::Connection with the given event loop. The Runner::Connection class ensures that # this event loop is stopped after the socket was closed. event_loop = Runner::EventLoop.new - socket = @strategy.attach_to_execution(command, event_loop, starting_time, &block) + socket = @strategy.attach_to_execution(command, event_loop, starting_time, privileged_execution: privileged_execution, &block) event_loop.wait raise socket.error if socket.error.present? rescue Runner::Error => e @@ -74,7 +74,7 @@ class Runner < ApplicationRecord Time.zone.now - starting_time # execution duration end - def execute_command(command, raise_exception: true) + def execute_command(command, privileged_execution: false, raise_exception: true) output = { stdout: +'', stderr: +'', @@ -89,7 +89,7 @@ class Runner < ApplicationRecord save end - execution_time = attach_to_execution(command) do |socket, starting_time| + execution_time = attach_to_execution(command, privileged_execution: privileged_execution) do |socket, starting_time| socket.on :stderr do |data| output[:stderr] << data output[:messages].push({cmd: :write, stream: :stderr, log: data, timestamp: Time.zone.now - starting_time}) diff --git a/app/views/execution_environments/shell.html.slim b/app/views/execution_environments/shell.html.slim index f4031e25..21baabbd 100644 --- a/app/views/execution_environments/shell.html.slim +++ b/app/views/execution_environments/shell.html.slim @@ -1,8 +1,11 @@ h1 = @execution_environment #shell data-message-timeout=t('exercises.editor.timeout', permitted_execution_time: @execution_environment.permitted_execution_time) data-message-out-of-memory=t('exercises.editor.out_of_memory', memory_limit: @execution_environment.memory_limit) data-url=execute_command_execution_environment_path(@execution_environment) - .mb-3 - label for='command' = t('.command') + label.form-label for='command' = t('execution_environments.shell.command') + .input-group.mb-3 + .input-group-text.form-switch.ps-5.text-muted + input#sudo.form-check-input.mt-0 type='checkbox' + label.ms-2 for='sudo' = 'sudo' input#command.form-control type='text' pre#output data-message-no-output=t('exercises.implement.no_output', timestamp: l(Time.now, format: :short)) p = t('exercises.implement.no_output_yet') diff --git a/lib/runner/strategy.rb b/lib/runner/strategy.rb index 6b2093c6..6844ed4d 100644 --- a/lib/runner/strategy.rb +++ b/lib/runner/strategy.rb @@ -33,7 +33,7 @@ class Runner::Strategy raise NotImplementedError end - def attach_to_execution(_command, _event_loop, _starting_time) + def attach_to_execution(_command, _event_loop, _starting_time, _privileged_execution:) raise NotImplementedError end diff --git a/lib/runner/strategy/docker_container_pool.rb b/lib/runner/strategy/docker_container_pool.rb index c2d53ece..0da9da30 100644 --- a/lib/runner/strategy/docker_container_pool.rb +++ b/lib/runner/strategy/docker_container_pool.rb @@ -104,7 +104,7 @@ class Runner::Strategy::DockerContainerPool < Runner::Strategy Rails.logger.debug { "#{Time.zone.now.getutc.inspect}: Finished copying files" } end - def attach_to_execution(command, event_loop, starting_time) + def attach_to_execution(command, event_loop, starting_time, _privileged_execution: false) reset_inactivity_timer @command = command diff --git a/lib/runner/strategy/null.rb b/lib/runner/strategy/null.rb index a47ac51c..4d37e87a 100644 --- a/lib/runner/strategy/null.rb +++ b/lib/runner/strategy/null.rb @@ -25,7 +25,7 @@ class Runner::Strategy::Null < Runner::Strategy def copy_files(_files); end - def attach_to_execution(command, event_loop, starting_time) + def attach_to_execution(command, event_loop, starting_time, _privileged_execution: false) socket = Connection.new(nil, self, event_loop) # We don't want to return an error if the execution environment is changed socket.status = :terminated_by_codeocean if command == ExecutionEnvironment::VALIDATION_COMMAND diff --git a/lib/runner/strategy/poseidon.rb b/lib/runner/strategy/poseidon.rb index e65f3384..4d74d920 100644 --- a/lib/runner/strategy/poseidon.rb +++ b/lib/runner/strategy/poseidon.rb @@ -134,8 +134,8 @@ class Runner::Strategy::Poseidon < Runner::Strategy Rails.logger.debug { "#{Time.zone.now.getutc.inspect}: Finished copying files" } end - def attach_to_execution(command, event_loop, starting_time) - websocket_url = execute_command(command) + def attach_to_execution(command, event_loop, starting_time, privileged_execution: false) + websocket_url = execute_command(command, privileged_execution: privileged_execution) socket = Connection.new(websocket_url, self, event_loop) yield(socket, starting_time) socket @@ -243,12 +243,12 @@ class Runner::Strategy::Poseidon < Runner::Strategy private - def execute_command(command) + def execute_command(command, privileged_execution: false) url = "#{runner_url}/execute" body = { command: command, timeLimit: @execution_environment.permitted_execution_time, - privilegedExecution: @execution_environment.privileged_execution, + privilegedExecution: privileged_execution || @execution_environment.privileged_execution, } Rails.logger.debug { "#{Time.zone.now.getutc.inspect}: Preparing command execution at #{url}: #{command}" } response = self.class.http_connection.post url, body.to_json