about summary refs log tree commit diff
path: root/app/lib/admin/system_check/media_privacy_check.rb
blob: 1df05b120ea80a07b9e3db4aab093274c802db56 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# 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