# frozen_string_literal: true class Admin::SystemCheck::MediaPrivacyCheck < Admin::SystemCheck::BaseCheck include RoutingHelper def skip? !current_user.can?(:view_devops) end def pass? check_media_uploads! @failure_message.nil? end def message Admin::SystemCheck::Message.new(@failure_message, @failure_value, @failure_action, true) end private def check_media_uploads! if Rails.configuration.x.use_s3 check_media_listing_inaccessible_s3! else check_media_listing_inaccessible! end end def check_media_listing_inaccessible! full_url = full_asset_url(media_attachment.file.url(:original, false)) # Check if we can list the uploaded file. If true, that's an error directory_url = Addressable::URI.parse(full_url) directory_url.query = nil filename = directory_url.path.gsub(%r{.*/}, '') directory_url.path = directory_url.path.gsub(%r{/[^/]+\Z}, '/') Request.new(:get, directory_url, allow_local: true).perform do |res| if res.truncated_body&.include?(filename) @failure_message = use_storage? ? :upload_check_privacy_error_object_storage : :upload_check_privacy_error @failure_action = 'https://docs.joinmastodon.org/admin/optional/object-storage/#FS' end end rescue nil end def check_media_listing_inaccessible_s3! urls_to_check = [] paperclip_options = Paperclip::Attachment.default_options s3_protocol = paperclip_options[:s3_protocol] s3_host_alias = paperclip_options[:s3_host_alias] s3_host_name = paperclip_options[:s3_host_name] bucket_name = paperclip_options.dig(:s3_credentials, :bucket) urls_to_check << "#{s3_protocol}://#{s3_host_alias}/" if s3_host_alias.present? urls_to_check << "#{s3_protocol}://#{s3_host_name}/#{bucket_name}/" urls_to_check.uniq.each do |full_url| check_s3_listing!(full_url) break if @failure_message.present? end rescue nil end def check_s3_listing!(full_url) bucket_url = Addressable::URI.parse(full_url) bucket_url.path = bucket_url.path.delete_suffix(media_attachment.file.path(:original)) bucket_url.query = "max-keys=1&x-random=#{SecureRandom.hex(10)}" Request.new(:get, bucket_url, allow_local: true).perform do |res| if res.truncated_body&.include?('ListBucketResult') @failure_message = :upload_check_privacy_error_object_storage @failure_action = 'https://docs.joinmastodon.org/admin/optional/object-storage/#S3' end end end def media_attachment @media_attachment ||= begin attachment = Account.representative.media_attachments.first if attachment.present? attachment.touch # rubocop:disable Rails/SkipsModelValidations attachment else create_test_attachment! end end end def create_test_attachment! Tempfile.create(%w(test-upload .jpg), binmode: true) do |tmp_file| tmp_file.write( Base64.decode64( '/9j/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAYAAAA' \ 'AAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA' \ 'QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE' \ 'BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/AABEIAAEAAgMBEQACEQEDEQH/x' \ 'ABKAAEAAAAAAAAAAAAAAAAAAAALEAEAAAAAAAAAAAAAAAAAAAAAAQEAAAAAAAAAAAAAAAA' \ 'AAAAAEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwA/8H//2Q==' ) ) tmp_file.flush Account.representative.media_attachments.create!(file: tmp_file) end end end