From ccf6f16f053944146bf356ec6011cec4b4a1647a Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Fri, 20 Mar 2026 10:51:20 +0100 Subject: [PATCH] Handle `Delete` of a `FeatureAuthorization` (#38292) --- app/lib/activitypub/activity/delete.rb | 20 ++++++++++++---- .../delete_collection_item_service.rb | 5 ++-- spec/lib/activitypub/activity/delete_spec.rb | 23 +++++++++++++++++++ .../delete_collection_item_service_spec.rb | 8 +++++++ 4 files changed, 49 insertions(+), 7 deletions(-) diff --git a/app/lib/activitypub/activity/delete.rb b/app/lib/activitypub/activity/delete.rb index f606d9520f0..62c298a6389 100644 --- a/app/lib/activitypub/activity/delete.rb +++ b/app/lib/activitypub/activity/delete.rb @@ -2,11 +2,10 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity def perform - if @account.uri == object_uri - delete_person - else - delete_object - end + return delete_person if @account.uri == object_uri + return delete_feature_authorization! unless !Mastodon::Feature.collections_federation_enabled? || feature_authorization_from_object.nil? + + delete_object end private @@ -66,7 +65,18 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity DistributionWorker.perform_async(@quote.status_id, { 'update' => true }) if @quote.status.present? end + def delete_feature_authorization! + collection_item = feature_authorization_from_object + DeleteCollectionItemService.new.call(collection_item, revoke: true) + end + def forwarder @forwarder ||= ActivityPub::Forwarder.new(@account, @json, @status) end + + def feature_authorization_from_object + return @collection_item if instance_variable_defined?(:@collection_item) + + @collection_item = CollectionItem.local.find_by(approval_uri: value_or_id(@object), account_id: @account.id) + end end diff --git a/app/services/delete_collection_item_service.rb b/app/services/delete_collection_item_service.rb index 23f539a6850..47df001d60b 100644 --- a/app/services/delete_collection_item_service.rb +++ b/app/services/delete_collection_item_service.rb @@ -1,10 +1,11 @@ # frozen_string_literal: true class DeleteCollectionItemService - def call(collection_item) + def call(collection_item, revoke: false) @collection_item = collection_item @collection = collection_item.collection - @collection_item.destroy! + + revoke ? @collection_item.revoke! : @collection_item.destroy! distribute_remove_activity if Mastodon::Feature.collections_federation_enabled? end diff --git a/spec/lib/activitypub/activity/delete_spec.rb b/spec/lib/activitypub/activity/delete_spec.rb index 48d2946b941..c2b3a63431d 100644 --- a/spec/lib/activitypub/activity/delete_spec.rb +++ b/spec/lib/activitypub/activity/delete_spec.rb @@ -119,5 +119,28 @@ RSpec.describe ActivityPub::Activity::Delete do .to change { quote.reload.state }.to('revoked') end end + + context 'with a FeatureAuthorization', feature: :collections_federation do + let(:recipient) { Fabricate(:account) } + let(:approval_uri) { 'https://example.com/authorizations/1' } + let(:collection) { Fabricate(:collection, account: recipient) } + let!(:collection_item) { Fabricate(:collection_item, collection:, account: sender, state: :accepted, approval_uri:) } + let(:json) do + { + 'id' => 'https://example.com/accepts/1', + 'type' => 'Delete', + 'actor' => sender.uri, + 'to' => ActivityPub::TagManager.instance.uri_for(recipient), + 'object' => approval_uri, + } + end + + it 'revokes the collection item and federates a `Delete` activity' do + subject.perform + + expect(collection_item.reload).to be_revoked + expect(ActivityPub::AccountRawDistributionWorker).to have_enqueued_sidekiq_job + end + end end end diff --git a/spec/services/delete_collection_item_service_spec.rb b/spec/services/delete_collection_item_service_spec.rb index 099671e8fc5..bdd37ad4a82 100644 --- a/spec/services/delete_collection_item_service_spec.rb +++ b/spec/services/delete_collection_item_service_spec.rb @@ -18,5 +18,13 @@ RSpec.describe DeleteCollectionItemService do expect(ActivityPub::AccountRawDistributionWorker).to have_enqueued_sidekiq_job end + + context 'when `revoke` is set to true' do + it 'revokes the collection item' do + subject.call(collection_item, revoke: true) + + expect(collection_item.reload).to be_revoked + end + end end end