Merge tag 'v3.1.4' into hometown-dev

This commit is contained in:
Darius Kazemi
2020-05-15 15:34:04 -07:00
1182 changed files with 15087 additions and 6745 deletions

View File

@ -6,7 +6,7 @@ class AccountFollowController < ApplicationController
before_action :authenticate_user!
def create
FollowService.new.call(current_user.account, @account.acct)
FollowService.new.call(current_user.account, @account, with_rate_limit: true)
redirect_to account_path(@account)
end
end

View File

@ -27,7 +27,7 @@ class AccountsController < ApplicationController
end
@pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses?
@statuses = filtered_status_page(params)
@statuses = filtered_status_page
@statuses = cache_collection(@statuses, Status)
@rss_url = rss_url
@ -40,7 +40,7 @@ class AccountsController < ApplicationController
format.rss do
expires_in 1.minute, public: true
@statuses = filtered_statuses.without_reblogs.without_local_only.without_replies.limit(PAGE_SIZE)
@statuses = filtered_statuses.without_reblogs.without_local_only.limit(PAGE_SIZE)
@statuses = cache_collection(@statuses, Status)
render xml: RSS::AccountSerializer.render(@account, @statuses, params[:tag])
end
@ -133,23 +133,23 @@ class AccountsController < ApplicationController
end
def media_requested?
request.path.ends_with?('/media') && !tag_requested?
request.path.split('.').first.ends_with?('/media') && !tag_requested?
end
def replies_requested?
request.path.ends_with?('/with_replies') && !tag_requested?
request.path.split('.').first.ends_with?('/with_replies') && !tag_requested?
end
def tag_requested?
request.path.split('.').first.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
end
def filtered_status_page(params)
if params[:min_id].present?
filtered_statuses.paginate_by_min_id(PAGE_SIZE, params[:min_id]).reverse
else
filtered_statuses.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id]).to_a
end
def filtered_status_page
filtered_statuses.paginate_by_id(PAGE_SIZE, params_slice(:max_id, :min_id, :since_id))
end
def params_slice(*keys)
params.slice(*keys).permit(*keys)
end
def restrict_fields_to

View File

@ -24,20 +24,23 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
def set_size
case params[:id]
when 'featured'
@account.pinned_statuses.count
@size = @account.pinned_statuses.count
else
raise ActiveRecord::RecordNotFound
not_found
end
end
def scope_for_collection
case params[:id]
when 'featured'
return Status.none if @account.blocking?(signed_request_account)
@account.pinned_statuses
else
raise ActiveRecord::RecordNotFound
# Because in public fetch mode we cache the response, there would be no
# benefit from performing the check below, since a blocked account or domain
# would likely be served the cache from the reverse proxy anyway
if authorized_fetch_mode? && !signed_request_account.nil? && (@account.blocking?(signed_request_account) || (!signed_request_account.domain.nil? && @account.domain_blocking?(signed_request_account.domain)))
Status.none
else
@account.pinned_statuses
end
end
end

View File

@ -49,7 +49,7 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
ResolveAccountWorker.perform_async(signed_request_account.acct)
end
DeliveryFailureTracker.track_inverse_success!(signed_request_account)
DeliveryFailureTracker.reset!(signed_request_account.inbox_url)
end
def process_payload

View File

@ -11,7 +11,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
before_action :set_cache_headers
def show
expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?)
expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode? && !(signed_request_account.present? && page_requested?))
render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
end
@ -50,12 +50,12 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
return unless page_requested?
@statuses = @account.statuses.permitted_for(@account, signed_request_account)
@statuses = params[:min_id].present? ? @statuses.paginate_by_min_id(LIMIT, params[:min_id]).reverse : @statuses.paginate_by_max_id(LIMIT, params[:max_id])
@statuses = @statuses.paginate_by_id(LIMIT, params_slice(:max_id, :min_id, :since_id))
@statuses = cache_collection(@statuses, Status)
end
def page_requested?
params[:page] == 'true'
truthy_param?(:page)
end
def page_params

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
class ActivityPub::RepliesController < ActivityPub::BaseController
include SignatureAuthentication
include SignatureVerification
include Authorization
include AccountOwnedConcern
@ -19,15 +19,19 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
private
def pundit_user
signed_request_account
end
def set_status
@status = @account.statuses.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound
not_found
end
def set_replies
@replies = page_params[:only_other_accounts] ? Status.where.not(account_id: @account.id) : @account.statuses
@replies = only_other_accounts? ? Status.where.not(account_id: @account.id) : @account.statuses
@replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted])
@replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id])
end
@ -38,7 +42,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
type: :unordered,
part_of: account_status_replies_url(@account, @status),
next: next_page,
items: @replies.map { |status| status.local ? status : status.uri }
items: @replies.map { |status| status.local? ? status : status.uri }
)
return page if page_requested?
@ -51,16 +55,21 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
end
def page_requested?
params[:page] == 'true'
truthy_param?(:page)
end
def only_other_accounts?
truthy_param?(:only_other_accounts)
end
def next_page
only_other_accounts = !(@replies&.last&.account_id == @account.id && @replies.size == DESCENDANTS_LIMIT)
account_status_replies_url(
@account,
@status,
page: true,
min_id: only_other_accounts && !page_params[:only_other_accounts] ? nil : @replies&.last&.id,
min_id: only_other_accounts && !only_other_accounts? ? nil : @replies&.last&.id,
only_other_accounts: only_other_accounts
)
end

