media_privacy_check.rb 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. # frozen_string_literal: true
  2. class Admin::SystemCheck::MediaPrivacyCheck < Admin::SystemCheck::BaseCheck
  3. include RoutingHelper
  4. def skip?
  5. !current_user.can?(:view_devops)
  6. end
  7. def pass?
  8. check_media_uploads!
  9. @failure_message.nil?
  10. end
  11. def message
  12. Admin::SystemCheck::Message.new(@failure_message, @failure_value, @failure_action, true)
  13. end
  14. private
  15. def check_media_uploads!
  16. if Rails.configuration.x.use_s3
  17. check_media_listing_inaccessible_s3!
  18. else
  19. check_media_listing_inaccessible!
  20. end
  21. end
  22. def check_media_listing_inaccessible!
  23. full_url = full_asset_url(media_attachment.file.url(:original, false))
  24. # Check if we can list the uploaded file. If true, that's an error
  25. directory_url = Addressable::URI.parse(full_url)
  26. directory_url.query = nil
  27. filename = directory_url.path.gsub(%r{.*/}, '')
  28. directory_url.path = directory_url.path.gsub(%r{/[^/]+\Z}, '/')
  29. Request.new(:get, directory_url, allow_local: true).perform do |res|
  30. if res.truncated_body&.include?(filename)
  31. @failure_message = use_storage? ? :upload_check_privacy_error_object_storage : :upload_check_privacy_error
  32. @failure_action = 'https://docs.joinmastodon.org/admin/optional/object-storage/#FS'
  33. end
  34. end
  35. rescue
  36. nil
  37. end
  38. def check_media_listing_inaccessible_s3!
  39. urls_to_check = []
  40. paperclip_options = Paperclip::Attachment.default_options
  41. s3_protocol = paperclip_options[:s3_protocol]
  42. s3_host_alias = paperclip_options[:s3_host_alias]
  43. s3_host_name = paperclip_options[:s3_host_name]
  44. bucket_name = paperclip_options.dig(:s3_credentials, :bucket)
  45. urls_to_check << "#{s3_protocol}://#{s3_host_alias}/" if s3_host_alias.present?
  46. urls_to_check << "#{s3_protocol}://#{s3_host_name}/#{bucket_name}/"
  47. urls_to_check.uniq.each do |full_url|
  48. check_s3_listing!(full_url)
  49. break if @failure_message.present?
  50. end
  51. rescue
  52. nil
  53. end
  54. def check_s3_listing!(full_url)
  55. bucket_url = Addressable::URI.parse(full_url)
  56. bucket_url.path = bucket_url.path.delete_suffix(media_attachment.file.path(:original))
  57. bucket_url.query = "max-keys=1&x-random=#{SecureRandom.hex(10)}"
  58. Request.new(:get, bucket_url, allow_local: true).perform do |res|
  59. if res.truncated_body&.include?('ListBucketResult')
  60. @failure_message = :upload_check_privacy_error_object_storage
  61. @failure_action = 'https://docs.joinmastodon.org/admin/optional/object-storage/#S3'
  62. end
  63. end
  64. end
  65. def media_attachment
  66. @media_attachment ||= begin
  67. attachment = Account.representative.media_attachments.first
  68. if attachment.present?
  69. attachment.touch # rubocop:disable Rails/SkipsModelValidations
  70. attachment
  71. else
  72. create_test_attachment!
  73. end
  74. end
  75. end
  76. def create_test_attachment!
  77. Tempfile.create(%w(test-upload .jpg), binmode: true) do |tmp_file|
  78. tmp_file.write(
  79. Base64.decode64(
  80. '/9j/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAYAAAA' \
  81. 'AAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA' \
  82. 'QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE' \
  83. 'BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/AABEIAAEAAgMBEQACEQEDEQH/x' \
  84. 'ABKAAEAAAAAAAAAAAAAAAAAAAALEAEAAAAAAAAAAAAAAAAAAAAAAQEAAAAAAAAAAAAAAAA' \
  85. 'AAAAAEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwA/8H//2Q=='
  86. )
  87. )
  88. tmp_file.flush
  89. Account.representative.media_attachments.create!(file: tmp_file)
  90. end
  91. end
  92. end