From 3134691948aeacb16b7386ed77bbea4581beec40 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 8 Nov 2020 00:28:39 +0100 Subject: Add support for reversible suspensions through ActivityPub (#14989) --- .../activitypub/process_account_service_spec.rb | 80 ++++++++++++++++++++++ .../activitypub/process_collection_service_spec.rb | 43 +++++++++++- spec/services/resolve_account_service_spec.rb | 29 +++++++- 3 files changed, 149 insertions(+), 3 deletions(-) (limited to 'spec/services') diff --git a/spec/services/activitypub/process_account_service_spec.rb b/spec/services/activitypub/process_account_service_spec.rb index 5141e3f16..56e7f8321 100644 --- a/spec/services/activitypub/process_account_service_spec.rb +++ b/spec/services/activitypub/process_account_service_spec.rb @@ -73,4 +73,84 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do expect(ProofProvider::Keybase::Worker).to have_received(:perform_async) end end + + context 'when account is not suspended' do + let!(:account) { Fabricate(:account, username: 'alice', domain: 'example.com') } + + let(:payload) do + { + id: 'https://foo.test', + type: 'Actor', + inbox: 'https://foo.test/inbox', + suspended: true, + }.with_indifferent_access + end + + before do + allow(Admin::SuspensionWorker).to receive(:perform_async) + end + + subject { described_class.new.call('alice', 'example.com', payload) } + + it 'suspends account remotely' do + expect(subject.suspended?).to be true + expect(subject.suspension_origin_remote?).to be true + end + + it 'queues suspension worker' do + subject + expect(Admin::SuspensionWorker).to have_received(:perform_async) + end + end + + context 'when account is suspended' do + let!(:account) { Fabricate(:account, username: 'alice', domain: 'example.com', display_name: '') } + + let(:payload) do + { + id: 'https://foo.test', + type: 'Actor', + inbox: 'https://foo.test/inbox', + suspended: false, + name: 'Hoge', + }.with_indifferent_access + end + + before do + allow(Admin::UnsuspensionWorker).to receive(:perform_async) + + account.suspend!(origin: suspension_origin) + end + + subject { described_class.new.call('alice', 'example.com', payload) } + + context 'locally' do + let(:suspension_origin) { :local } + + it 'does not unsuspend it' do + expect(subject.suspended?).to be true + end + + it 'does not update any attributes' do + expect(subject.display_name).to_not eq 'Hoge' + end + end + + context 'remotely' do + let(:suspension_origin) { :remote } + + it 'unsuspends it' do + expect(subject.suspended?).to be false + end + + it 'queues unsuspension worker' do + subject + expect(Admin::UnsuspensionWorker).to have_received(:perform_async) + end + + it 'updates attributes' do + expect(subject.display_name).to eq 'Hoge' + end + end + end end diff --git a/spec/services/activitypub/process_collection_service_spec.rb b/spec/services/activitypub/process_collection_service_spec.rb index b3baf6b6b..00d71a86a 100644 --- a/spec/services/activitypub/process_collection_service_spec.rb +++ b/spec/services/activitypub/process_collection_service_spec.rb @@ -22,7 +22,48 @@ RSpec.describe ActivityPub::ProcessCollectionService, type: :service do subject { described_class.new } describe '#call' do - context 'when actor is the sender' + context 'when actor is suspended' do + before do + actor.suspend!(origin: :remote) + end + + %w(Accept Add Announce Block Create Flag Follow Like Move Remove).each do |activity_type| + context "with #{activity_type} activity" do + let(:payload) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: 'foo', + type: activity_type, + actor: ActivityPub::TagManager.instance.uri_for(actor), + } + end + + it 'does not process payload' do + expect(ActivityPub::Activity).not_to receive(:factory) + subject.call(json, actor) + end + end + end + + %w(Delete Reject Undo Update).each do |activity_type| + context "with #{activity_type} activity" do + let(:payload) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: 'foo', + type: activity_type, + actor: ActivityPub::TagManager.instance.uri_for(actor), + } + end + + it 'processes the payload' do + expect(ActivityPub::Activity).to receive(:factory) + subject.call(json, actor) + end + end + end + end + context 'when actor differs from sender' do let(:forwarder) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/other_account') } diff --git a/spec/services/resolve_account_service_spec.rb b/spec/services/resolve_account_service_spec.rb index cea942e39..76cb9ed8d 100644 --- a/spec/services/resolve_account_service_spec.rb +++ b/spec/services/resolve_account_service_spec.rb @@ -13,16 +13,41 @@ RSpec.describe ResolveAccountService, type: :service do stub_request(:get, "https://ap.example.com/users/foo").to_return(request_fixture('activitypub-actor.txt')) stub_request(:get, "https://ap.example.com/users/foo.atom").to_return(request_fixture('activitypub-feed.txt')) stub_request(:get, %r{https://ap.example.com/users/foo/\w+}).to_return(status: 404) + stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:hoge@example.com').to_return(status: 410) end - it 'raises error if no such user can be resolved via webfinger' do + it 'returns nil if no such user can be resolved via webfinger' do expect(subject.call('catsrgr8@quitter.no')).to be_nil end - it 'raises error if the domain does not have webfinger' do + it 'returns nil if the domain does not have webfinger' do expect(subject.call('catsrgr8@example.com')).to be_nil end + context 'when webfinger returns http gone' do + context 'for a previously known account' do + before do + Fabricate(:account, username: 'hoge', domain: 'example.com', last_webfingered_at: nil) + allow(AccountDeletionWorker).to receive(:perform_async) + end + + it 'returns nil' do + expect(subject.call('hoge@example.com')).to be_nil + end + + it 'queues account deletion worker' do + subject.call('hoge@example.com') + expect(AccountDeletionWorker).to have_received(:perform_async) + end + end + + context 'for a previously unknown account' do + it 'returns nil' do + expect(subject.call('hoge@example.com')).to be_nil + end + end + end + context 'with an ActivityPub account' do it 'returns new remote account' do account = subject.call('foo@ap.example.com') -- cgit From 6c05db70faed7dbe075a04129fe26793a241124a Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Mon, 21 Sep 2020 18:37:59 +0200 Subject: Add tests --- spec/services/resolve_url_service_spec.rb | 97 +++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) (limited to 'spec/services') diff --git a/spec/services/resolve_url_service_spec.rb b/spec/services/resolve_url_service_spec.rb index aa4204637..a38b23590 100644 --- a/spec/services/resolve_url_service_spec.rb +++ b/spec/services/resolve_url_service_spec.rb @@ -15,5 +15,102 @@ describe ResolveURLService, type: :service do expect(subject.call(url)).to be_nil end + + context 'searching for a remote private status' do + let(:account) { Fabricate(:account) } + let(:poster) { Fabricate(:account, domain: 'example.com') } + let(:url) { 'https://example.com/@foo/42' } + let(:uri) { 'https://example.com/users/foo/statuses/42' } + let!(:status) { Fabricate(:status, url: url, uri: uri, account: poster, visibility: :private) } + + before do + stub_request(:get, url).to_return(status: 404) if url.present? + stub_request(:get, uri).to_return(status: 404) + end + + context 'when the account follows the poster' do + before do + account.follow!(poster) + end + + context 'when the status uses Mastodon-style URLs' do + let(:url) { 'https://example.com/@foo/42' } + let(:uri) { 'https://example.com/users/foo/statuses/42' } + + it 'returns status by url' do + expect(subject.call(url, on_behalf_of: account)).to eq(status) + end + + it 'returns status by uri' do + expect(subject.call(uri, on_behalf_of: account)).to eq(status) + end + end + + context 'when the status uses pleroma-style URLs' do + let(:url) { nil } + let(:uri) { 'https://example.com/objects/0123-456-789-abc-def' } + + it 'returns status by uri' do + expect(subject.call(uri, on_behalf_of: account)).to eq(status) + end + end + end + + context 'when the account does not follow the poster' do + context 'when the status uses Mastodon-style URLs' do + let(:url) { 'https://example.com/@foo/42' } + let(:uri) { 'https://example.com/users/foo/statuses/42' } + + it 'does not return the status by url' do + expect(subject.call(url, on_behalf_of: account)).to be_nil + end + + it 'does not return the status by uri' do + expect(subject.call(uri, on_behalf_of: account)).to be_nil + end + end + + context 'when the status uses pleroma-style URLs' do + let(:url) { nil } + let(:uri) { 'https://example.com/objects/0123-456-789-abc-def' } + + it 'returns status by uri' do + expect(subject.call(uri, on_behalf_of: account)).to be_nil + end + end + end + end + + context 'searching for a local private status' do + let(:account) { Fabricate(:account) } + let(:poster) { Fabricate(:account) } + let!(:status) { Fabricate(:status, account: poster, visibility: :private) } + let(:url) { ActivityPub::TagManager.instance.url_for(status) } + let(:uri) { ActivityPub::TagManager.instance.uri_for(status) } + + context 'when the account follows the poster' do + before do + account.follow!(poster) + end + + it 'returns status by url' do + expect(subject.call(url, on_behalf_of: account)).to eq(status) + end + + it 'returns status by uri' do + expect(subject.call(uri, on_behalf_of: account)).to eq(status) + end + end + + context 'when the account does not follow the poster' do + it 'does not return the status by url' do + expect(subject.call(url, on_behalf_of: account)).to be_nil + end + + it 'does not return the status by uri' do + expect(subject.call(uri, on_behalf_of: account)).to be_nil + end + end + end end end -- cgit