View File

@ -2,8 +2,18 @@
module Admin
class ActionLogsController < BaseController
def index
@action_logs = Admin::ActionLog.page(params[:page])
before_action :set_action_logs
def index; end
private
def set_action_logs
@action_logs = Admin::ActionLogFilter.new(filter_params).results.page(params[:page])
end
def filter_params
params.slice(:page, *Admin::ActionLogFilter::KEYS).permit(:page, *Admin::ActionLogFilter::KEYS)
end
end
end

View File

@ -6,12 +6,12 @@ module Admin
def index
authorize :email_domain_block, :index?
@email_domain_blocks = EmailDomainBlock.page(params[:page])
@email_domain_blocks = EmailDomainBlock.where(parent_id: nil).includes(:children).order(id: :desc).page(params[:page])
end
def new
authorize :email_domain_block, :create?
@email_domain_block = EmailDomainBlock.new
@email_domain_block = EmailDomainBlock.new(domain: params[:_domain])
end
def create
@ -21,6 +21,28 @@ module Admin
if @email_domain_block.save
log_action :create, @email_domain_block
if @email_domain_block.with_dns_records?
hostnames = []
ips = []
Resolv::DNS.open do |dns|
dns.timeouts = 1
hostnames = dns.getresources(@email_domain_block.domain, Resolv::DNS::Resource::IN::MX).to_a.map { |e| e.exchange.to_s }
([@email_domain_block.domain] + hostnames).uniq.each do |hostname|
ips.concat(dns.getresources(hostname, Resolv::DNS::Resource::IN::A).to_a.map { |e| e.address.to_s })
ips.concat(dns.getresources(hostname, Resolv::DNS::Resource::IN::AAAA).to_a.map { |e| e.address.to_s })
end
end
(hostnames + ips).each do |hostname|
another_email_domain_block = EmailDomainBlock.new(domain: hostname, parent: @email_domain_block)
log_action :create, another_email_domain_block if another_email_domain_block.save
end
end
redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.created_msg')
else
render :new
@ -41,7 +63,7 @@ module Admin
end
def resource_params
params.require(:email_domain_block).permit(:domain)
params.require(:email_domain_block).permit(:domain, :with_dns_records)
end
end
end

View File

@ -19,7 +19,7 @@ module Admin
@followers_count = Follow.where(target_account: Account.where(domain: params[:id])).count
@reports_count = Report.where(target_account: Account.where(domain: params[:id])).count
@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)
@available = DeliveryFailureTracker.available?(params[:id])
@media_storage = MediaAttachment.where(account: Account.where(domain: params[:id])).sum(:file_file_size)
@private_comment = @domain_block&.private_comment
@public_comment = @domain_block&.public_comment

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
module Admin
class SiteUploadsController < BaseController
before_action :set_site_upload
def destroy
authorize :settings, :destroy?
@site_upload.destroy!
redirect_to edit_admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg')
end
private
def set_site_upload
@site_upload = SiteUpload.find(params[:id])
end
end
end

View File

