Merge branch 'main' into glitch-soc/merge-upstream

This commit is contained in:
Claire 2021-04-21 13:52:03 +02:00
commit 536892b8ae
10 changed files with 100 additions and 88 deletions

View File

@ -12,7 +12,7 @@ export function fetchSuggestions(withRelationships = false) {
return (dispatch, getState) => { return (dispatch, getState) => {
dispatch(fetchSuggestionsRequest()); dispatch(fetchSuggestionsRequest());
api(getState).get('/api/v2/suggestions').then(response => { api(getState).get('/api/v2/suggestions', { params: { limit: 20 } }).then(response => {
dispatch(importFetchedAccounts(response.data.map(x => x.account))); dispatch(importFetchedAccounts(response.data.map(x => x.account)));
dispatch(fetchSuggestionsSuccess(response.data)); dispatch(fetchSuggestionsSuccess(response.data));

View File

@ -53,7 +53,7 @@ const messages = defineMessages({
publish: { id: 'compose_form.publish', defaultMessage: 'Toot' }, publish: { id: 'compose_form.publish', defaultMessage: 'Toot' },
}); });
const shouldHideFAB = path => path.match(/^\/statuses\/|^\/search|^\/getting-started/); const shouldHideFAB = path => path.match(/^\/statuses\/|^\/search|^\/getting-started|^\/start/);
export default @(component => injectIntl(component, { withRef: true })) export default @(component => injectIntl(component, { withRef: true }))
class ColumnsArea extends ImmutablePureComponent { class ColumnsArea extends ImmutablePureComponent {

View File

@ -144,7 +144,7 @@ class ActivityPub::Activity
end end
def delete_later!(uri) def delete_later!(uri)
redis.setex("delete_upon_arrival:#{@account.id}:#{uri}", 6.hours.seconds, uri) redis.setex("delete_upon_arrival:#{@account.id}:#{uri}", 6.hours.seconds, true)
end end
def status_from_object def status_from_object
@ -210,12 +210,22 @@ class ActivityPub::Activity
end end
end end
def lock_or_return(key, expire_after = 7.days.seconds) def lock_or_return(key, expire_after = 2.hours.seconds)
yield if redis.set(key, true, nx: true, ex: expire_after) yield if redis.set(key, true, nx: true, ex: expire_after)
ensure ensure
redis.del(key) redis.del(key)
end end
def lock_or_fail(key)
RedisLock.acquire({ redis: Redis.current, key: key }) do |lock|
if lock.acquired?
yield
else
raise Mastodon::RaceConditionError
end
end
end
def fetch? def fetch?
!@options[:delivery] !@options[:delivery]
end end

View File

@ -4,29 +4,25 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
def perform def perform
return reject_payload! if delete_arrived_first?(@json['id']) || !related_to_local_activity? return reject_payload! if delete_arrived_first?(@json['id']) || !related_to_local_activity?
RedisLock.acquire(lock_options) do |lock| lock_or_fail("announce:#{@object['id']}") do
if lock.acquired? original_status = status_from_object
original_status = status_from_object
return reject_payload! if original_status.nil? || !announceable?(original_status) return reject_payload! if original_status.nil? || !announceable?(original_status)
@status = Status.find_by(account: @account, reblog: original_status) @status = Status.find_by(account: @account, reblog: original_status)
return @status unless @status.nil? return @status unless @status.nil?
@status = Status.create!( @status = Status.create!(
account: @account, account: @account,
reblog: original_status, reblog: original_status,
uri: @json['id'], uri: @json['id'],
created_at: @json['published'], created_at: @json['published'],
override_timestamps: @options[:override_timestamps], override_timestamps: @options[:override_timestamps],
visibility: visibility_from_audience visibility: visibility_from_audience
) )
distribute(@status) distribute(@status)
else
raise Mastodon::RaceConditionError
end
end end
@status @status
@ -69,8 +65,4 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
def reblog_of_local_status? def reblog_of_local_status?
status_from_uri(object_uri)&.account&.local? status_from_uri(object_uri)&.account&.local?
end end
def lock_options
{ redis: Redis.current, key: "announce:#{@object['id']}" }
end
end end

View File

@ -45,19 +45,15 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
def create_status def create_status
return reject_payload! if unsupported_object_type? || invalid_origin?(object_uri) || tombstone_exists? || !related_to_local_activity? return reject_payload! if unsupported_object_type? || invalid_origin?(object_uri) || tombstone_exists? || !related_to_local_activity?
RedisLock.acquire(lock_options) do |lock| lock_or_fail("create:#{object_uri}") do
if lock.acquired? return if delete_arrived_first?(object_uri) || poll_vote? # rubocop:disable Lint/NonLocalExitFromIterator
return if delete_arrived_first?(object_uri) || poll_vote? # rubocop:disable Lint/NonLocalExitFromIterator
@status = find_existing_status @status = find_existing_status
if @status.nil? if @status.nil?
process_status process_status
elsif @options[:delivered_to_account_id].present? elsif @options[:delivered_to_account_id].present?
postprocess_audience_and_deliver postprocess_audience_and_deliver
end
else
raise Mastodon::RaceConditionError
end end
end end
@ -313,13 +309,9 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
poll = replied_to_status.preloadable_poll poll = replied_to_status.preloadable_poll
already_voted = true already_voted = true
RedisLock.acquire(poll_lock_options) do |lock| lock_or_fail("vote:#{replied_to_status.poll_id}:#{@account.id}") do
if lock.acquired? already_voted = poll.votes.where(account: @account).exists?
already_voted = poll.votes.where(account: @account).exists? poll.votes.create!(account: @account, choice: poll.options.index(@object['name']), uri: object_uri)
poll.votes.create!(account: @account, choice: poll.options.index(@object['name']), uri: object_uri)
else
raise Mastodon::RaceConditionError
end
end end
increment_voters_count! unless already_voted increment_voters_count! unless already_voted
@ -514,12 +506,4 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
poll.reload poll.reload
retry retry
end end
def lock_options
{ redis: Redis.current, key: "create:#{object_uri}" }
end
def poll_lock_options
{ redis: Redis.current, key: "vote:#{replied_to_status.poll_id}:#{@account.id}" }
end
end end

View File

@ -20,33 +20,35 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
def delete_note def delete_note
return if object_uri.nil? return if object_uri.nil?
unless invalid_origin?(object_uri) lock_or_return("delete_status_in_progress:#{object_uri}", 5.minutes.seconds) do
RedisLock.acquire(lock_options) { |_lock| delete_later!(object_uri) } unless invalid_origin?(object_uri)
Tombstone.find_or_create_by(uri: object_uri, account: @account) # This lock ensures a concurrent `ActivityPub::Activity::Create` either
# does not create a status at all, or has finished saving it to the
# database before we try to load it.
# Without the lock, `delete_later!` could be called after `delete_arrived_first?`
# and `Status.find` before `Status.create!`
lock_or_fail("create:#{object_uri}") { delete_later!(object_uri) }
Tombstone.find_or_create_by(uri: object_uri, account: @account)
end
@status = Status.find_by(uri: object_uri, account: @account)
@status ||= Status.find_by(uri: @object['atomUri'], account: @account) if @object.is_a?(Hash) && @object['atomUri'].present?
return if @status.nil?
forward! if @json['signature'].present? && @status.distributable?
delete_now!
end end
@status = Status.find_by(uri: object_uri, account: @account)
@status ||= Status.find_by(uri: @object['atomUri'], account: @account) if @object.is_a?(Hash) && @object['atomUri'].present?
return if @status.nil?
if @status.distributable?
forward_for_reply
forward_for_reblogs
end
delete_now!
end end
def forward_for_reblogs def rebloggers_ids
return if @json['signature'].blank? return @rebloggers_ids if defined?(@rebloggers_ids)
@rebloggers_ids = @status.reblogs.includes(:account).references(:account).merge(Account.local).pluck(:account_id)
end
rebloggers_ids = @status.reblogs.includes(:account).references(:account).merge(Account.local).pluck(:account_id) def inboxes_for_reblogs
inboxes = Account.where(id: ::Follow.where(target_account_id: rebloggers_ids).select(:account_id)).inboxes - [@account.preferred_inbox_url] Account.where(id: ::Follow.where(target_account_id: rebloggers_ids).select(:account_id)).inboxes
ActivityPub::LowPriorityDeliveryWorker.push_bulk(inboxes) do |inbox_url|
[payload, rebloggers_ids.first, inbox_url]
end
end end
def replied_to_status def replied_to_status
@ -58,13 +60,19 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
!replied_to_status.nil? && replied_to_status.account.local? !replied_to_status.nil? && replied_to_status.account.local?
end end
def forward_for_reply def inboxes_for_reply
return unless @json['signature'].present? && reply_to_local? replied_to_status.account.followers.inboxes
end
inboxes = replied_to_status.account.followers.inboxes - [@account.preferred_inbox_url] def forward!
inboxes = inboxes_for_reblogs
inboxes += inboxes_for_reply if reply_to_local?
inboxes -= [@account.preferred_inbox_url]
ActivityPub::LowPriorityDeliveryWorker.push_bulk(inboxes) do |inbox_url| sender_id = reply_to_local? ? replied_to_status.account_id : rebloggers_ids.first
[payload, replied_to_status.account_id, inbox_url]
ActivityPub::LowPriorityDeliveryWorker.push_bulk(inboxes.uniq) do |inbox_url|
[payload, sender_id, inbox_url]
end end
end end
@ -75,8 +83,4 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
def payload def payload
@payload ||= Oj.dump(@json) @payload ||= Oj.dump(@json)
end end
def lock_options
{ redis: Redis.current, key: "create:#{object_uri}" }
end
end end

View File

@ -16,6 +16,8 @@ class RemoveStatusService < BaseService
@account = status.account @account = status.account
@options = options @options = options
@status.discard
RedisLock.acquire(lock_options) do |lock| RedisLock.acquire(lock_options) do |lock|
if lock.acquired? if lock.acquired?
remove_from_self if @account.local? remove_from_self if @account.local?

View File

@ -69,10 +69,10 @@
- unless whitelist_mode? - unless whitelist_mode?
.fields-group .fields-group
= f.input :activity_api_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.activity_api_enabled.title'), hint: t('admin.settings.activity_api_enabled.desc_html') = f.input :activity_api_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.activity_api_enabled.title'), hint: t('admin.settings.activity_api_enabled.desc_html'), recommended: true
.fields-group .fields-group
= f.input :peers_api_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.peers_api_enabled.title'), hint: t('admin.settings.peers_api_enabled.desc_html') = f.input :peers_api_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.peers_api_enabled.title'), hint: t('admin.settings.peers_api_enabled.desc_html'), recommended: true
.fields-group .fields-group
= f.input :preview_sensitive_media, as: :boolean, wrapper: :with_label, label: t('admin.settings.preview_sensitive_media.title'), hint: t('admin.settings.preview_sensitive_media.desc_html') = f.input :preview_sensitive_media, as: :boolean, wrapper: :with_label, label: t('admin.settings.preview_sensitive_media.title'), hint: t('admin.settings.preview_sensitive_media.desc_html')

View File

@ -562,7 +562,7 @@ en:
settings: settings:
activity_api_enabled: activity_api_enabled:
desc_html: Counts of locally posted statuses, active users, and new registrations in weekly buckets desc_html: Counts of locally posted statuses, active users, and new registrations in weekly buckets
title: Publish aggregate statistics about user activity title: Publish aggregate statistics about user activity in the API
bootstrap_timeline_accounts: bootstrap_timeline_accounts:
desc_html: Separate multiple usernames by comma. Only local and unlocked accounts will work. Default when empty is all local admins. desc_html: Separate multiple usernames by comma. Only local and unlocked accounts will work. Default when empty is all local admins.
title: Default follows for new users title: Default follows for new users
@ -593,7 +593,7 @@ en:
title: Mascot image title: Mascot image
peers_api_enabled: peers_api_enabled:
desc_html: Domain names this server has encountered in the fediverse desc_html: Domain names this server has encountered in the fediverse
title: Publish list of discovered servers title: Publish list of discovered servers in the API
preview_sensitive_media: preview_sensitive_media:
desc_html: Link previews on other websites will display a thumbnail even if the media is marked as sensitive desc_html: Link previews on other websites will display a thumbnail even if the media is marked as sensitive
title: Show sensitive media in OpenGraph previews title: Show sensitive media in OpenGraph previews

View File

@ -49,4 +49,24 @@ RSpec.describe ActivityPub::Activity::Delete do
end end
end end
end end
context 'when the status has been reported' do
describe '#perform' do
subject { described_class.new(json, sender) }
let!(:reporter) { Fabricate(:account) }
before do
reporter.reports.create!(target_account: status.account, status_ids: [status.id], forwarded: false)
subject.perform
end
it 'marks the status as deleted' do
expect(Status.find_by(id: status.id)).to be_nil
end
it 'actually keeps a copy for inspection' do
expect(Status.with_discarded.find_by(id: status.id)).to_not be_nil
end
end
end
end end