Add initial support for ingesting and verifying remote quote posts (#34370)
This commit is contained in:
		
							
								
								
									
										246
									
								
								spec/services/activitypub/verify_quote_service_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										246
									
								
								spec/services/activitypub/verify_quote_service_spec.rb
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,246 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'rails_helper'
 | 
			
		||||
 | 
			
		||||
RSpec.describe ActivityPub::VerifyQuoteService do
 | 
			
		||||
  subject { described_class.new }
 | 
			
		||||
 | 
			
		||||
  let(:account) { Fabricate(:account, domain: 'a.example.com') }
 | 
			
		||||
  let(:quoted_account) { Fabricate(:account, domain: 'b.example.com') }
 | 
			
		||||
  let(:quoted_status) { Fabricate(:status, account: quoted_account) }
 | 
			
		||||
  let(:status) { Fabricate(:status, account: account) }
 | 
			
		||||
  let(:quote) { Fabricate(:quote, status: status, quoted_status: quoted_status, approval_uri: approval_uri) }
 | 
			
		||||
 | 
			
		||||
  context 'with an unfetchable approval URI' do
 | 
			
		||||
    let(:approval_uri) { 'https://b.example.com/approvals/1234' }
 | 
			
		||||
 | 
			
		||||
    before do
 | 
			
		||||
      stub_request(:get, approval_uri)
 | 
			
		||||
        .to_return(status: 404)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'with an already-fetched post' do
 | 
			
		||||
      it 'does not update the status' do
 | 
			
		||||
        expect { subject.call(quote) }
 | 
			
		||||
          .to change(quote, :state).to('rejected')
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'with an already-verified quote' do
 | 
			
		||||
      let(:quote) { Fabricate(:quote, status: status, quoted_status: quoted_status, approval_uri: approval_uri, state: :accepted) }
 | 
			
		||||
 | 
			
		||||
      it 'rejects the quote' do
 | 
			
		||||
        expect { subject.call(quote) }
 | 
			
		||||
          .to change(quote, :state).to('revoked')
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  context 'with an approval URI' do
 | 
			
		||||
    let(:approval_uri) { 'https://b.example.com/approvals/1234' }
 | 
			
		||||
 | 
			
		||||
    let(:approval_type) { 'QuoteAuthorization' }
 | 
			
		||||
    let(:approval_id) { approval_uri }
 | 
			
		||||
    let(:approval_attributed_to) { ActivityPub::TagManager.instance.uri_for(quoted_account) }
 | 
			
		||||
    let(:approval_interacting_object) { ActivityPub::TagManager.instance.uri_for(status) }
 | 
			
		||||
    let(:approval_interaction_target) { ActivityPub::TagManager.instance.uri_for(quoted_status) }
 | 
			
		||||
 | 
			
		||||
    let(:json) do
 | 
			
		||||
      {
 | 
			
		||||
        '@context': [
 | 
			
		||||
          'https://www.w3.org/ns/activitystreams',
 | 
			
		||||
          {
 | 
			
		||||
            toot: 'http://joinmastodon.org/ns#',
 | 
			
		||||
            QuoteAuthorization: 'toot:QuoteAuthorization',
 | 
			
		||||
            gts: 'https://gotosocial.org/ns#',
 | 
			
		||||
            interactionPolicy: {
 | 
			
		||||
              '@id': 'gts:interactionPolicy',
 | 
			
		||||
              '@type': '@id',
 | 
			
		||||
            },
 | 
			
		||||
            interactingObject: {
 | 
			
		||||
              '@id': 'gts:interactingObject',
 | 
			
		||||
              '@type': '@id',
 | 
			
		||||
            },
 | 
			
		||||
            interactionTarget: {
 | 
			
		||||
              '@id': 'gts:interactionTarget',
 | 
			
		||||
              '@type': '@id',
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
        type: approval_type,
 | 
			
		||||
        id: approval_id,
 | 
			
		||||
        attributedTo: approval_attributed_to,
 | 
			
		||||
        interactingObject: approval_interacting_object,
 | 
			
		||||
        interactionTarget: approval_interaction_target,
 | 
			
		||||
      }.with_indifferent_access
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    before do
 | 
			
		||||
      stub_request(:get, approval_uri)
 | 
			
		||||
        .to_return(status: 200, body: Oj.dump(json), headers: { 'Content-Type': 'application/activity+json' })
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'with a valid activity for already-fetched posts' do
 | 
			
		||||
      it 'updates the status' do
 | 
			
		||||
        expect { subject.call(quote) }
 | 
			
		||||
          .to change(quote, :state).to('accepted')
 | 
			
		||||
 | 
			
		||||
        expect(a_request(:get, approval_uri))
 | 
			
		||||
          .to have_been_made.once
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'with a valid activity for a post that cannot be fetched but is inlined' do
 | 
			
		||||
      let(:quoted_status) { nil }
 | 
			
		||||
 | 
			
		||||
      let(:approval_interaction_target) do
 | 
			
		||||
        {
 | 
			
		||||
          type: 'Note',
 | 
			
		||||
          id: 'https://b.example.com/unknown-quoted',
 | 
			
		||||
          to: 'https://www.w3.org/ns/activitystreams#Public',
 | 
			
		||||
          attributedTo: ActivityPub::TagManager.instance.uri_for(quoted_account),
 | 
			
		||||
          content: 'previously unknown post',
 | 
			
		||||
        }
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      before do
 | 
			
		||||
        stub_request(:get, 'https://b.example.com/unknown-quoted')
 | 
			
		||||
          .to_return(status: 404)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it 'updates the status' do
 | 
			
		||||
        expect { subject.call(quote, fetchable_quoted_uri: 'https://b.example.com/unknown-quoted') }
 | 
			
		||||
          .to change(quote, :state).to('accepted')
 | 
			
		||||
 | 
			
		||||
        expect(a_request(:get, approval_uri))
 | 
			
		||||
          .to have_been_made.once
 | 
			
		||||
 | 
			
		||||
        expect(quote.reload.quoted_status.content).to eq 'previously unknown post'
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'with a valid activity for a post that cannot be fetched and is inlined from an untrusted source' do
 | 
			
		||||
      let(:quoted_status) { nil }
 | 
			
		||||
 | 
			
		||||
      let(:approval_interaction_target) do
 | 
			
		||||
        {
 | 
			
		||||
          type: 'Note',
 | 
			
		||||
          id: 'https://example.com/unknown-quoted',
 | 
			
		||||
          to: 'https://www.w3.org/ns/activitystreams#Public',
 | 
			
		||||
          attributedTo: ActivityPub::TagManager.instance.uri_for(account),
 | 
			
		||||
          content: 'previously unknown post',
 | 
			
		||||
        }
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      before do
 | 
			
		||||
        stub_request(:get, 'https://example.com/unknown-quoted')
 | 
			
		||||
          .to_return(status: 404)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it 'does not update the status' do
 | 
			
		||||
        expect { subject.call(quote, fetchable_quoted_uri: 'https://example.com/unknown-quoted') }
 | 
			
		||||
          .to not_change(quote, :state)
 | 
			
		||||
          .and not_change(quote, :quoted_status)
 | 
			
		||||
 | 
			
		||||
        expect(a_request(:get, approval_uri))
 | 
			
		||||
          .to have_been_made.once
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'with a valid activity for already-fetched posts, with a pre-fetched approval' do
 | 
			
		||||
      it 'updates the status without fetching the activity' do
 | 
			
		||||
        expect { subject.call(quote, prefetched_body: Oj.dump(json)) }
 | 
			
		||||
          .to change(quote, :state).to('accepted')
 | 
			
		||||
 | 
			
		||||
        expect(a_request(:get, approval_uri))
 | 
			
		||||
          .to_not have_been_made
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'with an unverifiable approval' do
 | 
			
		||||
      let(:approval_uri) { 'https://evil.com/approvals/1234' }
 | 
			
		||||
 | 
			
		||||
      it 'does not update the status' do
 | 
			
		||||
        expect { subject.call(quote) }
 | 
			
		||||
          .to_not change(quote, :state)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'with an invalid approval document because of a mismatched ID' do
 | 
			
		||||
      let(:approval_id) { 'https://evil.com/approvals/1234' }
 | 
			
		||||
 | 
			
		||||
      it 'does not accept the quote' do
 | 
			
		||||
        # NOTE: maybe we want to skip that instead of rejecting it?
 | 
			
		||||
        expect { subject.call(quote) }
 | 
			
		||||
          .to change(quote, :state).to('rejected')
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'with an approval from the wrong account' do
 | 
			
		||||
      let(:approval_attributed_to) { ActivityPub::TagManager.instance.uri_for(Fabricate(:account, domain: 'b.example.com')) }
 | 
			
		||||
 | 
			
		||||
      it 'does not update the status' do
 | 
			
		||||
        expect { subject.call(quote) }
 | 
			
		||||
          .to_not change(quote, :state)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'with an approval for the wrong quoted post' do
 | 
			
		||||
      let(:approval_interaction_target) { ActivityPub::TagManager.instance.uri_for(Fabricate(:status, account: quoted_account)) }
 | 
			
		||||
 | 
			
		||||
      it 'does not update the status' do
 | 
			
		||||
        expect { subject.call(quote) }
 | 
			
		||||
          .to_not change(quote, :state)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'with an approval for the wrong quote post' do
 | 
			
		||||
      let(:approval_interacting_object) { ActivityPub::TagManager.instance.uri_for(Fabricate(:status, account: account)) }
 | 
			
		||||
 | 
			
		||||
      it 'does not update the status' do
 | 
			
		||||
        expect { subject.call(quote) }
 | 
			
		||||
          .to_not change(quote, :state)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'with an approval of the wrong type' do
 | 
			
		||||
      let(:approval_type) { 'ReplyAuthorization' }
 | 
			
		||||
 | 
			
		||||
      it 'does not update the status' do
 | 
			
		||||
        expect { subject.call(quote) }
 | 
			
		||||
          .to_not change(quote, :state)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  context 'with fast-track authorizations' do
 | 
			
		||||
    let(:approval_uri) { nil }
 | 
			
		||||
 | 
			
		||||
    context 'without any fast-track condition' do
 | 
			
		||||
      it 'does not update the status' do
 | 
			
		||||
        expect { subject.call(quote) }
 | 
			
		||||
          .to_not change(quote, :state)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'when the account and the quoted account are the same' do
 | 
			
		||||
      let(:quoted_account) { account }
 | 
			
		||||
 | 
			
		||||
      it 'updates the status' do
 | 
			
		||||
        expect { subject.call(quote) }
 | 
			
		||||
          .to change(quote, :state).to('accepted')
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'when the account is mentioned by the quoted post' do
 | 
			
		||||
      before do
 | 
			
		||||
        quoted_status.mentions << Mention.new(account: account)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it 'updates the status' do
 | 
			
		||||
        expect { subject.call(quote) }
 | 
			
		||||
          .to change(quote, :state).to('accepted')
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
		Reference in New Issue
	
	Block a user