@ -7,7 +7,7 @@ module Admin
def index
authorize :account_warning_preset, :index?
@warning_presets = AccountWarningPreset.all
@warning_presets = AccountWarningPreset.alphabetic
@warning_preset = AccountWarningPreset.new
end
@ -19,7 +19,7 @@ module Admin
if @warning_preset.save
redirect_to admin_warning_presets_path
else
@warning_presets = AccountWarningPreset.all
@warning_presets = AccountWarningPreset.alphabetic
render :index
end
end
@ -52,7 +52,7 @@ module Admin
end
def warning_preset_params
params.require(:account_warning_preset).permit(:text)
params.require(:account_warning_preset).permit(:title, :text)
end
end
end

View File

@ -44,6 +44,10 @@ class Api::BaseController < ApplicationController
render json: { error: 'There was a temporary problem serving your request, please try again' }, status: 503
end
rescue_from Mastodon::RateLimitExceededError do
render json: { error: I18n.t('errors.429') }, status: 429
end
rescue_from ActionController::ParameterMissing do |e|
render json: { error: e.to_s }, status: 400
end

View File

@ -5,8 +5,6 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
before_action :set_account
after_action :insert_pagination_headers
respond_to :json
def index
@accounts = load_accounts
render json: @accounts, each_serializer: REST::AccountSerializer
@ -22,12 +20,12 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
return [] if hide_results?
scope = default_accounts
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil?
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? || current_account.id == @account.id
scope.merge(paginated_follows).to_a
end
def hide_results?
(@account.user_hides_network? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account))
(@account.hides_followers? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account))
end
def default_accounts

View File

@ -5,8 +5,6 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
before_action :set_account
after_action :insert_pagination_headers
respond_to :json
def index
@accounts = load_accounts
render json: @accounts, each_serializer: REST::AccountSerializer
@ -22,12 +20,12 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
return [] if hide_results?
scope = default_accounts
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil?
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? || current_account.id == @account.id
scope.merge(paginated_follows).to_a
end
def hide_results?
(@account.user_hides_network? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account))
(@account.hides_following? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account))
end
def default_accounts

View File

@ -4,8 +4,6 @@ class Api::V1::Accounts::IdentityProofsController < Api::BaseController
before_action :require_user!
before_action :set_account
respond_to :json
def index
@proofs = @account.identity_proofs.active
render json: @proofs, each_serializer: REST::IdentityProofSerializer

View File

@ -5,8 +5,6 @@ class Api::V1::Accounts::ListsController < Api::BaseController
before_action :require_user!
before_action :set_account
respond_to :json
def index
@lists = @account.lists.where(account: current_account)
render json: @lists, each_serializer: REST::ListSerializer

View File

@ -7,8 +7,6 @@ class Api::V1::Accounts::PinsController < Api::BaseController
before_action :require_user!
before_action :set_account
respond_to :json
def create
AccountPin.create!(account: current_account, target_account: @account)
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter

View File

@ -4,8 +4,6 @@ class Api::V1::Accounts::RelationshipsController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:follows' }
before_action :require_user!
respond_to :json
def index
accounts = Account.where(id: account_ids).select('id')
# .where doesn't guarantee that our results are in the same order

View File

@ -4,8 +4,6 @@ class Api::V1::Accounts::SearchController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }
before_action :require_user!
respond_to :json
def show
@accounts = account_search
render json: @accounts, each_serializer: REST::AccountSerializer

View File

@ -6,8 +6,6 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
after_action :insert_pagination_headers, unless: -> { truthy_param?(:pinned) }
respond_to :json
def index
@statuses = load_statuses
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)

View File

@ -14,7 +14,7 @@ class Api::V1::AccountsController < Api::BaseController
skip_before_action :require_authenticated_user!, only: :create
respond_to :json
override_rate_limit_headers :follow, family: :follows
def show
render json: @account, serializer: REST::AccountSerializer
@ -31,7 +31,7 @@ class Api::V1::AccountsController < Api::BaseController
end
def follow
FollowService.new.call(current_user.account, @account, reblogs: truthy_param?(:reblogs))
FollowService.new.call(current_user.account, @account, reblogs: truthy_param?(:reblogs), with_rate_limit: true)
options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: truthy_param?(:reblogs) } }, requested_map: { @account.id => false } }

View File

@ -3,8 +3,6 @@
class Api::V1::Apps::CredentialsController < Api::BaseController
before_action -> { doorkeeper_authorize! :read }
respond_to :json
def show
render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer, fields: %i(name website vapid_key)
end

View File

@ -5,8 +5,6 @@ class Api::V1::BlocksController < Api::BaseController
before_action :require_user!
after_action :insert_pagination_headers
respond_to :json
def index
@accounts = load_accounts
render json: @accounts, each_serializer: REST::AccountSerializer

