Add API parameter to safeguard unexpect mentions in new posts (#18350)

This commit is contained in:
Claire 2023-02-13 16:36:29 +01:00 committed by GitHub
parent c84f38abc4
commit d6930b3847
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 94 additions and 12 deletions

View file

@ -6,6 +6,15 @@ class PostStatusService < BaseService
MIN_SCHEDULE_OFFSET = 5.minutes.freeze
class UnexpectedMentionsError < StandardError
attr_reader :accounts
def initialize(message, accounts)
super(message)
@accounts = accounts
end
end
# Post a text status update, fetch and notify remote users mentioned
# @param [Account] account Account from which to post
# @param [Hash] options
@ -21,6 +30,7 @@ class PostStatusService < BaseService
# @option [Doorkeeper::Application] :application
# @option [String] :idempotency Optional idempotency key
# @option [Boolean] :with_rate_limit
# @option [Enumerable] :allowed_mentions Optional array of expected mentioned account IDs, raises `UnexpectedMentionsError` if unexpected accounts end up in mentions
# @return [Status]
def call(account, options = {})
@account = account
@ -63,14 +73,27 @@ class PostStatusService < BaseService
end
def process_status!
@status = @account.statuses.new(status_attributes)
process_mentions_service.call(@status, save_records: false)
safeguard_mentions!(@status)
# The following transaction block is needed to wrap the UPDATEs to
# the media attachments when the status is created
ApplicationRecord.transaction do
@status = @account.statuses.create!(status_attributes)
@status.save!
end
end
def safeguard_mentions!(status)
return if @options[:allowed_mentions].nil?
expected_account_ids = @options[:allowed_mentions].map(&:to_i)
unexpected_accounts = status.mentions.map(&:account).to_a.reject { |mentioned_account| expected_account_ids.include?(mentioned_account.id) }
return if unexpected_accounts.empty?
raise UnexpectedMentionsError.new('Post would be sent to unexpected accounts', unexpected_accounts)
end
def schedule_status!
status_for_validation = @account.statuses.build(status_attributes)
@ -93,7 +116,6 @@ class PostStatusService < BaseService
def postprocess_status!
process_hashtags_service.call(@status)
process_mentions_service.call(@status)
Trends.tags.register(@status)
LinkCrawlWorker.perform_async(@status.id)
DistributionWorker.perform_async(@status.id)

View file

@ -3,12 +3,13 @@
class ProcessMentionsService < BaseService
include Payloadable
# Scan status for mentions and fetch remote mentioned users, create
# local mention pointers, send Salmon notifications to mentioned
# remote users
# Scan status for mentions and fetch remote mentioned users,
# and create local mention pointers
# @param [Status] status
def call(status)
# @param [Boolean] save_records Whether to save records in database
def call(status, save_records: true)
@status = status
@save_records = save_records
return unless @status.local?
@ -55,14 +56,15 @@ class ProcessMentionsService < BaseService
next match if mention_undeliverable?(mentioned_account) || mentioned_account&.suspended?
mention = @previous_mentions.find { |x| x.account_id == mentioned_account.id }
mention ||= mentioned_account.mentions.new(status: @status)
mention ||= @current_mentions.find { |x| x.account_id == mentioned_account.id }
mention ||= @status.mentions.new(account: mentioned_account)
@current_mentions << mention
"@#{mentioned_account.acct}"
end
@status.save!
@status.save! if @save_records
end
def assign_mentions!
@ -73,11 +75,12 @@ class ProcessMentionsService < BaseService
mentioned_account_ids = @current_mentions.map(&:account_id)
blocked_account_ids = Set.new(@status.account.block_relationships.where(target_account_id: mentioned_account_ids).pluck(:target_account_id))
@current_mentions.select! { |mention| !(blocked_account_ids.include?(mention.account_id) || blocked_domains.include?(mention.account.domain)) }
dropped_mentions, @current_mentions = @current_mentions.partition { |mention| blocked_account_ids.include?(mention.account_id) || blocked_domains.include?(mention.account.domain) }
dropped_mentions.each(&:destroy)
end
@current_mentions.each do |mention|
mention.save if mention.new_record?
mention.save if mention.new_record? && @save_records
end
# If previous mentions are no longer contained in the text, convert them