diff options
Diffstat (limited to 'app/lib/admin')
-rw-r--r-- | app/lib/admin/metrics/dimension/software_versions_dimension.rb | 12 | ||||
-rw-r--r-- | app/lib/admin/metrics/dimension/space_usage_dimension.rb | 12 | ||||
-rw-r--r-- | app/lib/admin/metrics/retention.rb | 56 | ||||
-rw-r--r-- | app/lib/admin/system_check.rb | 1 | ||||
-rw-r--r-- | app/lib/admin/system_check/elasticsearch_check.rb | 13 | ||||
-rw-r--r-- | app/lib/admin/system_check/media_privacy_check.rb | 105 | ||||
-rw-r--r-- | app/lib/admin/system_check/message.rb | 11 |
7 files changed, 164 insertions, 46 deletions
diff --git a/app/lib/admin/metrics/dimension/software_versions_dimension.rb b/app/lib/admin/metrics/dimension/software_versions_dimension.rb index 816615f99..9ab3776c9 100644 --- a/app/lib/admin/metrics/dimension/software_versions_dimension.rb +++ b/app/lib/admin/metrics/dimension/software_versions_dimension.rb @@ -58,12 +58,10 @@ class Admin::Metrics::Dimension::SoftwareVersionsDimension < Admin::Metrics::Dim end def redis_info - @redis_info ||= begin - if redis.is_a?(Redis::Namespace) - redis.redis.info - else - redis.info - end - end + @redis_info ||= if redis.is_a?(Redis::Namespace) + redis.redis.info + else + redis.info + end end end diff --git a/app/lib/admin/metrics/dimension/space_usage_dimension.rb b/app/lib/admin/metrics/dimension/space_usage_dimension.rb index 5867c5bab..cc8560890 100644 --- a/app/lib/admin/metrics/dimension/space_usage_dimension.rb +++ b/app/lib/admin/metrics/dimension/space_usage_dimension.rb @@ -59,12 +59,10 @@ class Admin::Metrics::Dimension::SpaceUsageDimension < Admin::Metrics::Dimension end def redis_info - @redis_info ||= begin - if redis.is_a?(Redis::Namespace) - redis.redis.info - else - redis.info - end - end + @redis_info ||= if redis.is_a?(Redis::Namespace) + redis.redis.info + else + redis.info + end end end diff --git a/app/lib/admin/metrics/retention.rb b/app/lib/admin/metrics/retention.rb index f6135ac1e..9bd47c58e 100644 --- a/app/lib/admin/metrics/retention.rb +++ b/app/lib/admin/metrics/retention.rb @@ -42,25 +42,54 @@ class Admin::Metrics::Retention end def perform_query - sql = <<-SQL.squish + report_rows.each_with_object([]) do |row, arr| + current_cohort = arr.last + + if current_cohort.nil? || current_cohort.period != row['cohort_period'] + current_cohort = Cohort.new(period: row['cohort_period'], frequency: @frequency, data: []) + arr << current_cohort + end + + value, rate = row['retention_value_and_rate'].delete('{}').split(',') + + current_cohort.data << CohortData.new( + date: row['retention_period'], + rate: rate.to_f, + value: value.to_s + ) + end + end + + def report_rows + ActiveRecord::Base.connection.select_all(sanitized_sql_string) + end + + def sanitized_sql_string + ActiveRecord::Base.sanitize_sql_array( + [sql_query_string, { start_at: @start_at, end_at: @end_at, frequency: @frequency }] + ) + end + + def sql_query_string + <<~SQL.squish SELECT axis.*, ( WITH new_users AS ( SELECT users.id FROM users - WHERE date_trunc($3, users.created_at)::date = axis.cohort_period + WHERE date_trunc(:frequency, users.created_at)::date = axis.cohort_period ), retained_users AS ( SELECT users.id FROM users INNER JOIN new_users on new_users.id = users.id - WHERE date_trunc($3, users.current_sign_in_at) >= axis.retention_period + WHERE date_trunc(:frequency, users.current_sign_in_at) >= axis.retention_period ) SELECT ARRAY[count(*), (count(*))::float / (SELECT GREATEST(count(*), 1) FROM new_users)] AS retention_value_and_rate FROM retained_users ) FROM ( WITH cohort_periods AS ( - SELECT generate_series(date_trunc($3, $1::timestamp)::date, date_trunc($3, $2::timestamp)::date, ('1 ' || $3)::interval) AS cohort_period + SELECT generate_series(date_trunc(:frequency, :start_at::timestamp)::date, date_trunc(:frequency, :end_at::timestamp)::date, ('1 ' || :frequency)::interval) AS cohort_period ), retention_periods AS ( SELECT cohort_period AS retention_period FROM cohort_periods @@ -70,24 +99,5 @@ class Admin::Metrics::Retention WHERE retention_period >= cohort_period ) as axis SQL - - rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, @frequency]]) - - rows.each_with_object([]) do |row, arr| - current_cohort = arr.last - - if current_cohort.nil? || current_cohort.period != row['cohort_period'] - current_cohort = Cohort.new(period: row['cohort_period'], frequency: @frequency, data: []) - arr << current_cohort - end - - value, rate = row['retention_value_and_rate'].delete('{}').split(',') - - current_cohort.data << CohortData.new( - date: row['retention_period'], - rate: rate.to_f, - value: value.to_s - ) - end end end diff --git a/app/lib/admin/system_check.rb b/app/lib/admin/system_check.rb index f512635ab..89dfcef9f 100644 --- a/app/lib/admin/system_check.rb +++ b/app/lib/admin/system_check.rb @@ -2,6 +2,7 @@ class Admin::SystemCheck ACTIVE_CHECKS = [ + Admin::SystemCheck::MediaPrivacyCheck, Admin::SystemCheck::DatabaseSchemaCheck, Admin::SystemCheck::SidekiqProcessCheck, Admin::SystemCheck::RulesCheck, diff --git a/app/lib/admin/system_check/elasticsearch_check.rb b/app/lib/admin/system_check/elasticsearch_check.rb index 7f922978f..0b55be350 100644 --- a/app/lib/admin/system_check/elasticsearch_check.rb +++ b/app/lib/admin/system_check/elasticsearch_check.rb @@ -30,19 +30,24 @@ class Admin::SystemCheck::ElasticsearchCheck < Admin::SystemCheck::BaseCheck def running_version @running_version ||= begin - Chewy.client.info['version']['minimum_wire_compatibility_version'] || - Chewy.client.info['version']['number'] - rescue Faraday::ConnectionFailed + Chewy.client.info['version']['number'] + rescue Faraday::ConnectionFailed, Elasticsearch::Transport::Transport::Error nil end end + def compatible_wire_version + Chewy.client.info['version']['minimum_wire_compatibility_version'] + end + def required_version '7.x' end def compatible_version? return false if running_version.nil? - Gem::Version.new(running_version) >= Gem::Version.new(required_version) + + Gem::Version.new(running_version) >= Gem::Version.new(required_version) || + Gem::Version.new(compatible_wire_version) >= Gem::Version.new(required_version) end end diff --git a/app/lib/admin/system_check/media_privacy_check.rb b/app/lib/admin/system_check/media_privacy_check.rb new file mode 100644 index 000000000..1df05b120 --- /dev/null +++ b/app/lib/admin/system_check/media_privacy_check.rb @@ -0,0 +1,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 diff --git a/app/lib/admin/system_check/message.rb b/app/lib/admin/system_check/message.rb index bfcad3bf3..ad8d4b607 100644 --- a/app/lib/admin/system_check/message.rb +++ b/app/lib/admin/system_check/message.rb @@ -1,11 +1,12 @@ # frozen_string_literal: true class Admin::SystemCheck::Message - attr_reader :key, :value, :action + attr_reader :key, :value, :action, :critical - def initialize(key, value = nil, action = nil) - @key = key - @value = value - @action = action + def initialize(key, value = nil, action = nil, critical = false) + @key = key + @value = value + @action = action + @critical = critical end end |