View File

@ -5,8 +5,6 @@ class Api::V1::BookmarksController < Api::BaseController
before_action :require_user!
after_action :insert_pagination_headers
respond_to :json
def index
@statuses = load_statuses
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)

View File

@ -9,8 +9,6 @@ class Api::V1::ConversationsController < Api::BaseController
before_action :set_conversation, except: :index
after_action :insert_pagination_headers, only: :index
respond_to :json
def index
@conversations = paginated_conversations
render json: @conversations, each_serializer: REST::ConversationSerializer

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
class Api::V1::CustomEmojisController < Api::BaseController
respond_to :json
skip_before_action :set_cache_headers
def index

View File

@ -8,8 +8,6 @@ class Api::V1::DomainBlocksController < Api::BaseController
before_action :require_user!
after_action :insert_pagination_headers, only: :show
respond_to :json
def show
@blocks = load_domain_blocks
render json: @blocks.map(&:domain)

View File

@ -5,8 +5,6 @@ class Api::V1::EndorsementsController < Api::BaseController
before_action :require_user!
after_action :insert_pagination_headers
respond_to :json
def index
@accounts = load_accounts
render json: @accounts, each_serializer: REST::AccountSerializer

View File

@ -5,8 +5,6 @@ class Api::V1::FavouritesController < Api::BaseController
before_action :require_user!
after_action :insert_pagination_headers
respond_to :json
def index
@statuses = load_statuses
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)

View File

@ -2,12 +2,9 @@
class Api::V1::FeaturedTags::SuggestionsController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index
before_action :require_user!
before_action :set_most_used_tags, only: :index
respond_to :json
def index
render json: @most_used_tags, each_serializer: REST::TagSerializer
end

View File

@ -7,8 +7,6 @@ class Api::V1::FiltersController < Api::BaseController
before_action :set_filters, only: :index
before_action :set_filter, only: [:show, :update, :destroy]
respond_to :json
def index
render json: @filters, each_serializer: REST::FilterSerializer
end

View File

@ -6,8 +6,6 @@ class Api::V1::Instances::ActivityController < Api::BaseController
skip_before_action :set_cache_headers
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
respond_to :json
def show
expires_in 1.day, public: true
render_with_cache json: :activity, expires_in: 1.day

View File

@ -6,8 +6,6 @@ class Api::V1::Instances::PeersController < Api::BaseController
skip_before_action :set_cache_headers
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
respond_to :json
def index
expires_in 1.day, public: true
render_with_cache(expires_in: 1.day) { Account.remote.domains }

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
class Api::V1::InstancesController < Api::BaseController
respond_to :json
skip_before_action :set_cache_headers
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?

View File

@ -3,27 +3,42 @@
class Api::V1::MediaController < Api::BaseController
before_action -> { doorkeeper_authorize! :write, :'write:media' }
before_action :require_user!
respond_to :json
before_action :set_media_attachment, except: [:create]
before_action :check_processing, except: [:create]
def create
@media = current_account.media_attachments.create!(media_params)
render json: @media, serializer: REST::MediaAttachmentSerializer
@media_attachment = current_account.media_attachments.create!(media_attachment_params)
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer
rescue Paperclip::Errors::NotIdentifiedByImageMagickError
render json: file_type_error, status: 422
rescue Paperclip::Error
render json: processing_error, status: 500
end
def show
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment
end
def update
@media = current_account.media_attachments.where(status_id: nil).find(params[:id])
@media.update!(media_params)
render json: @media, serializer: REST::MediaAttachmentSerializer
@media_attachment.update!(media_attachment_params)
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment
end
private
def media_params
def status_code_for_media_attachment
@media_attachment.not_processed? ? 206 : 200
end
def set_media_attachment
@media_attachment = current_account.media_attachments.unattached.find(params[:id])
end
def check_processing
render json: processing_error, status: 422 if @media_attachment.processing_failed?
end
def media_attachment_params
params.permit(:file, :description, :focus)
end

View File

@ -5,8 +5,6 @@ class Api::V1::MutesController < Api::BaseController
before_action :require_user!
after_action :insert_pagination_headers
respond_to :json
def index
@accounts = load_accounts
render json: @accounts, each_serializer: REST::AccountSerializer

View File

