From a186bad399fa7a3ad8347defc2d7ae688bc10d73 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 7 Oct 2025 15:14:38 +0200 Subject: [PATCH] Fix `"quote": { "type": "Tombstone" }` being ignored --- app/lib/activitypub/activity/create.rb | 2 +- app/lib/activitypub/parser/status_parser.rb | 4 ++ .../process_status_update_service.rb | 2 +- spec/lib/activitypub/activity/create_spec.rb | 24 ++++++++++++ .../process_status_update_service_spec.rb | 38 +++++++++++++++++++ 5 files changed, 68 insertions(+), 2 deletions(-) diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index c247e7025..10e3ecc57 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -217,7 +217,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity def process_quote @quote_uri = @status_parser.quote_uri - return if @quote_uri.blank? + return unless @status_parser.quote? approval_uri = @status_parser.quote_approval_uri approval_uri = nil if unsupported_uri_scheme?(approval_uri) diff --git a/app/lib/activitypub/parser/status_parser.rb b/app/lib/activitypub/parser/status_parser.rb index fecfd0e34..9db1cb572 100644 --- a/app/lib/activitypub/parser/status_parser.rb +++ b/app/lib/activitypub/parser/status_parser.rb @@ -117,6 +117,10 @@ class ActivityPub::Parser::StatusParser flags end + def quote? + %w(quote _misskey_quote quoteUrl quoteUri).any? { |key| @object[key].present? } + end + def quote_uri %w(quote _misskey_quote quoteUrl quoteUri).filter_map do |key| value_or_id(as_array(@object[key]).first) diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index b186719c9..d29e08cdd 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -300,7 +300,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService def update_quote! quote_uri = @status_parser.quote_uri - if quote_uri.present? + if @status_parser.quote? approval_uri = @status_parser.quote_approval_uri approval_uri = nil if unsupported_uri_scheme?(approval_uri) diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb index 7ce9ee577..1fa04dec0 100644 --- a/spec/lib/activitypub/activity/create_spec.rb +++ b/spec/lib/activitypub/activity/create_spec.rb @@ -938,6 +938,30 @@ RSpec.describe ActivityPub::Activity::Create do end end + context 'with an unverifiable quote of a dead post' do + let(:quoted_status) { Fabricate(:status) } + + let(:object_json) do + build_object( + type: 'Note', + content: 'woah what she said is amazing', + quote: { type: 'Tombstone' } + ) + end + + it 'creates a status with an unverified quote' do + expect { subject.perform }.to change(sender.statuses, :count).by(1) + + status = sender.statuses.first + expect(status).to_not be_nil + expect(status.quote).to_not be_nil + expect(status.quote).to have_attributes( + state: 'pending', + approval_uri: nil + ) + end + end + context 'with an unverifiable unknown post' do let(:unknown_post_uri) { 'https://unavailable.example.com/unavailable-post' } diff --git a/spec/services/activitypub/process_status_update_service_spec.rb b/spec/services/activitypub/process_status_update_service_spec.rb index f99f2ecb3..723f1343b 100644 --- a/spec/services/activitypub/process_status_update_service_spec.rb +++ b/spec/services/activitypub/process_status_update_service_spec.rb @@ -976,6 +976,44 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do end end + context 'when the status swaps a verified quote with an ID-less Tombstone through an explicit update' do + let(:quoted_account) { Fabricate(:account, domain: 'quoted.example.com') } + let(:quoted_status) { Fabricate(:status, account: quoted_account) } + let(:second_quoted_status) { Fabricate(:status, account: quoted_account) } + let!(:quote) { Fabricate(:quote, status: status, quoted_status: quoted_status, approval_uri: approval_uri, state: :accepted) } + let(:approval_uri) { 'https://quoted.example.com/approvals/1' } + + let(:payload) do + { + '@context': [ + 'https://www.w3.org/ns/activitystreams', + { + '@id': 'https://w3id.org/fep/044f#quote', + '@type': '@id', + }, + { + '@id': 'https://w3id.org/fep/044f#quoteAuthorization', + '@type': '@id', + }, + ], + id: 'foo', + type: 'Note', + summary: 'Show more', + content: 'Hello universe', + updated: '2021-09-08T22:39:25Z', + quote: { type: 'Tombstone' }, + } + end + + it 'updates the URI and unverifies the quote' do + expect { subject.call(status, json, json) } + .to change { status.quote.quoted_status }.from(quoted_status).to(nil) + .and change { status.quote.state }.from('accepted') + + expect { quote.reload }.to raise_error(ActiveRecord::RecordNotFound) + end + end + context 'when the status swaps a verified quote with another verifiable quote through an explicit update' do let(:quoted_account) { Fabricate(:account, domain: 'quoted.example.com') } let(:second_quoted_account) { Fabricate(:account, domain: 'second-quoted.example.com') }