Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions app/controllers/code_ocean/files_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,23 @@ def show_protected_upload
@file = CodeOcean::File.find(params[:id])
authorize!
# The `@file.name_with_extension` is assembled based on the user-selected file type, not on the actual file name stored on disk.
raise Pundit::NotAuthorizedError if @embed_options[:disable_download] || @file.filepath != params[:filename] || @file.native_file.blank?
raise Pundit::NotAuthorizedError if @embed_options[:disable_download] || @file.filepath != params[:filename] || @file.attachment.blank?

real_location = Pathname(@file.native_file.current_path).realpath
send_file(real_location, type: 'application/octet-stream', filename: @file.name_with_extension, disposition: 'attachment')
url = rails_blob_path(@file.attachment, disposition: 'attachment', expires_in: 5.minutes)
redirect_to url, allow_other_host: true
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)

# The `@file.name_with_extension` is assembled based on the user-selected file type, not on the actual file name stored on disk.
raise Pundit::NotAuthorizedError unless @file.filepath == params[:filename] || @file.native_file.present?
raise Pundit::NotAuthorizedError unless @file.filepath == params[:filename] || @file.attachment.present?

url = rails_blob_path(@file.attachment, disposition: 'inline', expires_in: 5.minutes)

real_location = Pathname(@file.native_file.current_path).realpath
send_file(real_location, type: @file.native_file.content_type, filename: @file.name_with_extension)
redirect_to url, allow_other_host: true
end

def create
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/concerns/file_parameters.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ def reject_illegal_file_attributes(exercise, params)
private :reject_illegal_file_attributes

def file_attributes
%w[content context_id feedback_message file_id file_type_id hidden id name native_file path read_only role weight
file_template_id hidden_feedback]
%w[content context_id feedback_message file_id file_type_id hidden id name path read_only role weight
file_template_id hidden_feedback attachment]
end
private :file_attributes
end
31 changes: 15 additions & 16 deletions app/controllers/exercises_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,8 @@ def import_start

uuid = ProformaService::UuidFromZip.call(zip: zip_file)
exists, updatable = uuid_check(user: current_user, uuid:).values_at(:uuid_found, :update_right)

uploader = ProformaZipUploader.new
uploader.cache!(params[:file])
key = SecureRandom.hex
ActiveStorage::Blob.create_and_upload!(key:, io: zip_file, filename: zip_file.original_filename)