@ -6,8 +6,6 @@ class Api::V1::NotificationsController < Api::BaseController
before_action :require_user!
after_action :insert_pagination_headers, only: :index
respond_to :json
DEFAULT_NOTIFICATIONS_LIMIT = 15
def index

View File

@ -7,8 +7,6 @@ class Api::V1::Polls::VotesController < Api::BaseController
before_action :require_user!
before_action :set_poll
respond_to :json
def create
VoteService.new.call(current_account, @poll, vote_params[:choices])
render json: @poll, serializer: REST::PollSerializer
@ -20,7 +18,7 @@ class Api::V1::Polls::VotesController < Api::BaseController
@poll = Poll.attached.find(params[:poll_id])
authorize @poll.status, :show?
rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound
not_found
end
def vote_params

View File

@ -7,8 +7,6 @@ class Api::V1::PollsController < Api::BaseController
before_action :set_poll
before_action :refresh_poll
respond_to :json
def show
render json: @poll, serializer: REST::PollSerializer, include_results: true
end
@ -19,7 +17,7 @@ class Api::V1::PollsController < Api::BaseController
@poll = Poll.attached.find(params[:id])
authorize @poll.status, :show?
rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound
not_found
end
def refresh_poll

View File

@ -4,8 +4,6 @@ class Api::V1::PreferencesController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }
before_action :require_user!
respond_to :json
def index
render json: current_account, serializer: REST::PreferencesSerializer
end

View File

@ -4,6 +4,7 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
before_action -> { doorkeeper_authorize! :push }
before_action :require_user!
before_action :set_web_push_subscription
before_action :check_web_push_subscription, only: [:show, :update]
def create
@web_subscription&.destroy!
@ -21,16 +22,11 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
end
def show
raise ActiveRecord::RecordNotFound if @web_subscription.nil?
render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer
end
def update
raise ActiveRecord::RecordNotFound if @web_subscription.nil?
@web_subscription.update!(data: data_params)
render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer
end
@ -45,12 +41,17 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
@web_subscription = ::Web::PushSubscription.find_by(access_token_id: doorkeeper_token.id)
end
def check_web_push_subscription
not_found if @web_subscription.nil?
end
def subscription_params
params.require(:subscription).permit(:endpoint, keys: [:auth, :p256dh])
end
def data_params
return {} if params[:data].blank?
params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll])
end
end

View File

