From dd7ef0dc41584089a97444d8192bc61505108e6c Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 8 Aug 2017 21:52:15 +0200 Subject: Add ActivityPub inbox (#4216) * Add ActivityPub inbox * Handle ActivityPub deletes * Handle ActivityPub creates * Handle ActivityPub announces * Stubs for handling all activities that need to be handled * Add ActivityPub actor resolving * Handle conversation URI passing in ActivityPub * Handle content language in ActivityPub * Send accept header when fetching actor, handle JSON parse errors * Test for ActivityPub::FetchRemoteAccountService * Handle public key and icon/image when embedded/as array/as resolvable URI * Implement ActivityPub::FetchRemoteStatusService * Add stubs for more interactions * Undo activities implemented * Handle out of order activities * Hook up ActivityPub to ResolveRemoteAccountService, handle Update Account activities * Add fragment IDs to all transient activity serializers * Add tests and fixes * Add stubs for missing tests * Add more tests * Add more tests --- spec/lib/activitypub/activity/announce_spec.rb | 29 ++++ spec/lib/activitypub/activity/block_spec.rb | 28 ++++ spec/lib/activitypub/activity/create_spec.rb | 221 +++++++++++++++++++++++++ spec/lib/activitypub/activity/delete_spec.rb | 28 ++++ spec/lib/activitypub/activity/follow_spec.rb | 28 ++++ spec/lib/activitypub/activity/like_spec.rb | 29 ++++ spec/lib/activitypub/activity/undo_spec.rb | 107 ++++++++++++ spec/lib/activitypub/activity/update_spec.rb | 41 +++++ 8 files changed, 511 insertions(+) create mode 100644 spec/lib/activitypub/activity/announce_spec.rb create mode 100644 spec/lib/activitypub/activity/block_spec.rb create mode 100644 spec/lib/activitypub/activity/create_spec.rb create mode 100644 spec/lib/activitypub/activity/delete_spec.rb create mode 100644 spec/lib/activitypub/activity/follow_spec.rb create mode 100644 spec/lib/activitypub/activity/like_spec.rb create mode 100644 spec/lib/activitypub/activity/undo_spec.rb create mode 100644 spec/lib/activitypub/activity/update_spec.rb (limited to 'spec/lib/activitypub/activity') diff --git a/spec/lib/activitypub/activity/announce_spec.rb b/spec/lib/activitypub/activity/announce_spec.rb new file mode 100644 index 000000000..54dd52a60 --- /dev/null +++ b/spec/lib/activitypub/activity/announce_spec.rb @@ -0,0 +1,29 @@ +require 'rails_helper' + +RSpec.describe ActivityPub::Activity::Announce do + let(:sender) { Fabricate(:account) } + let(:recipient) { Fabricate(:account) } + let(:status) { Fabricate(:status, account: recipient) } + + let(:json) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: 'foo', + type: 'Announce', + actor: ActivityPub::TagManager.instance.uri_for(sender), + object: ActivityPub::TagManager.instance.uri_for(status), + }.with_indifferent_access + end + + describe '#perform' do + subject { described_class.new(json, sender) } + + before do + subject.perform + end + + it 'creates a reblog by sender of status' do + expect(sender.reblogged?(status)).to be true + end + end +end diff --git a/spec/lib/activitypub/activity/block_spec.rb b/spec/lib/activitypub/activity/block_spec.rb new file mode 100644 index 000000000..23c8cc31c --- /dev/null +++ b/spec/lib/activitypub/activity/block_spec.rb @@ -0,0 +1,28 @@ +require 'rails_helper' + +RSpec.describe ActivityPub::Activity::Block do + let(:sender) { Fabricate(:account) } + let(:recipient) { Fabricate(:account) } + + let(:json) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: 'foo', + type: 'Block', + actor: ActivityPub::TagManager.instance.uri_for(sender), + object: ActivityPub::TagManager.instance.uri_for(recipient), + }.with_indifferent_access + end + + describe '#perform' do + subject { described_class.new(json, sender) } + + before do + subject.perform + end + + it 'creates a block from sender to recipient' do + expect(sender.blocking?(recipient)).to be true + end + end +end diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb new file mode 100644 index 000000000..fcb044ebc --- /dev/null +++ b/spec/lib/activitypub/activity/create_spec.rb @@ -0,0 +1,221 @@ +require 'rails_helper' + +RSpec.describe ActivityPub::Activity::Create do + let(:sender) { Fabricate(:account, followers_url: 'http://example.com/followers') } + + let(:json) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: 'foo', + type: 'Create', + actor: ActivityPub::TagManager.instance.uri_for(sender), + object: object_json, + }.with_indifferent_access + end + + subject { described_class.new(json, sender) } + + before do + stub_request(:get, 'http://example.com/attachment.png').to_return(request_fixture('avatar.txt')) + end + + describe '#perform' do + before do + subject.perform + end + + context 'standalone' do + let(:object_json) do + { + id: 'bar', + type: 'Note', + content: 'Lorem ipsum', + } + end + + it 'creates status' do + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.text).to eq 'Lorem ipsum' + end + + it 'missing to/cc defaults to direct privacy' do + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.visibility).to eq 'direct' + end + end + + context 'public' do + let(:object_json) do + { + id: 'bar', + type: 'Note', + content: 'Lorem ipsum', + to: 'https://www.w3.org/ns/activitystreams#Public', + } + end + + it 'creates status' do + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.visibility).to eq 'public' + end + end + + context 'unlisted' do + let(:object_json) do + { + id: 'bar', + type: 'Note', + content: 'Lorem ipsum', + cc: 'https://www.w3.org/ns/activitystreams#Public', + } + end + + it 'creates status' do + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.visibility).to eq 'unlisted' + end + end + + context 'private' do + let(:object_json) do + { + id: 'bar', + type: 'Note', + content: 'Lorem ipsum', + to: 'http://example.com/followers', + } + end + + it 'creates status' do + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.visibility).to eq 'private' + end + end + + context 'direct' do + let(:recipient) { Fabricate(:account) } + + let(:object_json) do + { + id: 'bar', + type: 'Note', + content: 'Lorem ipsum', + to: ActivityPub::TagManager.instance.uri_for(recipient), + } + end + + it 'creates status' do + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.visibility).to eq 'direct' + end + end + + context 'as a reply' do + let(:original_status) { Fabricate(:status) } + + let(:object_json) do + { + id: 'bar', + type: 'Note', + content: 'Lorem ipsum', + inReplyTo: ActivityPub::TagManager.instance.uri_for(original_status), + } + end + + it 'creates status' do + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.thread).to eq original_status + expect(status.reply?).to be true + expect(status.in_reply_to_account).to eq original_status.account + expect(status.conversation).to eq original_status.conversation + end + end + + context 'with mentions' do + let(:recipient) { Fabricate(:account) } + + let(:object_json) do + { + id: 'bar', + type: 'Note', + content: 'Lorem ipsum', + tag: [ + { + type: 'Mention', + href: ActivityPub::TagManager.instance.uri_for(recipient), + }, + ], + } + end + + it 'creates status' do + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.mentions.map(&:account)).to include(recipient) + end + end + + context 'with media attachments' do + let(:object_json) do + { + id: 'bar', + type: 'Note', + content: 'Lorem ipsum', + attachment: [ + { + type: 'Document', + mime_type: 'image/png', + url: 'http://example.com/attachment.png', + }, + ], + } + end + + it 'creates status' do + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.media_attachments.map(&:remote_url)).to include('http://example.com/attachment.png') + end + end + + context 'with hashtags' do + let(:object_json) do + { + id: 'bar', + type: 'Note', + content: 'Lorem ipsum', + tag: [ + { + type: 'Hashtag', + href: 'http://example.com/blah', + name: '#test', + }, + ], + } + end + + it 'creates status' do + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.tags.map(&:name)).to include('test') + end + end + end +end diff --git a/spec/lib/activitypub/activity/delete_spec.rb b/spec/lib/activitypub/activity/delete_spec.rb new file mode 100644 index 000000000..398669b48 --- /dev/null +++ b/spec/lib/activitypub/activity/delete_spec.rb @@ -0,0 +1,28 @@ +require 'rails_helper' + +RSpec.describe ActivityPub::Activity::Delete do + let(:sender) { Fabricate(:account) } + let(:status) { Fabricate(:status, account: sender, uri: 'foobar') } + + let(:json) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: 'foo', + type: 'Delete', + actor: ActivityPub::TagManager.instance.uri_for(sender), + object: ActivityPub::TagManager.instance.uri_for(status), + }.with_indifferent_access + end + + describe '#perform' do + subject { described_class.new(json, sender) } + + before do + subject.perform + end + + it 'deletes sender\'s status' do + expect(Status.find_by(id: status.id)).to be_nil + end + end +end diff --git a/spec/lib/activitypub/activity/follow_spec.rb b/spec/lib/activitypub/activity/follow_spec.rb new file mode 100644 index 000000000..7c0e447f3 --- /dev/null +++ b/spec/lib/activitypub/activity/follow_spec.rb @@ -0,0 +1,28 @@ +require 'rails_helper' + +RSpec.describe ActivityPub::Activity::Follow do + let(:sender) { Fabricate(:account) } + let(:recipient) { Fabricate(:account) } + + let(:json) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: 'foo', + type: 'Follow', + actor: ActivityPub::TagManager.instance.uri_for(sender), + object: ActivityPub::TagManager.instance.uri_for(recipient), + }.with_indifferent_access + end + + describe '#perform' do + subject { described_class.new(json, sender) } + + before do + subject.perform + end + + it 'creates a follow from sender to recipient' do + expect(sender.following?(recipient)).to be true + end + end +end diff --git a/spec/lib/activitypub/activity/like_spec.rb b/spec/lib/activitypub/activity/like_spec.rb new file mode 100644 index 000000000..b69615a9d --- /dev/null +++ b/spec/lib/activitypub/activity/like_spec.rb @@ -0,0 +1,29 @@ +require 'rails_helper' + +RSpec.describe ActivityPub::Activity::Like do + let(:sender) { Fabricate(:account) } + let(:recipient) { Fabricate(:account) } + let(:status) { Fabricate(:status, account: recipient) } + + let(:json) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: 'foo', + type: 'Like', + actor: ActivityPub::TagManager.instance.uri_for(sender), + object: ActivityPub::TagManager.instance.uri_for(status), + }.with_indifferent_access + end + + describe '#perform' do + subject { described_class.new(json, sender) } + + before do + subject.perform + end + + it 'creates a favourite from sender to status' do + expect(sender.favourited?(status)).to be true + end + end +end diff --git a/spec/lib/activitypub/activity/undo_spec.rb b/spec/lib/activitypub/activity/undo_spec.rb new file mode 100644 index 000000000..4629a033f --- /dev/null +++ b/spec/lib/activitypub/activity/undo_spec.rb @@ -0,0 +1,107 @@ +require 'rails_helper' + +RSpec.describe ActivityPub::Activity::Undo do + let(:sender) { Fabricate(:account) } + + let(:json) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: 'foo', + type: 'Undo', + actor: ActivityPub::TagManager.instance.uri_for(sender), + object: object_json, + }.with_indifferent_access + end + + subject { described_class.new(json, sender) } + + describe '#perform' do + context 'with Announce' do + let(:status) { Fabricate(:status) } + + let(:object_json) do + { + id: 'bar', + type: 'Announce', + actor: ActivityPub::TagManager.instance.uri_for(sender), + object: ActivityPub::TagManager.instance.uri_for(status), + } + end + + before do + Fabricate(:status, reblog: status, account: sender, uri: 'bar') + end + + it 'deletes the reblog' do + subject.perform + expect(sender.reblogged?(status)).to be false + end + end + + context 'with Block' do + let(:recipient) { Fabricate(:account) } + + let(:object_json) do + { + id: 'bar', + type: 'Block', + actor: ActivityPub::TagManager.instance.uri_for(sender), + object: ActivityPub::TagManager.instance.uri_for(recipient), + } + end + + before do + sender.block!(recipient) + end + + it 'deletes block from sender to recipient' do + subject.perform + expect(sender.blocking?(recipient)).to be false + end + end + + context 'with Follow' do + let(:recipient) { Fabricate(:account) } + + let(:object_json) do + { + id: 'bar', + type: 'Follow', + actor: ActivityPub::TagManager.instance.uri_for(sender), + object: ActivityPub::TagManager.instance.uri_for(recipient), + } + end + + before do + sender.follow!(recipient) + end + + it 'deletes follow from sender to recipient' do + subject.perform + expect(sender.following?(recipient)).to be false + end + end + + context 'with Like' do + let(:status) { Fabricate(:status) } + + let(:object_json) do + { + id: 'bar', + type: 'Like', + actor: ActivityPub::TagManager.instance.uri_for(sender), + object: ActivityPub::TagManager.instance.uri_for(status), + } + end + + before do + Fabricate(:favourite, account: sender, status: status) + end + + it 'deletes favourite from sender to status' do + subject.perform + expect(sender.favourited?(status)).to be false + end + end + end +end diff --git a/spec/lib/activitypub/activity/update_spec.rb b/spec/lib/activitypub/activity/update_spec.rb new file mode 100644 index 000000000..0bd6d00d9 --- /dev/null +++ b/spec/lib/activitypub/activity/update_spec.rb @@ -0,0 +1,41 @@ +require 'rails_helper' + +RSpec.describe ActivityPub::Activity::Update do + let!(:sender) { Fabricate(:account) } + + before do + sender.update!(uri: ActivityPub::TagManager.instance.uri_for(sender)) + end + + let(:modified_sender) do + sender.dup.tap do |modified_sender| + modified_sender.display_name = 'Totally modified now' + end + end + + let(:actor_json) do + ActiveModelSerializers::SerializableResource.new(modified_sender, serializer: ActivityPub::ActorSerializer, key_transform: :camel_lower).as_json + end + + let(:json) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: 'foo', + type: 'Update', + actor: ActivityPub::TagManager.instance.uri_for(sender), + object: actor_json, + }.with_indifferent_access + end + + describe '#perform' do + subject { described_class.new(json, sender) } + + before do + subject.perform + end + + it 'updates profile' do + expect(sender.reload.display_name).to eq 'Totally modified now' + end + end +end -- cgit From a855956185630742ad670f971337a3ff76fd8b32 Mon Sep 17 00:00:00 2001 From: unarist Date: Mon, 14 Aug 2017 23:57:46 +0900 Subject: Fix ActivityPub follow interaction and add more specs (#4601) --- app/lib/activitypub/activity/accept.rb | 2 +- app/lib/activitypub/activity/reject.rb | 2 +- spec/lib/activitypub/activity/accept_spec.rb | 38 ++++++++++++++++++++++++++++ spec/lib/activitypub/activity/follow_spec.rb | 29 ++++++++++++++++++--- spec/lib/activitypub/activity/reject_spec.rb | 38 ++++++++++++++++++++++++++++ 5 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 spec/lib/activitypub/activity/accept_spec.rb create mode 100644 spec/lib/activitypub/activity/reject_spec.rb (limited to 'spec/lib/activitypub/activity') diff --git a/app/lib/activitypub/activity/accept.rb b/app/lib/activitypub/activity/accept.rb index f5880937a..44c432ae7 100644 --- a/app/lib/activitypub/activity/accept.rb +++ b/app/lib/activitypub/activity/accept.rb @@ -20,6 +20,6 @@ class ActivityPub::Activity::Accept < ActivityPub::Activity end def target_uri - @target_uri ||= @object['object'].is_a?(String) ? @object['object'] : @object['object']['id'] + @target_uri ||= @object['actor'] end end diff --git a/app/lib/activitypub/activity/reject.rb b/app/lib/activitypub/activity/reject.rb index 78dbfd1e5..6a234994e 100644 --- a/app/lib/activitypub/activity/reject.rb +++ b/app/lib/activitypub/activity/reject.rb @@ -20,6 +20,6 @@ class ActivityPub::Activity::Reject < ActivityPub::Activity end def target_uri - @target_uri ||= @object['object'].is_a?(String) ? @object['object'] : @object['object']['id'] + @target_uri ||= @object['actor'] end end diff --git a/spec/lib/activitypub/activity/accept_spec.rb b/spec/lib/activitypub/activity/accept_spec.rb new file mode 100644 index 000000000..6503c83e3 --- /dev/null +++ b/spec/lib/activitypub/activity/accept_spec.rb @@ -0,0 +1,38 @@ +require 'rails_helper' + +RSpec.describe ActivityPub::Activity::Accept do + let(:sender) { Fabricate(:account) } + let(:recipient) { Fabricate(:account) } + + let(:json) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: 'foo', + type: 'Accept', + actor: ActivityPub::TagManager.instance.uri_for(sender), + object: { + id: 'bar', + type: 'Follow', + actor: ActivityPub::TagManager.instance.uri_for(recipient), + object: ActivityPub::TagManager.instance.uri_for(sender), + }, + }.with_indifferent_access + end + + describe '#perform' do + subject { described_class.new(json, sender) } + + before do + Fabricate(:follow_request, account: recipient, target_account: sender) + subject.perform + end + + it 'creates a follow relationship' do + expect(recipient.following?(sender)).to be true + end + + it 'removes the follow request' do + expect(recipient.requested?(sender)).to be false + end + end +end diff --git a/spec/lib/activitypub/activity/follow_spec.rb b/spec/lib/activitypub/activity/follow_spec.rb index 7c0e447f3..6bbacdbe6 100644 --- a/spec/lib/activitypub/activity/follow_spec.rb +++ b/spec/lib/activitypub/activity/follow_spec.rb @@ -17,12 +17,33 @@ RSpec.describe ActivityPub::Activity::Follow do describe '#perform' do subject { described_class.new(json, sender) } - before do - subject.perform + context 'unlocked account' do + before do + subject.perform + end + + it 'creates a follow from sender to recipient' do + expect(sender.following?(recipient)).to be true + end + + it 'does not create a follow request' do + expect(sender.requested?(recipient)).to be false + end end - it 'creates a follow from sender to recipient' do - expect(sender.following?(recipient)).to be true + context 'locked account' do + before do + recipient.update(locked: true) + subject.perform + end + + it 'does not create a follow from sender to recipient' do + expect(sender.following?(recipient)).to be false + end + + it 'creates a follow request' do + expect(sender.requested?(recipient)).to be true + end end end end diff --git a/spec/lib/activitypub/activity/reject_spec.rb b/spec/lib/activitypub/activity/reject_spec.rb new file mode 100644 index 000000000..7fd95bcc6 --- /dev/null +++ b/spec/lib/activitypub/activity/reject_spec.rb @@ -0,0 +1,38 @@ +require 'rails_helper' + +RSpec.describe ActivityPub::Activity::Reject do + let(:sender) { Fabricate(:account) } + let(:recipient) { Fabricate(:account) } + + let(:json) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: 'foo', + type: 'Reject', + actor: ActivityPub::TagManager.instance.uri_for(sender), + object: { + id: 'bar', + type: 'Follow', + actor: ActivityPub::TagManager.instance.uri_for(recipient), + object: ActivityPub::TagManager.instance.uri_for(sender), + }, + }.with_indifferent_access + end + + describe '#perform' do + subject { described_class.new(json, sender) } + + before do + Fabricate(:follow_request, account: recipient, target_account: sender) + subject.perform + end + + it 'does not create a follow relationship' do + expect(recipient.following?(sender)).to be false + end + + it 'removes the follow request' do + expect(recipient.requested?(sender)).to be false + end + end +end -- cgit From 938cd2875b14db3655a6c9f82f672f4baf7720a3 Mon Sep 17 00:00:00 2001 From: unarist Date: Tue, 29 Aug 2017 05:08:11 +0900 Subject: Fix Delete activity handling when the status has been reblogged (#4729) --- app/lib/activitypub/activity/delete.rb | 4 ++-- spec/lib/activitypub/activity/delete_spec.rb | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) (limited to 'spec/lib/activitypub/activity') diff --git a/app/lib/activitypub/activity/delete.rb b/app/lib/activitypub/activity/delete.rb index e7eb53a02..789ed58f1 100644 --- a/app/lib/activitypub/activity/delete.rb +++ b/app/lib/activitypub/activity/delete.rb @@ -16,8 +16,8 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity private def forward_for_reblogs(status) - ActivityPub::RawDistributionWorker.push_bulk(status.reblogs.includes(:account).references(:account).merge(Account.local).pluck(:account_id)) do |account| - [payload, account.id] + ActivityPub::RawDistributionWorker.push_bulk(status.reblogs.includes(:account).references(:account).merge(Account.local).pluck(:account_id)) do |account_id| + [payload, account_id] end end diff --git a/spec/lib/activitypub/activity/delete_spec.rb b/spec/lib/activitypub/activity/delete_spec.rb index 398669b48..6601f7262 100644 --- a/spec/lib/activitypub/activity/delete_spec.rb +++ b/spec/lib/activitypub/activity/delete_spec.rb @@ -25,4 +25,28 @@ RSpec.describe ActivityPub::Activity::Delete do expect(Status.find_by(id: status.id)).to be_nil end end + + context 'when the status has been reblogged' do + describe '#perform' do + subject { described_class.new(json, sender) } + let(:reblogger) { Fabricate(:account) } + let(:follower) { Fabricate(:account, username: 'follower', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') } + + before do + stub_request(:post, 'http://example.com/inbox').to_return(status: 200) + follower.follow!(reblogger) + Fabricate(:status, account: reblogger, reblog: status) + subject.perform + end + + it 'deletes sender\'s status' do + expect(Status.find_by(id: status.id)).to be_nil + end + + it 'sends delete activity to followers of rebloggers' do + # one for Delete original post, and one for Undo reblog (normal delivery) + expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.twice + end + end + end end -- cgit From 7b8f26284072120701289f90bc6602ce918e4304 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 30 Aug 2017 15:37:02 +0200 Subject: Forward ActivityPub creates that reply to local statuses (#4709) * Forward ActivityPub creates that reply to local statuses * Fix test * Fix wrong signers --- app/lib/activitypub/activity/create.rb | 10 ++++++ app/lib/activitypub/activity/delete.rb | 2 ++ app/services/post_status_service.rb | 1 + .../activitypub/reply_distribution_worker.rb | 42 ++++++++++++++++++++++ spec/lib/activitypub/activity/delete_spec.rb | 1 + 5 files changed, 56 insertions(+) create mode 100644 app/workers/activitypub/reply_distribution_worker.rb (limited to 'spec/lib/activitypub/activity') diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 114aed84f..2eea1827a 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -17,6 +17,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity resolve_thread(status) distribute(status) + forward_for_reply if status.public_visibility? || status.unlisted_visibility? status end @@ -162,4 +163,13 @@ class ActivityPub::Activity::Create < ActivityPub::Activity return @skip_download if defined?(@skip_download) @skip_download ||= DomainBlock.find_by(domain: @account.domain)&.reject_media? end + + def reply_to_local? + !replied_to_status.nil? && replied_to_status.account.local? + end + + def forward_for_reply + return unless @json['signature'].present? && reply_to_local? + ActivityPub::RawDistributionWorker.perform_async(Oj.dump(@json), replied_to_status.account_id) + end end diff --git a/app/lib/activitypub/activity/delete.rb b/app/lib/activitypub/activity/delete.rb index 789ed58f1..afa9a8079 100644 --- a/app/lib/activitypub/activity/delete.rb +++ b/app/lib/activitypub/activity/delete.rb @@ -16,6 +16,8 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity private def forward_for_reblogs(status) + return if @json['signature'].blank? + ActivityPub::RawDistributionWorker.push_bulk(status.reblogs.includes(:account).references(:account).merge(Account.local).pluck(:account_id)) do |account_id| [payload, account_id] end diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 5ff93f21e..568f5a9e7 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -40,6 +40,7 @@ class PostStatusService < BaseService DistributionWorker.perform_async(status.id) Pubsubhubbub::DistributionWorker.perform_async(status.stream_entry.id) ActivityPub::DistributionWorker.perform_async(status.id) + ActivityPub::ReplyDistributionWorker.perform_async(status.id) if status.reply? && status.thread.account.local? if options[:idempotency].present? redis.setex("idempotency:status:#{account.id}:#{options[:idempotency]}", 3_600, status.id) diff --git a/app/workers/activitypub/reply_distribution_worker.rb b/app/workers/activitypub/reply_distribution_worker.rb new file mode 100644 index 000000000..f9127340f --- /dev/null +++ b/app/workers/activitypub/reply_distribution_worker.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +class ActivityPub::ReplyDistributionWorker + include Sidekiq::Worker + + sidekiq_options queue: 'push' + + def perform(status_id) + @status = Status.find(status_id) + @account = @status.thread.account + + return if skip_distribution? + + ActivityPub::DeliveryWorker.push_bulk(inboxes) do |inbox_url| + [signed_payload, @status.account_id, inbox_url] + end + rescue ActiveRecord::RecordNotFound + true + end + + private + + def skip_distribution? + @status.private_visibility? || @status.direct_visibility? + end + + def inboxes + @inboxes ||= @account.followers.inboxes + end + + def signed_payload + @signed_payload ||= Oj.dump(ActivityPub::LinkedDataSignature.new(payload).sign!(@status.account)) + end + + def payload + @payload ||= ActiveModelSerializers::SerializableResource.new( + @status, + serializer: ActivityPub::ActivitySerializer, + adapter: ActivityPub::Adapter + ).as_json + end +end diff --git a/spec/lib/activitypub/activity/delete_spec.rb b/spec/lib/activitypub/activity/delete_spec.rb index 6601f7262..65e743abb 100644 --- a/spec/lib/activitypub/activity/delete_spec.rb +++ b/spec/lib/activitypub/activity/delete_spec.rb @@ -11,6 +11,7 @@ RSpec.describe ActivityPub::Activity::Delete do type: 'Delete', actor: ActivityPub::TagManager.instance.uri_for(sender), object: ActivityPub::TagManager.instance.uri_for(status), + signature: 'foo', }.with_indifferent_access end -- cgit