diff --git a/decidim-core/app/controllers/concerns/decidim/direct_upload.rb b/decidim-core/app/controllers/concerns/decidim/direct_upload.rb new file mode 100644 index 000000000000..6be4849e89a3 --- /dev/null +++ b/decidim-core/app/controllers/concerns/decidim/direct_upload.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +module Decidim + module DirectUpload + extend ActiveSupport::Concern + + included do + include Decidim::NeedsOrganization + skip_before_action :verify_organization + + before_action :check_organization! + before_action :check_authenticated! + before_action :validate_direct_upload + end + + protected + + def verify_organization; end + + def validate_direct_upload + return if current_admin.present? + + head :unprocessable_entity unless [ + maximum_allowed_size.try(:to_i) >= blob_args[:byte_size].try(:to_i), + content_types.any? { |pattern| pattern.match?(blob_args[:content_type]) }, + content_types.any? { |pattern| pattern.match?(MiniMime.lookup_by_extension(extension)&.content_type) }, + allowed_extensions.any? { |pattern| pattern.match?(extension) } + ].all? + rescue NoMethodError + head :unprocessable_entity + end + + def extension + File.extname(blob_args[:filename]).delete(".") + end + + def maximum_allowed_size + current_organization.settings.upload_maximum_file_size + end + + def check_organization! + head :unauthorized if current_organization.blank? && current_admin.blank? + end + + def check_authenticated! + head :unauthorized if current_user.blank? && current_admin.blank? + end + + def allowed_extensions + if user_has_elevated_role? + current_organization.settings.upload_allowed_file_extensions_admin + else + current_organization.settings.upload_allowed_file_extensions + end + end + + def content_types + if user_has_elevated_role? + current_organization.settings.upload_allowed_content_types_admin + else + current_organization.settings.upload_allowed_content_types + end + end + + private + + def user_has_elevated_role? + [ + current_user&.admin?, + defined?(Decidim::Assemblies::AssembliesWithUserRole) && Decidim::Assemblies::AssembliesWithUserRole.for(current_user).any?, + defined?(Decidim::Conferences::ConferencesWithUserRole) && Decidim::Conferences::ConferencesWithUserRole.for(current_user).any?, + defined?(Decidim::ParticipatoryProcessesWithUserRole) && Decidim::ParticipatoryProcessesWithUserRole.for(current_user).any? + ].any? + end + end +end diff --git a/decidim-core/app/controllers/decidim/direct_uploads_controller.rb b/decidim-core/app/controllers/decidim/direct_uploads_controller.rb deleted file mode 100644 index 4200d43ce3ff..000000000000 --- a/decidim-core/app/controllers/decidim/direct_uploads_controller.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true - -module Decidim - class DirectUploadsController < ActiveStorage::DirectUploadsController - include Decidim::NeedsOrganization - skip_before_action :verify_organization - - before_action :check_organization! - before_action :check_authenticated! - before_action :validate_direct_upload - - protected - - def verify_organization; end - - def validate_direct_upload - maximum_allowed_size = current_organization.settings.upload_maximum_file_size - - extension = File.extname(blob_args[:filename]).delete(".") - - Rails.logger.debug content_types.inspect - - head :unprocessable_entity unless maximum_allowed_size.try(:to_i) >= blob_args[:byte_size].try(:to_i) - head :unprocessable_entity unless content_types.any? { |pattern| pattern.match?(blob_args[:content_type]) } - head :unprocessable_entity unless content_types.any? { |pattern| pattern.match?(MiniMime.lookup_by_extension(extension).content_type) } - head :unprocessable_entity unless allowed_extensions.any? { |pattern| pattern.match?(extension) } - rescue NoMethodError - head :unprocessable_entity - end - - def check_organization! - head :unauthorized if current_organization.blank? - end - - def check_authenticated! - head :unauthorized if current_user.blank? - end - - def allowed_extensions - if URI.parse(request.referer).path.starts_with?("/admin") - current_organization.settings.upload_allowed_file_extensions_admin - else - current_organization.settings.upload_allowed_file_extensions - end - end - - def content_types - if URI.parse(request.referer).path.starts_with?("/admin") - current_organization.settings.upload_allowed_content_types_admin - else - current_organization.settings.upload_allowed_content_types - end - end - end -end diff --git a/decidim-core/lib/decidim/core.rb b/decidim-core/lib/decidim/core.rb index 823563c5f280..f315d6092d9e 100644 --- a/decidim-core/lib/decidim/core.rb +++ b/decidim-core/lib/decidim/core.rb @@ -561,6 +561,12 @@ def self.reset_all_column_information {} end + # This config parameter is used to set the default amount of time after which the unattached blobs + # are cleaned up. The default value is 60 minutes. + config_accessor :clean_up_unattached_blobs_after do + 60.minutes + end + # Public: Registers a global engine. This method is intended to be used # by component engines that also offer unscoped functionality # diff --git a/decidim-core/lib/decidim/core/engine.rb b/decidim-core/lib/decidim/core/engine.rb index 074696231b96..b8968b59901a 100644 --- a/decidim-core/lib/decidim/core/engine.rb +++ b/decidim-core/lib/decidim/core/engine.rb @@ -267,10 +267,8 @@ class Engine < ::Rails::Engine end initializer "decidim_core.direct_uploader_paths", after: "decidim_core.exceptions_app" do |_app| - Rails.application.routes.prepend do - scope ActiveStorage.routes_prefix do - post "/direct_uploads" => "decidim/direct_uploads#create", :as => :decidim_direct_uploads - end + config.to_prepare do + ActiveStorage::DirectUploadsController.include Decidim::DirectUpload end end diff --git a/decidim-core/lib/decidim/organization_settings.rb b/decidim-core/lib/decidim/organization_settings.rb index 07aeb2e05d6a..5894a291141a 100644 --- a/decidim-core/lib/decidim/organization_settings.rb +++ b/decidim-core/lib/decidim/organization_settings.rb @@ -104,8 +104,8 @@ def defaults_hash { "upload" => { "allowed_file_extensions" => { - "default" => %w(jpg jpeg png webp pdf rtf txt), - "admin" => %w(jpg jpeg png webp pdf doc docx xls xlsx ppt pptx ppx rtf txt odt ott odf otg ods ots), + "default" => %w(jpg jpeg png webp pdf rtf txt csv), + "admin" => %w(jpg jpeg png webp pdf doc docx xls xlsx ppt pptx ppx rtf txt odt ott odf otg ods ots csv json), "image" => %w(jpg jpeg png webp) }, "allowed_content_types" => { @@ -114,6 +114,7 @@ def defaults_hash application/pdf application/rtf text/plain + text/csv ), "admin" => %w( image/* @@ -125,7 +126,9 @@ def defaults_hash application/vnd.oasis.opendocument application/pdf application/rtf + application/json text/plain + text/csv ) }, "maximum_file_size" => { diff --git a/decidim-core/lib/tasks/decidim_attachments.rake b/decidim-core/lib/tasks/decidim_attachments.rake index d504b3df3f77..eaf3551c4c47 100644 --- a/decidim-core/lib/tasks/decidim_attachments.rake +++ b/decidim-core/lib/tasks/decidim_attachments.rake @@ -4,7 +4,7 @@ namespace :decidim do namespace :attachments do desc "Cleanup the orphaned blobs attachments" task cleanup: :environment do - ActiveStorage::Blob.unattached.where(ActiveStorage::Blob.arel_table[:created_at].lteq(1.hour.ago)).find_each(&:purge_later) + ActiveStorage::Blob.unattached.where(ActiveStorage::Blob.arel_table[:created_at].lteq(Decidim.clean_up_unattached_blobs_after.ago)).find_each(&:purge_later) end end end diff --git a/decidim-core/spec/controllers/decidim/direct_uploads_controller_spec.rb b/decidim-core/spec/controllers/active_storage/direct_uploads_controller_spec.rb similarity index 99% rename from decidim-core/spec/controllers/decidim/direct_uploads_controller_spec.rb rename to decidim-core/spec/controllers/active_storage/direct_uploads_controller_spec.rb index c2bae13984d2..7bf6cd118542 100644 --- a/decidim-core/spec/controllers/decidim/direct_uploads_controller_spec.rb +++ b/decidim-core/spec/controllers/active_storage/direct_uploads_controller_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -module Decidim +module ActiveStorage describe DirectUploadsController do describe "POST #create" do let(:checksum) { OpenSSL::Digest.base64digest("MD5", "Hello") } diff --git a/decidim-generators/lib/decidim/generators/app_templates/initializer.rb b/decidim-generators/lib/decidim/generators/app_templates/initializer.rb index 3058c63c4487..f596a852f135 100644 --- a/decidim-generators/lib/decidim/generators/app_templates/initializer.rb +++ b/decidim-generators/lib/decidim/generators/app_templates/initializer.rb @@ -382,6 +382,10 @@ # Read more: https://docs.decidim.org/en/develop/configure/initializer#_content_security_policy config.content_security_policies_extra = {} + # This config parameter is used to set the default amount of time after which the unattached blobs + # are cleaned up. The default value is 60 minutes. + # config.clean_up_unattached_blobs_after = 60.minutes + # Admin admin password configurations Rails.application.secrets.dig(:decidim, :admin_password, :strong).tap do |strong_pw| # When the strong password is not configured, default to true