From f7f23b4a19a84371f44ec5297125e96ba81681a1 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 19 Jun 2019 23:42:38 +0200 Subject: Add audio uploads (#11123) * Add audio uploads Fix #4827 Accept uploads of OGG, WAV, FLAC, OPUS and MP3 files, and converts them to OGG. Media attachments get a new `audio` type. In the UI, audio uploads are displayed identically to video uploads. * Improve code style --- app/models/media_attachment.rb | 64 ++++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 21 deletions(-) (limited to 'app/models') diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index ab794faa0..5ca218411 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -24,14 +24,16 @@ class MediaAttachment < ApplicationRecord self.inheritance_column = nil - enum type: [:image, :gifv, :video, :unknown] + enum type: [:image, :gifv, :video, :unknown, :audio] IMAGE_FILE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp'].freeze VIDEO_FILE_EXTENSIONS = ['.webm', '.mp4', '.m4v', '.mov'].freeze + AUDIO_FILE_EXTENSIONS = ['.ogg', '.oga', '.mp3', '.wav', '.flac', '.opus'].freeze IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].freeze VIDEO_MIME_TYPES = ['video/webm', 'video/mp4', 'video/quicktime'].freeze VIDEO_CONVERTIBLE_MIME_TYPES = ['video/webm', 'video/quicktime'].freeze + AUDIO_MIME_TYPES = ['audio/wave', 'audio/wav', 'audio/x-wav', 'audio/x-pn-wave', 'audio/ogg', 'audio/mpeg', 'audio/webm', 'audio/flac'].freeze BLURHASH_OPTIONS = { x_comp: 4, @@ -65,6 +67,13 @@ class MediaAttachment < ApplicationRecord }, }.freeze + AUDIO_STYLES = { + original: { + format: 'ogg', + convert_options: {}, + }, + }.freeze + VIDEO_FORMAT = { format: 'mp4', convert_options: { @@ -83,6 +92,11 @@ class MediaAttachment < ApplicationRecord }, }.freeze + VIDEO_CONVERTED_STYLES = { + small: VIDEO_STYLES[:small], + original: VIDEO_FORMAT, + }.freeze + IMAGE_LIMIT = 8.megabytes VIDEO_LIMIT = 40.megabytes @@ -95,9 +109,9 @@ class MediaAttachment < ApplicationRecord processors: ->(f) { file_processors f }, convert_options: { all: '-quality 90 -strip' } - validates_attachment_content_type :file, content_type: IMAGE_MIME_TYPES + VIDEO_MIME_TYPES - validates_attachment_size :file, less_than: IMAGE_LIMIT, unless: :video_or_gifv? - validates_attachment_size :file, less_than: VIDEO_LIMIT, if: :video_or_gifv? + validates_attachment_content_type :file, content_type: IMAGE_MIME_TYPES + VIDEO_MIME_TYPES + AUDIO_MIME_TYPES + validates_attachment_size :file, less_than: IMAGE_LIMIT, unless: :larger_media_format? + validates_attachment_size :file, less_than: VIDEO_LIMIT, if: :larger_media_format? remotable_attachment :file, VIDEO_LIMIT include Attachmentable @@ -120,8 +134,12 @@ class MediaAttachment < ApplicationRecord file.blank? && remote_url.present? end - def video_or_gifv? - video? || gifv? + def larger_media_format? + video? || gifv? || audio? + end + + def audio_or_video? + audio? || video? end def to_param @@ -156,28 +174,24 @@ class MediaAttachment < ApplicationRecord private def file_styles(f) - if f.instance.file_content_type == 'image/gif' - { - small: IMAGE_STYLES[:small], - original: VIDEO_FORMAT, - } - elsif IMAGE_MIME_TYPES.include? f.instance.file_content_type + if f.instance.file_content_type == 'image/gif' || VIDEO_CONVERTIBLE_MIME_TYPES.include?(f.instance.file_content_type) + VIDEO_CONVERTED_STYLES + elsif IMAGE_MIME_TYPES.include?(f.instance.file_content_type) IMAGE_STYLES - elsif VIDEO_CONVERTIBLE_MIME_TYPES.include?(f.instance.file_content_type) - { - small: VIDEO_STYLES[:small], - original: VIDEO_FORMAT, - } - else + elsif VIDEO_MIME_TYPES.include?(f.instance.file_content_type) VIDEO_STYLES + else + AUDIO_STYLES end end def file_processors(f) if f.file_content_type == 'image/gif' [:gif_transcoder, :blurhash_transcoder] - elsif VIDEO_MIME_TYPES.include? f.file_content_type + elsif VIDEO_MIME_TYPES.include?(f.file_content_type) [:video_transcoder, :blurhash_transcoder] + elsif AUDIO_MIME_TYPES.include?(f.file_content_type) + [:transcoder] else [:lazy_thumbnail, :blurhash_transcoder] end @@ -202,7 +216,15 @@ class MediaAttachment < ApplicationRecord end def set_type_and_extension - self.type = VIDEO_MIME_TYPES.include?(file_content_type) ? :video : :image + self.type = begin + if VIDEO_MIME_TYPES.include?(file_content_type) + :video + elsif AUDIO_MIME_TYPES.include?(file_content_type) + :audio + else + :image + end + end end def set_meta @@ -245,7 +267,7 @@ class MediaAttachment < ApplicationRecord frame_rate: movie.frame_rate, duration: movie.duration, bitrate: movie.bitrate, - } + }.compact end def reset_parent_cache -- cgit From 7696f77245c2302787d239da50248385b3292a5e Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 20 Jun 2019 02:52:34 +0200 Subject: Add moderation API (#9387) Fix #8580 Fix #7143 --- app/controllers/admin/accounts_controller.rb | 1 + .../api/v1/admin/account_actions_controller.rb | 32 +++++ .../api/v1/admin/accounts_controller.rb | 128 ++++++++++++++++++ app/controllers/api/v1/admin/reports_controller.rb | 108 +++++++++++++++ app/models/account.rb | 2 + app/models/account_filter.rb | 2 + app/models/concerns/user_roles.rb | 14 ++ app/models/report.rb | 3 + app/models/report_filter.rb | 2 + app/models/user.rb | 1 + app/serializers/rest/admin/account_serializer.rb | 77 +++++++++++ app/serializers/rest/admin/report_serializer.rb | 16 +++ config/initializers/doorkeeper.rb | 8 +- config/locales/doorkeeper.en.yml | 6 + config/routes.rb | 23 ++++ .../v1/admin/account_actions_controller_spec.rb | 57 ++++++++ .../api/v1/admin/accounts_controller_spec.rb | 147 +++++++++++++++++++++ .../api/v1/admin/reports_controller_spec.rb | 109 +++++++++++++++ 18 files changed, 735 insertions(+), 1 deletion(-) create mode 100644 app/controllers/api/v1/admin/account_actions_controller.rb create mode 100644 app/controllers/api/v1/admin/accounts_controller.rb create mode 100644 app/controllers/api/v1/admin/reports_controller.rb create mode 100644 app/serializers/rest/admin/account_serializer.rb create mode 100644 app/serializers/rest/admin/report_serializer.rb create mode 100644 spec/controllers/api/v1/admin/account_actions_controller_spec.rb create mode 100644 spec/controllers/api/v1/admin/accounts_controller_spec.rb create mode 100644 spec/controllers/api/v1/admin/reports_controller_spec.rb (limited to 'app/models') diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb index b0d45ce47..0c7760d77 100644 --- a/app/controllers/admin/accounts_controller.rb +++ b/app/controllers/admin/accounts_controller.rb @@ -127,6 +127,7 @@ module Admin :by_domain, :active, :pending, + :disabled, :silenced, :suspended, :username, diff --git a/app/controllers/api/v1/admin/account_actions_controller.rb b/app/controllers/api/v1/admin/account_actions_controller.rb new file mode 100644 index 000000000..29c9b7107 --- /dev/null +++ b/app/controllers/api/v1/admin/account_actions_controller.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +class Api::V1::Admin::AccountActionsController < Api::BaseController + before_action -> { doorkeeper_authorize! :'admin:write', :'admin:write:accounts' } + before_action :require_staff! + before_action :set_account + + def create + account_action = Admin::AccountAction.new(resource_params) + account_action.target_account = @account + account_action.current_account = current_account + account_action.save! + + render_empty + end + + private + + def set_account + @account = Account.find(params[:account_id]) + end + + def resource_params + params.permit( + :type, + :report_id, + :warning_preset_id, + :text, + :send_email_notification + ) + end +end diff --git a/app/controllers/api/v1/admin/accounts_controller.rb b/app/controllers/api/v1/admin/accounts_controller.rb new file mode 100644 index 000000000..c306180ca --- /dev/null +++ b/app/controllers/api/v1/admin/accounts_controller.rb @@ -0,0 +1,128 @@ +# frozen_string_literal: true + +class Api::V1::Admin::AccountsController < Api::BaseController + include Authorization + include AccountableConcern + + LIMIT = 100 + + before_action -> { doorkeeper_authorize! :'admin:read', :'admin:read:accounts' }, only: [:index, :show] + before_action -> { doorkeeper_authorize! :'admin:write', :'admin:write:accounts' }, except: [:index, :show] + before_action :require_staff! + before_action :set_accounts, only: :index + before_action :set_account, except: :index + before_action :require_local_account!, only: [:enable, :approve, :reject] + + after_action :insert_pagination_headers, only: :index + + FILTER_PARAMS = %i( + local + remote + by_domain + active + pending + disabled + silenced + suspended + username + display_name + email + ip + staff + ).freeze + + PAGINATION_PARAMS = (%i(limit) + FILTER_PARAMS).freeze + + def index + authorize :account, :index? + render json: @accounts, each_serializer: REST::Admin::AccountSerializer + end + + def show + authorize @account, :show? + render json: @account, serializer: REST::Admin::AccountSerializer + end + + def enable + authorize @account.user, :enable? + @account.user.enable! + log_action :enable, @account.user + render json: @account, serializer: REST::Admin::AccountSerializer + end + + def approve + authorize @account.user, :approve? + @account.user.approve! + render json: @account, serializer: REST::Admin::AccountSerializer + end + + def reject + authorize @account.user, :reject? + SuspendAccountService.new.call(@account, including_user: true, destroy: true, skip_distribution: true) + render json: @account, serializer: REST::Admin::AccountSerializer + end + + def unsilence + authorize @account, :unsilence? + @account.unsilence! + log_action :unsilence, @account + render json: @account, serializer: REST::Admin::AccountSerializer + end + + def unsuspend + authorize @account, :unsuspend? + @account.unsuspend! + log_action :unsuspend, @account + render json: @account, serializer: REST::Admin::AccountSerializer + end + + private + + def set_accounts + @accounts = filtered_accounts.order(id: :desc).includes(user: [:invite_request, :invite]).paginate_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id)) + end + + def set_account + @account = Account.find(params[:id]) + end + + def filtered_accounts + AccountFilter.new(filter_params).results + end + + def filter_params + params.permit(*FILTER_PARAMS) + end + + def insert_pagination_headers + set_pagination_headers(next_path, prev_path) + end + + def next_path + api_v1_admin_accounts_url(pagination_params(max_id: pagination_max_id)) if records_continue? + end + + def prev_path + api_v1_admin_accounts_url(pagination_params(min_id: pagination_since_id)) unless @accounts.empty? + end + + def pagination_max_id + @accounts.last.id + end + + def pagination_since_id + @accounts.first.id + end + + def records_continue? + @accounts.size == limit_param(LIMIT) + end + + def pagination_params(core_params) + params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params) + end + + def require_local_account! + forbidden unless @account.local? && @account.user.present? + end +end diff --git a/app/controllers/api/v1/admin/reports_controller.rb b/app/controllers/api/v1/admin/reports_controller.rb new file mode 100644 index 000000000..1d48d3160 --- /dev/null +++ b/app/controllers/api/v1/admin/reports_controller.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +class Api::V1::Admin::ReportsController < Api::BaseController + include Authorization + include AccountableConcern + + LIMIT = 100 + + before_action -> { doorkeeper_authorize! :'admin:read', :'admin:read:reports' }, only: [:index, :show] + before_action -> { doorkeeper_authorize! :'admin:write', :'admin:write:reports' }, except: [:index, :show] + before_action :require_staff! + before_action :set_reports, only: :index + before_action :set_report, except: :index + + after_action :insert_pagination_headers, only: :index + + FILTER_PARAMS = %i( + resolved + account_id + target_account_id + ).freeze + + PAGINATION_PARAMS = (%i(limit) + FILTER_PARAMS).freeze + + def index + authorize :report, :index? + render json: @reports, each_serializer: REST::Admin::ReportSerializer + end + + def show + authorize @report, :show? + render json: @report, serializer: REST::Admin::ReportSerializer + end + + def assign_to_self + authorize @report, :update? + @report.update!(assigned_account_id: current_account.id) + log_action :assigned_to_self, @report + render json: @report, serializer: REST::Admin::ReportSerializer + end + + def unassign + authorize @report, :update? + @report.update!(assigned_account_id: nil) + log_action :unassigned, @report + render json: @report, serializer: REST::Admin::ReportSerializer + end + + def reopen + authorize @report, :update? + @report.unresolve! + log_action :reopen, @report + render json: @report, serializer: REST::Admin::ReportSerializer + end + + def resolve + authorize @report, :update? + @report.resolve!(current_account) + log_action :resolve, @report + render json: @report, serializer: REST::Admin::ReportSerializer + end + + private + + def set_reports + @reports = filtered_reports.order(id: :desc).with_accounts.paginate_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id)) + end + + def set_report + @report = Report.find(params[:id]) + end + + def filtered_reports + ReportFilter.new(filter_params).results + end + + def filter_params + params.permit(*FILTER_PARAMS) + end + + def insert_pagination_headers + set_pagination_headers(next_path, prev_path) + end + + def next_path + api_v1_admin_reports_url(pagination_params(max_id: pagination_max_id)) if records_continue? + end + + def prev_path + api_v1_admin_reports_url(pagination_params(min_id: pagination_since_id)) unless @reports.empty? + end + + def pagination_max_id + @reports.last.id + end + + def pagination_since_id + @reports.first.id + end + + def records_continue? + @reports.size == limit_param(LIMIT) + end + + def pagination_params(core_params) + params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params) + end +end diff --git a/app/models/account.rb b/app/models/account.rb index c977f887c..9276aa927 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -106,6 +106,8 @@ class Account < ApplicationRecord :confirmed?, :approved?, :pending?, + :disabled?, + :role, :admin?, :moderator?, :staff?, diff --git a/app/models/account_filter.rb b/app/models/account_filter.rb index d2503100c..c3b1fe08d 100644 --- a/app/models/account_filter.rb +++ b/app/models/account_filter.rb @@ -37,6 +37,8 @@ class AccountFilter Account.without_suspended when 'pending' accounts_with_users.merge User.pending + when 'disabled' + accounts_with_users.merge User.disabled when 'silenced' Account.silenced when 'suspended' diff --git a/app/models/concerns/user_roles.rb b/app/models/concerns/user_roles.rb index 58dffdc46..a42b4a172 100644 --- a/app/models/concerns/user_roles.rb +++ b/app/models/concerns/user_roles.rb @@ -13,6 +13,20 @@ module UserRoles admin? || moderator? end + def role=(value) + case value + when 'admin' + self.admin = true + self.moderator = false + when 'moderator' + self.admin = false + self.moderator = true + else + self.admin = false + self.moderator = false + end + end + def role if admin? 'admin' diff --git a/app/models/report.rb b/app/models/report.rb index 86c303798..5192ceef7 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -17,6 +17,8 @@ # class Report < ApplicationRecord + include Paginable + belongs_to :account belongs_to :target_account, class_name: 'Account' belongs_to :action_taken_by_account, class_name: 'Account', optional: true @@ -26,6 +28,7 @@ class Report < ApplicationRecord scope :unresolved, -> { where(action_taken: false) } scope :resolved, -> { where(action_taken: true) } + scope :with_accounts, -> { includes([:account, :target_account, :action_taken_by_account, :assigned_account].each_with_object({}) { |k, h| h[k] = { user: [:invite_request, :invite] } }) } validates :comment, length: { maximum: 1000 } diff --git a/app/models/report_filter.rb b/app/models/report_filter.rb index 56ab28df7..a392d60c3 100644 --- a/app/models/report_filter.rb +++ b/app/models/report_filter.rb @@ -9,9 +9,11 @@ class ReportFilter def results scope = Report.unresolved + params.each do |key, value| scope = scope.merge scope_for(key, value) end + scope end diff --git a/app/models/user.rb b/app/models/user.rb index 4abf124fc..50873dd01 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -87,6 +87,7 @@ class User < ApplicationRecord scope :approved, -> { where(approved: true) } scope :confirmed, -> { where.not(confirmed_at: nil) } scope :enabled, -> { where(disabled: false) } + scope :disabled, -> { where(disabled: true) } scope :inactive, -> { where(arel_table[:current_sign_in_at].lt(ACTIVE_DURATION.ago)) } scope :active, -> { confirmed.where(arel_table[:current_sign_in_at].gteq(ACTIVE_DURATION.ago)).joins(:account).where(accounts: { suspended_at: nil }) } scope :matches_email, ->(value) { where(arel_table[:email].matches("#{value}%")) } diff --git a/app/serializers/rest/admin/account_serializer.rb b/app/serializers/rest/admin/account_serializer.rb new file mode 100644 index 000000000..f579d3302 --- /dev/null +++ b/app/serializers/rest/admin/account_serializer.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +class REST::Admin::AccountSerializer < ActiveModel::Serializer + attributes :id, :username, :domain, :created_at, + :email, :ip, :role, :confirmed, :suspended, + :silenced, :disabled, :approved, :locale, + :invite_request + + attribute :created_by_application_id, if: :created_by_application? + attribute :invited_by_account_id, if: :invited? + + has_one :account, serializer: REST::AccountSerializer + + def id + object.id.to_s + end + + def email + object.user_email + end + + def ip + object.user_current_sign_in_ip.to_s.presence + end + + def role + object.user_role + end + + def suspended + object.suspended? + end + + def silenced + object.silenced? + end + + def confirmed + object.user_confirmed? + end + + def disabled + object.user_disabled? + end + + def approved + object.user_approved? + end + + def account + object + end + + def locale + object.user_locale + end + + def created_by_application_id + object.user&.created_by_application_id&.to_s&.presence + end + + def invite_request + object.user&.invite_request&.text + end + + def invited_by_account_id + object.user&.invite&.user&.account_id&.to_s&.presence + end + + def invited? + object.user&.invited? + end + + def created_by_application? + object.user&.created_by_application_id&.present? + end +end diff --git a/app/serializers/rest/admin/report_serializer.rb b/app/serializers/rest/admin/report_serializer.rb new file mode 100644 index 000000000..7a77132c0 --- /dev/null +++ b/app/serializers/rest/admin/report_serializer.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class REST::Admin::ReportSerializer < ActiveModel::Serializer + attributes :id, :action_taken, :comment, :created_at, :updated_at + + has_one :account, serializer: REST::Admin::AccountSerializer + has_one :target_account, serializer: REST::Admin::AccountSerializer + has_one :assigned_account, serializer: REST::Admin::AccountSerializer + has_one :action_taken_by_account, serializer: REST::Admin::AccountSerializer + + has_many :statuses, serializer: REST::StatusSerializer + + def id + object.id.to_s + end +end diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb index 367eead6a..914b3c001 100644 --- a/config/initializers/doorkeeper.rb +++ b/config/initializers/doorkeeper.rb @@ -80,7 +80,13 @@ Doorkeeper.configure do :'read:search', :'read:statuses', :follow, - :push + :push, + :'admin:read', + :'admin:read:accounts', + :'admin:read:reports', + :'admin:write', + :'admin:write:accounts', + :'admin:write:reports' # Change the way client credentials are retrieved from the request object. # By default it retrieves first from the `HTTP_AUTHORIZATION` header, then diff --git a/config/locales/doorkeeper.en.yml b/config/locales/doorkeeper.en.yml index f1fe03716..d9b7c2c8e 100644 --- a/config/locales/doorkeeper.en.yml +++ b/config/locales/doorkeeper.en.yml @@ -114,6 +114,12 @@ en: application: title: OAuth authorization required scopes: + admin:read: read all data on the server + admin:read:accounts: read sensitive information of all accounts + admin:read:reports: read sensitive information of all reports and reported accounts + admin:write: modify all data on the server + admin:write:accounts: perform moderation actions on accounts + admin:write:reports: perform moderation actions on reports follow: modify account relationships push: receive your push notifications read: read all your account's data diff --git a/config/routes.rb b/config/routes.rb index 145079c69..764db8db2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -398,6 +398,29 @@ Rails.application.routes.draw do namespace :push do resource :subscription, only: [:create, :show, :update, :destroy] end + + namespace :admin do + resources :accounts, only: [:index, :show] do + member do + post :enable + post :unsilence + post :unsuspend + post :approve + post :reject + end + + resource :action, only: [:create], controller: 'account_actions' + end + + resources :reports, only: [:index, :show] do + member do + post :assign_to_self + post :unassign + post :reopen + post :resolve + end + end + end end namespace :v2 do diff --git a/spec/controllers/api/v1/admin/account_actions_controller_spec.rb b/spec/controllers/api/v1/admin/account_actions_controller_spec.rb new file mode 100644 index 000000000..a5a8f4bb0 --- /dev/null +++ b/spec/controllers/api/v1/admin/account_actions_controller_spec.rb @@ -0,0 +1,57 @@ +require 'rails_helper' + +RSpec.describe Api::V1::Admin::AccountActionsController, type: :controller do + render_views + + let(:role) { 'moderator' } + let(:user) { Fabricate(:user, role: role, account: Fabricate(:account, username: 'alice')) } + let(:scopes) { 'admin:read admin:write' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:account) { Fabricate(:user).account } + + before do + allow(controller).to receive(:doorkeeper_token) { token } + end + + shared_examples 'forbidden for wrong scope' do |wrong_scope| + let(:scopes) { wrong_scope } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + + shared_examples 'forbidden for wrong role' do |wrong_role| + let(:role) { wrong_role } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + + describe 'POST #create' do + before do + post :create, params: { account_id: account.id, type: 'disable' } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', 'user' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'performs action against account' do + expect(account.reload.user_disabled?).to be true + end + + it 'logs action' do + log_item = Admin::ActionLog.last + + expect(log_item).to_not be_nil + expect(log_item.action).to eq :disable + expect(log_item.account_id).to eq user.account_id + expect(log_item.target_id).to eq account.user.id + end + end +end diff --git a/spec/controllers/api/v1/admin/accounts_controller_spec.rb b/spec/controllers/api/v1/admin/accounts_controller_spec.rb new file mode 100644 index 000000000..f3f9946ba --- /dev/null +++ b/spec/controllers/api/v1/admin/accounts_controller_spec.rb @@ -0,0 +1,147 @@ +require 'rails_helper' + +RSpec.describe Api::V1::Admin::AccountsController, type: :controller do + render_views + + let(:role) { 'moderator' } + let(:user) { Fabricate(:user, role: role, account: Fabricate(:account, username: 'alice')) } + let(:scopes) { 'admin:read admin:write' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:account) { Fabricate(:user).account } + + before do + allow(controller).to receive(:doorkeeper_token) { token } + end + + shared_examples 'forbidden for wrong scope' do |wrong_scope| + let(:scopes) { wrong_scope } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + + shared_examples 'forbidden for wrong role' do |wrong_role| + let(:role) { wrong_role } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + + describe 'GET #index' do + before do + get :index + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', 'user' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end + + describe 'GET #show' do + before do + get :show, params: { id: account.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', 'user' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end + + describe 'POST #approve' do + before do + account.user.update(approved: false) + post :approve, params: { id: account.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', 'user' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'approves user' do + expect(account.reload.user_approved?).to be true + end + end + + describe 'POST #reject' do + before do + account.user.update(approved: false) + post :reject, params: { id: account.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', 'user' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'removes user' do + expect(User.where(id: account.user.id).count).to eq 0 + end + end + + describe 'POST #enable' do + before do + account.user.update(disabled: true) + post :enable, params: { id: account.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', 'user' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'enables user' do + expect(account.reload.user_disabled?).to be false + end + end + + describe 'POST #unsuspend' do + before do + account.touch(:suspended_at) + post :unsuspend, params: { id: account.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', 'user' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'unsuspends account' do + expect(account.reload.suspended?).to be false + end + end + + describe 'POST #unsilence' do + before do + account.touch(:silenced_at) + post :unsilence, params: { id: account.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', 'user' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'unsilences account' do + expect(account.reload.silenced?).to be false + end + end +end diff --git a/spec/controllers/api/v1/admin/reports_controller_spec.rb b/spec/controllers/api/v1/admin/reports_controller_spec.rb new file mode 100644 index 000000000..4ed3c5dc4 --- /dev/null +++ b/spec/controllers/api/v1/admin/reports_controller_spec.rb @@ -0,0 +1,109 @@ +require 'rails_helper' + +RSpec.describe Api::V1::Admin::ReportsController, type: :controller do + render_views + + let(:role) { 'moderator' } + let(:user) { Fabricate(:user, role: role, account: Fabricate(:account, username: 'alice')) } + let(:scopes) { 'admin:read admin:write' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:report) { Fabricate(:report) } + + before do + allow(controller).to receive(:doorkeeper_token) { token } + end + + shared_examples 'forbidden for wrong scope' do |wrong_scope| + let(:scopes) { wrong_scope } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + + shared_examples 'forbidden for wrong role' do |wrong_role| + let(:role) { wrong_role } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + + describe 'GET #index' do + before do + get :index + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', 'user' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end + + describe 'GET #show' do + before do + get :show, params: { id: report.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', 'user' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end + + describe 'POST #resolve' do + before do + post :resolve, params: { id: report.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', 'user' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end + + describe 'POST #reopen' do + before do + post :reopen, params: { id: report.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', 'user' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end + + describe 'POST #assign_to_self' do + before do + post :assign_to_self, params: { id: report.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', 'user' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end + + describe 'POST #unassign' do + before do + post :unassign, params: { id: report.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', 'user' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end +end -- cgit From 8f23726918fd8ded18ce9e55e6199df87abcc7cf Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 20 Jun 2019 10:52:36 +0200 Subject: Fix converted media being saved with original extension and mime type (#11130) --- app/models/concerns/attachmentable.rb | 2 +- app/models/media_attachment.rb | 8 +++++--- config/application.rb | 1 + lib/paperclip/type_corrector.rb | 19 +++++++++++++++++++ 4 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 lib/paperclip/type_corrector.rb (limited to 'app/models') diff --git a/app/models/concerns/attachmentable.rb b/app/models/concerns/attachmentable.rb index de4cf8775..f4e37f1e6 100644 --- a/app/models/concerns/attachmentable.rb +++ b/app/models/concerns/attachmentable.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'mime/types' +require 'mime/types/columnar' module Attachmentable extend ActiveSupport::Concern diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index 5ca218411..ae94ce68a 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -70,12 +70,14 @@ class MediaAttachment < ApplicationRecord AUDIO_STYLES = { original: { format: 'ogg', + content_type: 'audio/ogg', convert_options: {}, }, }.freeze VIDEO_FORMAT = { format: 'mp4', + content_type: 'video/mp4', convert_options: { output: { 'loglevel' => 'fatal', @@ -189,11 +191,11 @@ class MediaAttachment < ApplicationRecord if f.file_content_type == 'image/gif' [:gif_transcoder, :blurhash_transcoder] elsif VIDEO_MIME_TYPES.include?(f.file_content_type) - [:video_transcoder, :blurhash_transcoder] + [:video_transcoder, :blurhash_transcoder, :type_corrector] elsif AUDIO_MIME_TYPES.include?(f.file_content_type) - [:transcoder] + [:transcoder, :type_corrector] else - [:lazy_thumbnail, :blurhash_transcoder] + [:lazy_thumbnail, :blurhash_transcoder, :type_corrector] end end end diff --git a/config/application.rb b/config/application.rb index 74006f5fa..4534ede49 100644 --- a/config/application.rb +++ b/config/application.rb @@ -10,6 +10,7 @@ require_relative '../app/lib/exceptions' require_relative '../lib/paperclip/lazy_thumbnail' require_relative '../lib/paperclip/gif_transcoder' require_relative '../lib/paperclip/video_transcoder' +require_relative '../lib/paperclip/type_corrector' require_relative '../lib/mastodon/snowflake' require_relative '../lib/mastodon/version' require_relative '../lib/devise/ldap_authenticatable' diff --git a/lib/paperclip/type_corrector.rb b/lib/paperclip/type_corrector.rb new file mode 100644 index 000000000..0b0c10a56 --- /dev/null +++ b/lib/paperclip/type_corrector.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'mime/types/columnar' + +module Paperclip + class TypeCorrector < Paperclip::Processor + def make + target_extension = options[:format] + extension = File.extname(attachment.instance.file_file_name) + + return @file unless options[:style] == :original && target_extension && extension != target_extension + + attachment.instance.file_content_type = options[:content_type] || attachment.instance.file_content_type + attachment.instance.file_file_name = File.basename(attachment.instance.file_file_name, '.*') + '.' + target_extension + + @file + end + end +end -- cgit From 49ebda4d49af50b375126e4a8285686fb6448a60 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 21 Jun 2019 22:59:44 +0200 Subject: Change audio format from ogg to mp3 for wider compatibility (#11141) * Change audio format from ogg to mp3 for wider compatibility * Add media description as title to links of unknown media attachments --- app/javascript/mastodon/components/media_gallery.js | 2 +- app/javascript/styles/mastodon/components.scss | 1 + app/models/media_attachment.rb | 10 +++++++--- 3 files changed, 9 insertions(+), 4 deletions(-) (limited to 'app/models') diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js index 56618462b..77bac61ee 100644 --- a/app/javascript/mastodon/components/media_gallery.js +++ b/app/javascript/mastodon/components/media_gallery.js @@ -157,7 +157,7 @@ class Item extends React.PureComponent { if (attachment.get('type') === 'unknown') { return (
- +
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 545427d22..4a0d64ad3 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -557,6 +557,7 @@ .compose-form__upload-thumbnail { border-radius: 4px; + background-color: $base-shadow-color; background-position: center; background-size: cover; background-repeat: no-repeat; diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index ae94ce68a..4c55d4b98 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -69,9 +69,13 @@ class MediaAttachment < ApplicationRecord AUDIO_STYLES = { original: { - format: 'ogg', - content_type: 'audio/ogg', - convert_options: {}, + format: 'mp3', + content_type: 'audio/mpeg', + convert_options: { + output: { + 'q:a' => 2, + }, + }, }, }.freeze -- cgit From 707ddf7808f90e3ab042d7642d368c2ce8e95e6f Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 22 Jun 2019 00:13:10 +0200 Subject: Change domain blocks to automatically support subdomains (#11138) * Change domain blocks to automatically support subdomains If a more authoritative domain is blocked (example.com), then the same block will be applied to a subdomain (foo.example.com) * Match subdomains of existing accounts when blocking/unblocking domains * Improve code style --- app/controllers/admin/domain_blocks_controller.rb | 2 +- app/controllers/admin/instances_controller.rb | 2 +- app/controllers/media_proxy_controller.rb | 2 +- app/lib/activitypub/activity/create.rb | 2 +- app/lib/activitypub/activity/flag.rb | 2 +- app/lib/ostatus/activity/creation.rb | 4 +-- app/models/account.rb | 1 + app/models/custom_emoji.rb | 1 + app/models/domain_block.rb | 33 ++++++++++++++++++++-- app/models/instance.rb | 2 +- .../activitypub/process_account_service.rb | 2 +- app/services/block_domain_service.rb | 4 +-- app/services/resolve_account_service.rb | 2 +- app/services/unblock_domain_service.rb | 3 +- app/services/update_remote_profile_service.rb | 4 +-- spec/models/account_spec.rb | 17 +++++++++++ spec/models/domain_block_spec.rb | 31 +++++++++++++++----- 17 files changed, 89 insertions(+), 25 deletions(-) (limited to 'app/models') diff --git a/app/controllers/admin/domain_blocks_controller.rb b/app/controllers/admin/domain_blocks_controller.rb index 71597763b..377cac8ad 100644 --- a/app/controllers/admin/domain_blocks_controller.rb +++ b/app/controllers/admin/domain_blocks_controller.rb @@ -13,7 +13,7 @@ module Admin authorize :domain_block, :create? @domain_block = DomainBlock.new(resource_params) - existing_domain_block = resource_params[:domain].present? ? DomainBlock.find_by(domain: resource_params[:domain]) : nil + existing_domain_block = resource_params[:domain].present? ? DomainBlock.rule_for(resource_params[:domain]) : nil if existing_domain_block.present? && !@domain_block.stricter_than?(existing_domain_block) @domain_block.save diff --git a/app/controllers/admin/instances_controller.rb b/app/controllers/admin/instances_controller.rb index 6dd659a30..7888e844f 100644 --- a/app/controllers/admin/instances_controller.rb +++ b/app/controllers/admin/instances_controller.rb @@ -18,7 +18,7 @@ module Admin @blocks_count = Block.where(target_account: Account.where(domain: params[:id])).count @available = DeliveryFailureTracker.available?(Account.select(:shared_inbox_url).where(domain: params[:id]).first&.shared_inbox_url) @media_storage = MediaAttachment.where(account: Account.where(domain: params[:id])).sum(:file_file_size) - @domain_block = DomainBlock.find_by(domain: params[:id]) + @domain_block = DomainBlock.rule_for(params[:id]) end private diff --git a/app/controllers/media_proxy_controller.rb b/app/controllers/media_proxy_controller.rb index 950cf6d09..8fc18dd06 100644 --- a/app/controllers/media_proxy_controller.rb +++ b/app/controllers/media_proxy_controller.rb @@ -39,6 +39,6 @@ class MediaProxyController < ApplicationController end def reject_media? - DomainBlock.find_by(domain: @media_attachment.account.domain)&.reject_media? + DomainBlock.reject_media?(@media_attachment.account.domain) end end diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index f55dd35b2..487e8e91e 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -380,7 +380,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity def skip_download? return @skip_download if defined?(@skip_download) - @skip_download ||= DomainBlock.find_by(domain: @account.domain)&.reject_media? + @skip_download ||= DomainBlock.reject_media?(@account.domain) end def reply_to_local? diff --git a/app/lib/activitypub/activity/flag.rb b/app/lib/activitypub/activity/flag.rb index f73b93058..1659bc61f 100644 --- a/app/lib/activitypub/activity/flag.rb +++ b/app/lib/activitypub/activity/flag.rb @@ -23,7 +23,7 @@ class ActivityPub::Activity::Flag < ActivityPub::Activity private def skip_reports? - DomainBlock.find_by(domain: @account.domain)&.reject_reports? + DomainBlock.reject_reports?(@account.domain) end def object_uris diff --git a/app/lib/ostatus/activity/creation.rb b/app/lib/ostatus/activity/creation.rb index 3840c8fbf..60de712db 100644 --- a/app/lib/ostatus/activity/creation.rb +++ b/app/lib/ostatus/activity/creation.rb @@ -148,7 +148,7 @@ class OStatus::Activity::Creation < OStatus::Activity::Base end def save_media - do_not_download = DomainBlock.find_by(domain: @account.domain)&.reject_media? + do_not_download = DomainBlock.reject_media?(@account.domain) media_attachments = [] @xml.xpath('./xmlns:link[@rel="enclosure"]', xmlns: OStatus::TagManager::XMLNS).each do |link| @@ -176,7 +176,7 @@ class OStatus::Activity::Creation < OStatus::Activity::Base end def save_emojis(parent) - do_not_download = DomainBlock.find_by(domain: parent.account.domain)&.reject_media? + do_not_download = DomainBlock.reject_media?(parent.account.domain) return if do_not_download diff --git a/app/models/account.rb b/app/models/account.rb index 9276aa927..c588451fc 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -98,6 +98,7 @@ class Account < ApplicationRecord scope :tagged_with, ->(tag) { joins(:accounts_tags).where(accounts_tags: { tag_id: tag }) } scope :by_recent_status, -> { order(Arel.sql('(case when account_stats.last_status_at is null then 1 else 0 end) asc, account_stats.last_status_at desc')) } scope :popular, -> { order('account_stats.followers_count desc') } + scope :by_domain_and_subdomains, ->(domain) { where(domain: domain).or(where(arel_table[:domain].matches('%.' + domain))) } delegate :email, :unconfirmed_email, diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb index d3cc70504..e73cd9bd2 100644 --- a/app/models/custom_emoji.rb +++ b/app/models/custom_emoji.rb @@ -39,6 +39,7 @@ class CustomEmoji < ApplicationRecord scope :local, -> { where(domain: nil) } scope :remote, -> { where.not(domain: nil) } scope :alphabetic, -> { order(domain: :asc, shortcode: :asc) } + scope :by_domain_and_subdomains, ->(domain) { where(domain: domain).or(where(arel_table[:domain].matches('%.' + domain))) } remotable_attachment :image, LIMIT diff --git a/app/models/domain_block.rb b/app/models/domain_block.rb index 84c08c158..25d3b87ef 100644 --- a/app/models/domain_block.rb +++ b/app/models/domain_block.rb @@ -24,14 +24,41 @@ class DomainBlock < ApplicationRecord scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) } - def self.blocked?(domain) - where(domain: domain, severity: :suspend).exists? + class << self + def suspend?(domain) + !!rule_for(domain)&.suspend? + end + + def silence?(domain) + !!rule_for(domain)&.silence? + end + + def reject_media?(domain) + !!rule_for(domain)&.reject_media? + end + + def reject_reports?(domain) + !!rule_for(domain)&.reject_reports? + end + + alias blocked? suspend? + + def rule_for(domain) + return if domain.blank? + + uri = Addressable::URI.new.tap { |u| u.host = domain.gsub(/[\/]/, '') } + segments = uri.normalized_host.split('.') + variants = segments.map.with_index { |_, i| segments[i..-1].join('.') } + + where(domain: variants[0..-2]).order(Arel.sql('char_length(domain) desc')).first + end end def stricter_than?(other_block) - return true if suspend? + return true if suspend? return false if other_block.suspend? && (silence? || noop?) return false if other_block.silence? && noop? + (reject_media || !other_block.reject_media) && (reject_reports || !other_block.reject_reports) end diff --git a/app/models/instance.rb b/app/models/instance.rb index 7bf000d40..a01db1212 100644 --- a/app/models/instance.rb +++ b/app/models/instance.rb @@ -8,7 +8,7 @@ class Instance def initialize(resource) @domain = resource.domain @accounts_count = resource.is_a?(DomainBlock) ? nil : resource.accounts_count - @domain_block = resource.is_a?(DomainBlock) ? resource : DomainBlock.find_by(domain: domain) + @domain_block = resource.is_a?(DomainBlock) ? resource : DomainBlock.rule_for(domain) end def cached_sample_accounts diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index ad22d37fe..05c017bdf 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -205,7 +205,7 @@ class ActivityPub::ProcessAccountService < BaseService def domain_block return @domain_block if defined?(@domain_block) - @domain_block = DomainBlock.find_by(domain: @domain) + @domain_block = DomainBlock.rule_for(@domain) end def key_changed? diff --git a/app/services/block_domain_service.rb b/app/services/block_domain_service.rb index 497f0394b..c6eef04d4 100644 --- a/app/services/block_domain_service.rb +++ b/app/services/block_domain_service.rb @@ -76,7 +76,7 @@ class BlockDomainService < BaseService end def blocked_domain_accounts - Account.where(domain: blocked_domain) + Account.by_domain_and_subdomains(blocked_domain) end def media_from_blocked_domain @@ -84,6 +84,6 @@ class BlockDomainService < BaseService end def emojis_from_blocked_domains - CustomEmoji.where(domain: blocked_domain) + CustomEmoji.by_domain_and_subdomains(blocked_domain) end end diff --git a/app/services/resolve_account_service.rb b/app/services/resolve_account_service.rb index 11e33a83a..57c9ccfe1 100644 --- a/app/services/resolve_account_service.rb +++ b/app/services/resolve_account_service.rb @@ -146,7 +146,7 @@ class ResolveAccountService < BaseService def domain_block return @domain_block if defined?(@domain_block) - @domain_block = DomainBlock.find_by(domain: @domain) + @domain_block = DomainBlock.rule_for(@domain) end def atom_url diff --git a/app/services/unblock_domain_service.rb b/app/services/unblock_domain_service.rb index 9b8526fbe..fc262a50a 100644 --- a/app/services/unblock_domain_service.rb +++ b/app/services/unblock_domain_service.rb @@ -14,7 +14,8 @@ class UnblockDomainService < BaseService end def blocked_accounts - scope = Account.where(domain: domain_block.domain) + scope = Account.by_domain_and_subdomains(domain_block.domain) + if domain_block.silence? scope.where(silenced_at: @domain_block.created_at) else diff --git a/app/services/update_remote_profile_service.rb b/app/services/update_remote_profile_service.rb index 68d36addf..403395a0d 100644 --- a/app/services/update_remote_profile_service.rb +++ b/app/services/update_remote_profile_service.rb @@ -26,7 +26,7 @@ class UpdateRemoteProfileService < BaseService account.note = remote_profile.note || '' account.locked = remote_profile.locked? - if !account.suspended? && !DomainBlock.find_by(domain: account.domain)&.reject_media? + if !account.suspended? && !DomainBlock.reject_media?(account.domain) if remote_profile.avatar.present? account.avatar_remote_url = remote_profile.avatar else @@ -46,7 +46,7 @@ class UpdateRemoteProfileService < BaseService end def save_emojis - do_not_download = DomainBlock.find_by(domain: account.domain)&.reject_media? + do_not_download = DomainBlock.reject_media?(account.domain) return if do_not_download diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index 379872316..ce9ea250d 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -687,6 +687,23 @@ RSpec.describe Account, type: :model do end end + describe 'by_domain_and_subdomains' do + it 'returns exact domain matches' do + account = Fabricate(:account, domain: 'example.com') + expect(Account.by_domain_and_subdomains('example.com')).to eq [account] + end + + it 'returns subdomains' do + account = Fabricate(:account, domain: 'foo.example.com') + expect(Account.by_domain_and_subdomains('example.com')).to eq [account] + end + + it 'does not return partially matching domains' do + account = Fabricate(:account, domain: 'grexample.com') + expect(Account.by_domain_and_subdomains('example.com')).to_not eq [account] + end + end + describe 'expiring' do it 'returns remote accounts with followers whose subscription expiration date is past or not given' do local = Fabricate(:account, domain: nil) diff --git a/spec/models/domain_block_spec.rb b/spec/models/domain_block_spec.rb index 0035fd0ff..d98c5e118 100644 --- a/spec/models/domain_block_spec.rb +++ b/spec/models/domain_block_spec.rb @@ -21,23 +21,40 @@ RSpec.describe DomainBlock, type: :model do end end - describe 'blocked?' do + describe '.blocked?' do it 'returns true if the domain is suspended' do - Fabricate(:domain_block, domain: 'domain', severity: :suspend) - expect(DomainBlock.blocked?('domain')).to eq true + Fabricate(:domain_block, domain: 'example.com', severity: :suspend) + expect(DomainBlock.blocked?('example.com')).to eq true end it 'returns false even if the domain is silenced' do - Fabricate(:domain_block, domain: 'domain', severity: :silence) - expect(DomainBlock.blocked?('domain')).to eq false + Fabricate(:domain_block, domain: 'example.com', severity: :silence) + expect(DomainBlock.blocked?('example.com')).to eq false end it 'returns false if the domain is not suspended nor silenced' do - expect(DomainBlock.blocked?('domain')).to eq false + expect(DomainBlock.blocked?('example.com')).to eq false end end - describe 'stricter_than?' do + describe '.rule_for' do + it 'returns rule matching a blocked domain' do + block = Fabricate(:domain_block, domain: 'example.com') + expect(DomainBlock.rule_for('example.com')).to eq block + end + + it 'returns a rule matching a subdomain of a blocked domain' do + block = Fabricate(:domain_block, domain: 'example.com') + expect(DomainBlock.rule_for('sub.example.com')).to eq block + end + + it 'returns a rule matching a blocked subdomain' do + block = Fabricate(:domain_block, domain: 'sub.example.com') + expect(DomainBlock.rule_for('sub.example.com')).to eq block + end + end + + describe '#stricter_than?' do it 'returns true if the new block has suspend severity while the old has lower severity' do suspend = DomainBlock.new(domain: 'domain', severity: :suspend) silence = DomainBlock.new(domain: 'domain', severity: :silence) -- cgit From 1b052c7b2d70b371648732d4d2a1d0ca3a52cab0 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 22 Jun 2019 00:39:09 +0200 Subject: Remove expensive counters from federation page in admin UI (#11139) --- app/models/instance.rb | 8 ++------ app/views/admin/instances/index.html.haml | 21 +++++++++++---------- 2 files changed, 13 insertions(+), 16 deletions(-) (limited to 'app/models') diff --git a/app/models/instance.rb b/app/models/instance.rb index a01db1212..797a191e0 100644 --- a/app/models/instance.rb +++ b/app/models/instance.rb @@ -11,12 +11,8 @@ class Instance @domain_block = resource.is_a?(DomainBlock) ? resource : DomainBlock.rule_for(domain) end - def cached_sample_accounts - Rails.cache.fetch("#{cache_key}/sample_accounts", expires_in: 12.hours) { Account.where(domain: domain).searchable.joins(:account_stat).popular.limit(3) } - end - - def cached_accounts_count - @accounts_count || Rails.cache.fetch("#{cache_key}/count", expires_in: 12.hours) { Account.where(domain: domain).count } + def countable? + @accounts_count.present? end def to_param diff --git a/app/views/admin/instances/index.html.haml b/app/views/admin/instances/index.html.haml index 9574c3147..61e578409 100644 --- a/app/views/admin/instances/index.html.haml +++ b/app/views/admin/instances/index.html.haml @@ -33,21 +33,22 @@ %h4 = instance.domain %small - = t('admin.instances.known_accounts', count: instance.cached_accounts_count) - - if instance.domain_block + - first_item = true - if !instance.domain_block.noop? - • = t("admin.domain_blocks.severity.#{instance.domain_block.severity}") + - first_item = false - if instance.domain_block.reject_media? - • + - unless first_item + • = t('admin.domain_blocks.rejecting_media') + - first_item = false - if instance.domain_block.reject_reports? - • + - unless first_item + • = t('admin.domain_blocks.rejecting_reports') - - .avatar-stack - - instance.cached_sample_accounts.each do |account| - = image_tag current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url, width: 48, height: 48, alt: '', class: 'account__avatar' - + - else + = t('admin.accounts.no_limits_imposed') + - if instance.countable? + .trends__item__current{ title: t('admin.instances.known_accounts', count: instance.accounts_count) }= number_to_human instance.accounts_count, strip_insignificant_zeros: true = paginate paginated_instances -- cgit From aa9b37822bd39dff771844b7b1ac149e37788d34 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 22 Jun 2019 02:50:36 +0200 Subject: Fix audio not being downloaded from remote servers (#11145) --- app/lib/activitypub/activity/create.rb | 2 +- app/models/media_attachment.rb | 8 ++++++++ app/serializers/initial_state_serializer.rb | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) (limited to 'app/models') diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 487e8e91e..00f0dd42d 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -370,7 +370,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity end def unsupported_media_type?(mime_type) - mime_type.present? && !(MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES).include?(mime_type) + mime_type.present? && !MediaAttachment.supported_mime_types.include?(mime_type) end def supported_blurhash?(blurhash) diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index 4c55d4b98..a9b6d96c6 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -177,6 +177,14 @@ class MediaAttachment < ApplicationRecord before_save :set_meta class << self + def supported_mime_types + IMAGE_MIME_TYPES + VIDEO_MIME_TYPES + AUDIO_MIME_TYPES + end + + def supported_file_extensions + IMAGE_FILE_EXTENSIONS + VIDEO_FILE_EXTENSIONS + AUDIO_FILE_EXTENSIONS + end + private def file_styles(f) diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index 3d825410e..8daf36e0a 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -60,7 +60,7 @@ class InitialStateSerializer < ActiveModel::Serializer end def media_attachments - { accept_content_types: MediaAttachment::IMAGE_FILE_EXTENSIONS + MediaAttachment::VIDEO_FILE_EXTENSIONS + MediaAttachment::AUDIO_FILE_EXTENSIONS + MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES + MediaAttachment::AUDIO_MIME_TYPES } + { accept_content_types: MediaAttachment.supported_file_extensions + MediaAttachment.supported_mime_types } end private -- cgit From b927bb3f07b88d0a7bc39a7661d26dd1c9fc05d4 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 22 Jun 2019 16:54:06 +0200 Subject: Fix audio-only OGG and WebM files not being processed as such (#11151) Also, because Chrome sends audio/mp3 instead of audio/mpeg as it's supposed to, we need to whitelist that mime type as well --- app/models/concerns/attachmentable.rb | 17 +++++++++++++++++ app/models/media_attachment.rb | 4 ++-- 2 files changed, 19 insertions(+), 2 deletions(-) (limited to 'app/models') diff --git a/app/models/concerns/attachmentable.rb b/app/models/concerns/attachmentable.rb index f4e37f1e6..24f5968de 100644 --- a/app/models/concerns/attachmentable.rb +++ b/app/models/concerns/attachmentable.rb @@ -10,10 +10,21 @@ module Attachmentable included do before_post_process :set_file_extensions before_post_process :check_image_dimensions + before_post_process :set_file_content_type end private + def set_file_content_type + self.class.attachment_definitions.each_key do |attachment_name| + attachment = send(attachment_name) + + next if attachment.blank? || attachment.queued_for_write[:original].blank? + + attachment.instance_write :content_type, calculated_content_type(attachment) + end + end + def set_file_extensions self.class.attachment_definitions.each_key do |attachment_name| attachment = send(attachment_name) @@ -47,4 +58,10 @@ module Attachmentable extension end + + def calculated_content_type(attachment) + Paperclip.run('file', '-b --mime :file', file: attachment.queued_for_write[:original].path).split(/[:;\s]+/).first.chomp + rescue Terrapin::CommandLineError + '' + end end diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index a9b6d96c6..30d9a9851 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -31,9 +31,9 @@ class MediaAttachment < ApplicationRecord AUDIO_FILE_EXTENSIONS = ['.ogg', '.oga', '.mp3', '.wav', '.flac', '.opus'].freeze IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].freeze - VIDEO_MIME_TYPES = ['video/webm', 'video/mp4', 'video/quicktime'].freeze + VIDEO_MIME_TYPES = ['video/webm', 'video/mp4', 'video/quicktime', 'video/ogg'].freeze VIDEO_CONVERTIBLE_MIME_TYPES = ['video/webm', 'video/quicktime'].freeze - AUDIO_MIME_TYPES = ['audio/wave', 'audio/wav', 'audio/x-wav', 'audio/x-pn-wave', 'audio/ogg', 'audio/mpeg', 'audio/webm', 'audio/flac'].freeze + AUDIO_MIME_TYPES = ['audio/wave', 'audio/wav', 'audio/x-wav', 'audio/x-pn-wave', 'audio/ogg', 'audio/mpeg', 'audio/mp3', 'audio/webm', 'audio/flac'].freeze BLURHASH_OPTIONS = { x_comp: 4, -- cgit