@ -4,7 +4,7 @@ class Api::V1::ReportsController < Api::BaseController
before_action -> { doorkeeper_authorize! :write, :'write:reports' }, only: [:create]
before_action :require_user!
respond_to :json
override_rate_limit_headers :create, family: :reports
def create
@report = ReportService.new.call(

View File

@ -7,8 +7,6 @@ class Api::V1::Statuses::BookmarksController < Api::BaseController
before_action :require_user!
before_action :set_status
respond_to :json
def create
current_account.bookmarks.find_or_create_by!(account: current_account, status: @status)
render json: @status, serializer: REST::StatusSerializer

View File

@ -7,8 +7,6 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
before_action :set_status
after_action :insert_pagination_headers
respond_to :json
def index
@accounts = load_accounts
render json: @accounts, each_serializer: REST::AccountSerializer

View File

@ -7,8 +7,6 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController
before_action :require_user!
before_action :set_status
respond_to :json
def create
FavouriteService.new.call(current_account, @status)
render json: @status, serializer: REST::StatusSerializer

View File

@ -8,8 +8,6 @@ class Api::V1::Statuses::MutesController < Api::BaseController
before_action :set_status
before_action :set_conversation
respond_to :json
def create
current_account.mute_conversation!(@conversation)
@mutes_map = { @conversation.id => true }
@ -30,8 +28,7 @@ class Api::V1::Statuses::MutesController < Api::BaseController
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
# Reraise in order to get a 404 instead of a 403 error code
raise ActiveRecord::RecordNotFound
not_found
end
def set_conversation

View File

@ -7,8 +7,6 @@ class Api::V1::Statuses::PinsController < Api::BaseController
before_action :require_user!
before_action :set_status
respond_to :json
def create
StatusPin.create!(account: current_account, status: @status)
distribute_add_activity!

View File

@ -7,8 +7,6 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
before_action :set_status
after_action :insert_pagination_headers
respond_to :json
def index
@accounts = load_accounts
render json: @accounts, each_serializer: REST::AccountSerializer

View File

@ -7,10 +7,11 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController
before_action :require_user!
before_action :set_reblog
respond_to :json
override_rate_limit_headers :create, family: :statuses
def create
@status = ReblogService.new.call(current_account, @reblog, reblog_params)
render json: @status, serializer: REST::StatusSerializer
end

View File

@ -7,8 +7,9 @@ class Api::V1::StatusesController < Api::BaseController
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:create, :destroy]
before_action :require_user!, except: [:show, :context]
before_action :set_status, only: [:show, :context]
before_action :set_thread, only: [:create]
respond_to :json
override_rate_limit_headers :create, family: :statuses
# This API was originally unlimited, pagination cannot be introduced without
# breaking backwards-compatibility. Arbitrarily high number to cover most
@ -36,7 +37,7 @@ class Api::V1::StatusesController < Api::BaseController
def create
@status = PostStatusService.new.call(current_user.account,
text: status_params[:status],
thread: status_params[:in_reply_to_id].blank? ? nil : Status.find(status_params[:in_reply_to_id]),
thread: @thread,
media_ids: status_params[:media_ids],
sensitive: status_params[:sensitive],
spoiler_text: status_params[:spoiler_text],
@ -45,7 +46,8 @@ class Api::V1::StatusesController < Api::BaseController
application: doorkeeper_token.application,
poll: status_params[:poll],
idempotency: request.headers['Idempotency-Key'],
local_only: status_params[:local_only])
local_only: status_params[:local_only]),
with_rate_limit: true)
render json: @status, serializer: @status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer
end
@ -66,7 +68,13 @@ class Api::V1::StatusesController < Api::BaseController
@status = Status.find(params[:id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound
not_found
end
def set_thread
@thread = status_params[:in_reply_to_id].blank? ? nil : Status.find(status_params[:in_reply_to_id])
rescue ActiveRecord::RecordNotFound
render json: { error: I18n.t('statuses.errors.in_reply_not_found') }, status: 404
end
def status_params

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
class Api::V1::StreamingController < Api::BaseController
respond_to :json
def index
if Rails.configuration.x.streaming_api_base_url != request.host
redirect_to streaming_api_url, status: 301

View File

@ -7,8 +7,6 @@ class Api::V1::SuggestionsController < Api::BaseController
before_action :require_user!
before_action :set_accounts
respond_to :json
def index
render json: @accounts, each_serializer: REST::AccountSerializer
end

View File

@ -5,8 +5,6 @@ class Api::V1::Timelines::HomeController < Api::BaseController
before_action :require_user!, only: [:show]
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
respond_to :json
def show
@statuses = load_statuses

View File

@ -4,8 +4,6 @@ class Api::V1::Timelines::PublicController < Api::BaseController
before_action :require_user!, only: [:show], if: :require_auth?
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
respond_to :json
def show
@statuses = load_statuses
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
@ -41,7 +39,7 @@ class Api::V1::Timelines::PublicController < Api::BaseController
end
def public_timeline_statuses
Status.as_public_timeline(current_account, truthy_param?(:local))
Status.as_public_timeline(current_account, truthy_param?(:remote) ? :remote : truthy_param?(:local))
end
def insert_pagination_headers
@ -49,7 +47,7 @@ class Api::V1::Timelines::PublicController < Api::BaseController
end
def pagination_params(core_params)
params.slice(:local, :limit, :only_media).permit(:local, :limit, :only_media).merge(core_params)
params.slice(:local, :remote, :limit, :only_media).permit(:local, :remote, :limit, :only_media).merge(core_params)
end
def next_path

View File

@ -4,8 +4,6 @@ class Api::V1::Timelines::TagController < Api::BaseController
before_action :load_tag
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
respond_to :json
def show
@statuses = load_statuses
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)

View File

@ -3,8 +3,6 @@
class Api::V1::TrendsController < Api::BaseController
before_action :set_tags
respond_to :json
def index
render json: @tags, each_serializer: REST::TagSerializer
end

View File

@ -0,0 +1,12 @@
# frozen_string_literal: true
class Api::V2::MediaController < Api::V1::MediaController
def create
@media_attachment = current_account.media_attachments.create!({ delay_processing: true }.merge(media_attachment_params))
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: 202
rescue Paperclip::Errors::NotIdentifiedByImageMagickError
render json: file_type_error, status: 422
rescue Paperclip::Error
render json: processing_error, status: 500
end
end

View File

@ -8,8 +8,6 @@ class Api::V2::SearchController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:search' }
before_action :require_user!
respond_to :json
def index
@search = Search.new(search_results)
render json: @search, serializer: REST::SearchSerializer

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
class Api::Web::EmbedsController < Api::Web::BaseController
respond_to :json
before_action :require_user!
def create

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
class Api::Web::PushSubscriptionsController < Api::Web::BaseController
respond_to :json
before_action :require_user!
def create

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
class Api::Web::SettingsController < Api::Web::BaseController
respond_to :json
before_action :require_user!
def update

View File

@ -29,6 +29,7 @@ class ApplicationController < ActionController::Base
rescue_from Mastodon::NotPermittedError, with: :forbidden
rescue_from HTTP::Error, OpenSSL::SSL::SSLError, with: :internal_server_error
rescue_from Mastodon::RaceConditionError, with: :service_unavailable
rescue_from Mastodon::RateLimitExceededError, with: :too_many_requests
before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?
before_action :require_functional!, if: :user_signed_in?
@ -111,6 +112,10 @@ class ApplicationController < ActionController::Base
respond_with_error(503)
end
def too_many_requests
respond_with_error(429)
end
def single_user_mode?
@single_user_mode ||= Rails.configuration.x.single_user_mode && Account.where('id > 0').exists?
end

View File

@ -111,6 +111,13 @@ class Auth::SessionsController < Devise::SessionsController
render :two_factor
end
def require_no_authentication
super
# Delete flash message that isn't entirely useful and may be confusing in
# most cases because /web doesn't display/clear flash messages.
flash.delete(:alert) if flash[:alert] == I18n.t('devise.failure.already_authenticated')
end
private
def set_instance_presenter

View File

@ -20,7 +20,7 @@ class AuthorizeInteractionsController < ApplicationController
end
def create
if @resource.is_a?(Account) && FollowService.new.call(current_account, @resource)
if @resource.is_a?(Account) && FollowService.new.call(current_account, @resource, with_rate_limit: true)
render :success
else
render :error

View File

@ -28,18 +28,6 @@ module Localized
end
def request_locale
preferred_locale || compatible_locale
end
def preferred_locale
http_accept_language.preferred_language_from(available_locales)
end
def compatible_locale
http_accept_language.compatible_language_from(available_locales)
end
def available_locales
I18n.available_locales.reverse
http_accept_language.language_region_compatible_from(I18n.available_locales)
end
end

View File

@ -3,6 +3,20 @@
module RateLimitHeaders
extend ActiveSupport::Concern
class_methods do
def override_rate_limit_headers(method_name, options = {})
around_action(only: method_name, if: :current_account) do |_controller, block|
begin
block.call
ensure
rate_limiter = RateLimiter.new(current_account, options)
rate_limit_headers = rate_limiter.to_headers
response.headers.merge!(rate_limit_headers) unless response.headers['X-RateLimit-Remaining'].present? && rate_limit_headers['X-RateLimit-Remaining'].to_i > response.headers['X-RateLimit-Remaining'].to_i
end
end
end
end
included do
before_action :set_rate_limit_headers, if: :rate_limited_request?
end
@ -44,7 +58,7 @@ module RateLimitHeaders
end
def api_throttle_data
most_limited_type, = request.env['rack.attack.throttle_data'].min_by { |_, v| v[:limit] }
most_limited_type, = request.env['rack.attack.throttle_data'].min_by { |_, v| v[:limit] - v[:count] }
request.env['rack.attack.throttle_data'][most_limited_type]
end

View File

@ -28,7 +28,8 @@ class FollowerAccountsController < ApplicationController
render json: collection_presenter,
serializer: ActivityPub::CollectionSerializer,
adapter: ActivityPub::Adapter,
content_type: 'application/activity+json'
content_type: 'application/activity+json',
fields: restrict_fields_to
end
end
end
@ -71,4 +72,12 @@ class FollowerAccountsController < ApplicationController
)
end
end
def restrict_fields_to
if page_requested? || !@account.user_hides_network?
# Return all fields
else
%i(id type totalItems)
end
end
end

View File

@ -28,7 +28,8 @@ class FollowingAccountsController < ApplicationController
render json: collection_presenter,
serializer: ActivityPub::CollectionSerializer,
adapter: ActivityPub::Adapter,
content_type: 'application/activity+json'
content_type: 'application/activity+json',
fields: restrict_fields_to
end
end
end
@ -71,4 +72,12 @@ class FollowingAccountsController < ApplicationController
)
end
end
def restrict_fields_to
if page_requested? || !@account.user_hides_network?
# Return all fields
else
%i(id type totalItems)
end
end
end

View File

@ -33,7 +33,7 @@ class MediaController < ApplicationController
def verify_permitted_status!
authorize @media_attachment.status, :show?
rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound
not_found
end
def check_playable

View File

@ -41,7 +41,7 @@ class RemoteInteractionController < ApplicationController
@status = Status.find(params[:id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound
not_found
end
def set_body_classes

View File

@ -21,8 +21,7 @@ class Settings::IdentityProofsController < Settings::BaseController
if current_account.username.casecmp(params[:username]).zero?
render layout: 'auth'
else
flash[:alert] = I18n.t('identity_proofs.errors.wrong_user', proving: params[:username], current: current_account.username)
redirect_to settings_identity_proofs_path
redirect_to settings_identity_proofs_path, alert: I18n.t('identity_proofs.errors.wrong_user', proving: params[:username], current: current_account.username)
end
end
@ -34,11 +33,16 @@ class Settings::IdentityProofsController < Settings::BaseController
PostStatusService.new.call(current_user.account, text: post_params[:status_text]) if publish_proof?
redirect_to @proof.on_success_path(params[:user_agent])
else
flash[:alert] = I18n.t('identity_proofs.errors.failed', provider: @proof.provider.capitalize)
redirect_to settings_identity_proofs_path
redirect_to settings_identity_proofs_path, alert: I18n.t('identity_proofs.errors.failed', provider: @proof.provider.capitalize)
end
end
def destroy
@proof = current_account.identity_proofs.find(params[:id])
@proof.destroy!
redirect_to settings_identity_proofs_path, success: I18n.t('identity_proofs.removed')
end
private
def check_required_params

View File

@ -29,6 +29,6 @@ class Settings::ImportsController < Settings::BaseController
end
def import_params
params.require(:import).permit(:data, :type)
params.require(:import).permit(:data, :type, :mode)
end
end

View File

@ -0,0 +1,37 @@
# frozen_string_literal: true
module Settings
class PicturesController < BaseController
before_action :authenticate_user!
before_action :set_account
before_action :set_picture
def destroy
if valid_picture
account_params = {
@picture => nil,
(@picture + '_remote_url') => nil,
}
msg = UpdateAccountService.new.call(@account, account_params) ? I18n.t('generic.changes_saved_msg') : nil
redirect_to settings_profile_path, notice: msg, status: 303
else
bad_request
end
end
private
def set_account
@account = current_account
end
def set_picture
@picture = params[:id]
end
def valid_picture
@picture == 'avatar' || @picture == 'header'
end
end
end

View File

@ -46,7 +46,7 @@ class StatusesController < ApplicationController
end
def embed
return not_found if @status.hidden?
return not_found if @status.hidden? || @status.reblog?
expires_in 180, public: true
response.headers['X-Frame-Options'] = 'ALLOWALL'

View File

@ -10,6 +10,7 @@ class TagsController < ApplicationController
before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :authenticate_user!, if: :whitelist_mode?
before_action :set_tag
before_action :set_local
before_action :set_body_classes
before_action :set_instance_presenter
@ -24,7 +25,7 @@ class TagsController < ApplicationController
format.rss do
expires_in 0, public: true
@statuses = HashtagQueryService.new.call(@tag, filter_params).limit(PAGE_SIZE)
@statuses = HashtagQueryService.new.call(@tag, filter_params, nil, @local).limit(PAGE_SIZE)
@statuses = cache_collection(@statuses, Status)
render xml: RSS::TagSerializer.render(@tag, @statuses)
@ -33,7 +34,7 @@ class TagsController < ApplicationController
format.json do
expires_in 3.minutes, public: public_fetch_mode?
@statuses = HashtagQueryService.new.call(@tag, filter_params, current_account, params[:local]).paginate_by_max_id(PAGE_SIZE, params[:max_id])
@statuses = HashtagQueryService.new.call(@tag, filter_params, current_account, @local).paginate_by_max_id(PAGE_SIZE, params[:max_id])
@statuses = cache_collection(@statuses, Status)
render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
@ -47,6 +48,10 @@ class TagsController < ApplicationController
@tag = Tag.usable.find_normalized!(params[:id])
end
def set_local
@local = truthy_param?(:local)
end
def set_body_classes
@body_classes = 'with-modals'
end