message = if exists && updatable
t('.exercise_exists_and_is_updatable')
Expand All @@ -177,7 +176,7 @@ def import_start
status: 'success',
message:,
actions: render_to_string(partial: 'import_actions',
locals: {exercise: @exercise, imported: false, exists:, updatable:, file_id: uploader.cache_name}),
locals: {exercise: @exercise, imported: false, exists:, updatable:, file_id: key}),
}
rescue ProformaXML::InvalidZip => e
render json: {
Expand All @@ -187,16 +186,16 @@ def import_start
end

def import_confirm
uploader = ProformaZipUploader.new
uploader.retrieve_from_cache!(params[:file_id])
exercise = ::ProformaService::Import.call(zip: uploader.file, user: current_user)
exercise.save!
ActiveStorage::Blob.find_by(key: params[:file_id]).open do |zip|
exercise = ::ProformaService::Import.call(zip:, user: current_user)
exercise.save!

render json: {
status: 'success',
message: t('.success'),
actions: render_to_string(partial: 'import_actions', locals: {exercise:, imported: true}),
}
render json: {
status: 'success',
message: t('.success'),
actions: render_to_string(partial: 'import_actions', locals: {exercise:, imported: true}),
}
end
rescue ProformaXML::ProformaError, ActiveRecord::RecordInvalid => e
render json: {
status: 'failure',
Expand Down Expand Up @@ -305,12 +304,12 @@ def exercise_params_with_tags
def handle_file_uploads
if exercise_params
exercise_params[:files_attributes].try(:each) do |_index, file_attributes|
if file_attributes[:content].respond_to?(:read)
if file_attributes[:attachment].respond_to?(:read)
if FileType.find_by(id: file_attributes[:file_type_id]).try(:binary?)
file_attributes[:native_file] = file_attributes[:content]
file_attributes[:content] = nil
else
file_attributes[:content] = file_attributes[:content].read.detect_encoding!.encode.delete("\x00")
file_attributes[:content] = file_attributes[:attachment].read.detect_encoding!.encode.delete("\x00")
file_attributes[:attachment] = nil
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/submissions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def download
def download_file
raise Pundit::NotAuthorizedError if @embed_options[:disable_download]

if @file.native_file?
if @file.attachment.attached?
redirect_to protected_upload_path(id: @file.id, filename: @file.filepath), status: :see_other
else
response.set_header('Content-Length', @file.size)
Expand Down Expand Up @@ -96,7 +96,7 @@ def render_file
end

# Finally grant access and send the file
if @file.native_file?
if @file.attachment.attached?
url = render_protected_upload_url(id: @file.id, filename: @file.filepath)
redirect_to AuthenticatedUrlHelper.sign(url, @file), status: :see_other
else
Expand Down
27 changes: 6 additions & 21 deletions app/models/code_ocean/file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ class File < ApplicationRecord

after_initialize :set_default_values
before_validation :clear_weight, unless: :teacher_defined_assessment?
before_validation :hash_content, if: :content_present?
before_validation :set_ancestor_values, if: :incomplete_descendent?

attr_writer :size
Expand All @@ -36,7 +35,7 @@ class File < ApplicationRecord
has_many :events_synchronized_editor, class_name: 'Event::SynchronizedEditor'
alias descendants files

mount_uploader :native_file, FileUploader
has_one_attached :attachment

scope :editable, -> { where(read_only: false) }
scope :visible, -> { where(hidden: false) }
Expand All @@ -50,7 +49,6 @@ class File < ApplicationRecord

validates :feedback_message, if: :teacher_defined_assessment?, presence: true
validates :feedback_message, absence: true, unless: :teacher_defined_assessment?
validates :hashed_content, if: :content_present?, presence: true
validates :hidden, inclusion: [true, false]
validates :hidden_feedback, inclusion: [true, false]
validates :name, presence: true
Expand All @@ -73,21 +71,13 @@ class File < ApplicationRecord
end

def read
if native_file?
return nil unless native_file_location_valid?

native_file.read
if attachment.attached?
attachment.download
else
content
end
end

def native_file_location_valid?
real_location = Pathname(native_file.current_path).realpath
upload_location = Pathname(::File.join(native_file.root, 'uploads')).realpath
real_location.fnmatch? ::File.join(upload_location.to_s, '**')
end

def ancestor_id
file_id || id
end
Expand All @@ -102,7 +92,7 @@ def teacher_defined_assessment?
end

def content_present?
content? || native_file?
content? || attachment.attached?
end
private :content_present?

Expand All @@ -122,11 +112,6 @@ def filepath_without_extension
end
end

def hash_content
self.hashed_content = Digest::MD5.new.hexdigest(read || '')
end
private :hash_content

def incomplete_descendent?
file_id.present? && file_type_id.blank?
end
Expand Down Expand Up @@ -158,8 +143,8 @@ def visible?
end

def size
@size ||= if native_file?
native_file.size
@size ||= if attachment.attached?
attachment.byte_size
else
content.size
end
Expand Down
5 changes: 0 additions & 5 deletions app/policies/code_ocean/file_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ def author?
end

def show?
return no_one if @record.native_file? && [email protected]_file_location_valid?

if @record.context.is_a?(Exercise)
admin? || author? || [email protected]
else
Expand All @@ -17,8 +15,6 @@ def show?
end

def show_protected_upload?
return no_one if @record.native_file? && [email protected]_file_location_valid?

if @record.context.is_a?(Exercise)
admin? || author? || ([email protected] && [email protected])
else
Expand All @@ -27,7 +23,6 @@ def show_protected_upload?
end

def render_protected_upload?
return no_one if @record.native_file? && [email protected]_file_location_valid?
return no_one if @record.context.is_a?(Exercise) && (@record.context.unpublished || @record.hidden)

# The AuthenticatedUrlHelper will check for more details, but we cannot determine a specific user
Expand Down
2 changes: 1 addition & 1 deletion app/services/proforma_service/convert_exercise_to_task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ def filename(file)
end

def add_content_to_task_file(file, task_file)
if file.native_file.present?
if file.attachment.attached?
file_content = file.read
task_file.content = file_content
task_file.used_by_grader = false
Expand Down
2 changes: 1 addition & 1 deletion app/services/proforma_service/convert_task_to_exercise.rb
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def codeocean_file_from_task_file(file, parent_object = nil)
xml_id_path: (parent_object.nil? ? [file.id] : [parent_object.id, file.id]).map(&:to_s)
)
if file.binary
codeocean_file.native_file = FileIO.new(file.content.dup.force_encoding('UTF-8'), File.basename(file.filename))
codeocean_file.attachment.attach(io: StringIO.new(file.content.dup.force_encoding('UTF-8')), filename: file.filename)
else
codeocean_file.content = file.content
end
Expand Down
7 changes: 0 additions & 7 deletions app/uploaders/proforma_zip_uploader.rb

This file was deleted.

4 changes: 2 additions & 2 deletions app/views/exercises/_code_field.html.slim
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.mb-3
= form.label(attribute, class: 'form-label')
= form.text_area(attribute, class: 'code-field form-control', rows: 16, style: 'display:none;')
= form.label('content', class: 'form-label')
= form.text_area('content', class: 'code-field form-control', rows: 16, style: 'display:none;')
= render(partial: 'editor_edit', locals: {exercise: @exercise})
.card.border-warning.p-2.my-2
= form.file_field(attribute, class: 'form-control', style: 'display: inline-block;')
Expand Down
2 changes: 1 addition & 1 deletion app/views/exercises/_editor_frame.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ div class=(defined?(own_solution) ? 'own-frame' : 'frame') data-executable=file.
- elsif file.file_type.video?
= video_tag(file_path, controls: true)
- else
= link_to(file.native_file.file.filename, file_path)
= link_to(file.attachment.filename, file_path)
- else
.editor-content.d-none data-file-id=file.ancestor_id = file.content
div class=(defined?(own_solution) ? 'own-editor' : 'editor') data-file-id=file.ancestor_id data-indent-size=file.file_type.indent_size data-mode=file.file_type.editor_mode data-allow-auto-completion=exercise.allow_auto_completion.to_s data-id=file.id
3 changes: 2 additions & 1 deletion app/views/exercises/_file_form.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,5 @@ li.card.mt-2
.mb-3
= f.label(:weight, class: 'form-label')
= f.number_field(:weight, class: 'form-control', min: 0, step: 'any')
= render('code_field', attribute: :content, form: f)
= render('code_field', attribute: :attachment, form: f)
/ = render('code_field', attribute: :content, form: f)
2 changes: 1 addition & 1 deletion app/views/shared/_file.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
= row(label: 'code_ocean/file.hidden_feedback', value: file.hidden_feedback)
= row(label: 'code_ocean/file.feedback_message', value: render_markdown(file.feedback_message), class: 'm-0')
= row(label: 'code_ocean/file.weight', value: file.weight)
= row(label: 'code_ocean/file.content', value: file.native_file? ? link_to_if(policy(file).show?, file.native_file.file.filename, protected_upload_path(id: file.id, filename: file.filepath)) : code_tag(file.content, file.file_type.programming_language))
= row(label: 'code_ocean/file.content', value: file.attachment.attached? ? link_to_if(policy(file).show?, file.attachment.filename, protected_upload_path(id: file.id, filename: file.filepath)) : code_tag(file.content, file.file_type.programming_language))
5 changes: 0 additions & 5 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,6 @@ class Application < Rails::Application

config.action_mailer.preview_paths << Rails.root.join('spec/mailers/previews')

# Specify default options for Rails generators
config.generators do |g|
g.orm :active_record, primary_key_type: :uuid
end

# Allow tables in addition to existing default tags
config.action_view.sanitized_allowed_tags = ActionView::Base.sanitized_allowed_tags + %w[table thead tbody tfoot td tr details summary]

Expand Down
2 changes: 1 addition & 1 deletion db/cable_schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# frozen_string_literal: true

# This migration comes from active_storage (originally 20170806125915)
class CreateActiveStorageTables < ActiveRecord::Migration[7.0]
def change
# Use Active Record's configured type for primary and foreign keys
primary_key_type, foreign_key_type = primary_and_foreign_key_types

create_table :active_storage_blobs, id: primary_key_type do |t|
t.string :key, null: false
t.string :filename, null: false
t.string :content_type
t.text :metadata
t.string :service_name, null: false
t.bigint :byte_size, null: false
t.string :checksum

if connection.supports_datetime_with_precision?
t.datetime :created_at, precision: 6, null: false
else
t.datetime :created_at, null: false
end

t.index [:key], unique: true
end

create_table :active_storage_attachments, id: primary_key_type do |t|
t.string :name, null: false
t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type
t.references :blob, null: false, type: foreign_key_type

if connection.supports_datetime_with_precision?
t.datetime :created_at, precision: 6, null: false
else
t.datetime :created_at, null: false
end

t.index %i[record_type record_id name blob_id], name: :index_active_storage_attachments_uniqueness, unique: true
t.foreign_key :active_storage_blobs, column: :blob_id
end

create_table :active_storage_variant_records, id: primary_key_type do |t|
t.belongs_to :blob, null: false, index: false, type: foreign_key_type
t.string :variation_digest, null: false

t.index %i[blob_id variation_digest], name: :index_active_storage_variant_records_uniqueness, unique: true
t.foreign_key :active_storage_blobs, column: :blob_id
end
end

private

def primary_and_foreign_key_types
config = Rails.configuration.generators
setting = config.options[config.orm][:primary_key_type]
primary_key_type = setting || :primary_key
foreign_key_type = setting || :bigint
[primary_key_type, foreign_key_type]
end
end
2 changes: 1 addition & 1 deletion db/queue_schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading