Only persist a collection item's approval URI once it is verified (#38255)

This commit is contained in:
David Roetzel
2026-03-17 16:14:30 +01:00
committed by GitHub
parent b9d25bde3e
commit 96c93ba835
8 changed files with 24 additions and 23 deletions

View File

@@ -29,7 +29,7 @@ class CollectionItem < ApplicationRecord
validates :position, numericality: { only_integer: true, greater_than: 0 } validates :position, numericality: { only_integer: true, greater_than: 0 }
validates :activity_uri, presence: true, if: :local_item_with_remote_account? validates :activity_uri, presence: true, if: :local_item_with_remote_account?
validates :approval_uri, presence: true, unless: -> { local? || account&.local? } validates :approval_uri, presence: true, unless: -> { local? || account&.local? || !accepted? }
validates :account, presence: true, if: :accepted? validates :account, presence: true, if: :accepted?
validates :object_uri, presence: true, if: -> { account.nil? } validates :object_uri, presence: true, if: -> { account.nil? }
validates :uri, presence: true, if: :remote_item_with_remote_account? validates :uri, presence: true, if: :remote_item_with_remote_account?

View File

@@ -21,9 +21,9 @@ class ActivityPub::ProcessFeaturedItemService
else else
@collection_item = collection.collection_items.create!( @collection_item = collection.collection_items.create!(
uri: item_json['id'], uri: item_json['id'],
object_uri: item_json['featuredObject'], object_uri: item_json['featuredObject']
approval_uri: item_json['featureAuthorization']
) )
@approval_uri = item_json['featureAuthorization']
verify_authorization! verify_authorization!
end end
@@ -35,8 +35,8 @@ class ActivityPub::ProcessFeaturedItemService
private private
def verify_authorization! def verify_authorization!
ActivityPub::VerifyFeaturedItemService.new.call(@collection_item) ActivityPub::VerifyFeaturedItemService.new.call(@collection_item, @approval_uri)
rescue Mastodon::RecursionLimitExceededError, Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS rescue Mastodon::RecursionLimitExceededError, Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS
ActivityPub::VerifyFeaturedItemWorker.perform_in(rand(30..600).seconds, @collection_item.id) ActivityPub::VerifyFeaturedItemWorker.perform_in(rand(30..600).seconds, @collection_item.id, @approval_uri)
end end
end end

View File

@@ -3,23 +3,23 @@
class ActivityPub::VerifyFeaturedItemService class ActivityPub::VerifyFeaturedItemService
include JsonLdHelper include JsonLdHelper
def call(collection_item) def call(collection_item, approval_uri)
@collection_item = collection_item @collection_item = collection_item
@authorization = fetch_resource(@collection_item.approval_uri, true, raise_on_error: :temporary) @authorization = fetch_resource(approval_uri, true, raise_on_error: :temporary)
if @authorization.nil? if @authorization.nil?
@collection_item.update!(state: :rejected) @collection_item.update!(state: :rejected)
return return
end end
return if non_matching_uri_hosts?(@collection_item.approval_uri, @authorization['interactionTarget']) return if non_matching_uri_hosts?(approval_uri, @authorization['interactionTarget'])
return unless matching_type? && matching_collection_uri? return unless matching_type? && matching_collection_uri?
account = Account.where(uri: @collection_item.object_uri).first account = Account.where(uri: @collection_item.object_uri).first
account ||= ActivityPub::FetchRemoteAccountService.new.call(@collection_item.object_uri) account ||= ActivityPub::FetchRemoteAccountService.new.call(@collection_item.object_uri)
return if account.blank? return if account.blank?
@collection_item.update!(account:, state: :accepted) @collection_item.update!(account:, approval_uri:, state: :accepted)
end end
private private

View File

@@ -7,10 +7,10 @@ class ActivityPub::VerifyFeaturedItemWorker
sidekiq_options queue: 'pull', retry: 5 sidekiq_options queue: 'pull', retry: 5
def perform(collection_item_id) def perform(collection_item_id, approval_uri)
collection_item = CollectionItem.find(collection_item_id) collection_item = CollectionItem.find(collection_item_id)
ActivityPub::VerifyFeaturedItemService.new.call(collection_item) ActivityPub::VerifyFeaturedItemService.new.call(collection_item, approval_uri)
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
# Do nothing # Do nothing
nil nil

View File

@@ -11,6 +11,5 @@ Fabricator(:unverified_remote_collection_item, from: :collection_item) do
account nil account nil
state :pending state :pending
object_uri { Fabricate.build(:remote_account).uri } object_uri { Fabricate.build(:remote_account).uri }
approval_uri { sequence(:uri) { |i| "https://example.com/authorizations/#{i}" } }
uri { sequence(:uri) { |i| "https://example.com/collection_items/#{i}" } } uri { sequence(:uri) { |i| "https://example.com/collection_items/#{i}" } }
end end

View File

@@ -52,7 +52,7 @@ RSpec.describe ActivityPub::ProcessFeaturedItemService do
new_item = collection.collection_items.last new_item = collection.collection_items.last
expect(new_item.object_uri).to eq 'https://example.com/actor/1' expect(new_item.object_uri).to eq 'https://example.com/actor/1'
expect(new_item.approval_uri).to eq 'https://example.com/auth/1' expect(new_item.approval_uri).to be_nil
end end
context 'when an item exists for a local featured account' do context 'when an item exists for a local featured account' do
@@ -93,7 +93,7 @@ RSpec.describe ActivityPub::ProcessFeaturedItemService do
new_item = collection.collection_items.last new_item = collection.collection_items.last
expect(new_item.object_uri).to eq 'https://example.com/actor/1' expect(new_item.object_uri).to eq 'https://example.com/actor/1'
expect(new_item.approval_uri).to eq 'https://example.com/auth/1' expect(new_item.approval_uri).to be_nil
end end
end end
end end

View File

@@ -12,14 +12,14 @@ RSpec.describe ActivityPub::VerifyFeaturedItemService do
account: nil, account: nil,
state: :pending, state: :pending,
uri: 'https://other.example.com/items/1', uri: 'https://other.example.com/items/1',
object_uri: 'https://example.com/actor/1', object_uri: 'https://example.com/actor/1')
approval_uri: verification_json['id'])
end end
let(:approval_uri) { 'https://example.com/auth/1' }
let(:verification_json) do let(:verification_json) do
{ {
'@context' => 'https://www.w3.org/ns/activitystreams', '@context' => 'https://www.w3.org/ns/activitystreams',
'type' => 'FeatureAuthorization', 'type' => 'FeatureAuthorization',
'id' => 'https://example.com/auth/1', 'id' => approval_uri,
'interactionTarget' => 'https://example.com/actor/1', 'interactionTarget' => 'https://example.com/actor/1',
'interactingObject' => collection.uri, 'interactingObject' => collection.uri,
} }
@@ -41,12 +41,13 @@ RSpec.describe ActivityPub::VerifyFeaturedItemService do
before { featured_account } before { featured_account }
it 'verifies and creates the item' do it 'verifies and creates the item' do
subject.call(collection_item) subject.call(collection_item, approval_uri)
expect(verification_request).to have_been_requested expect(verification_request).to have_been_requested
expect(collection_item.account_id).to eq featured_account.id expect(collection_item.account_id).to eq featured_account.id
expect(collection_item).to be_accepted expect(collection_item).to be_accepted
expect(collection_item.approval_uri).to eq approval_uri
end end
end end
@@ -59,13 +60,14 @@ RSpec.describe ActivityPub::VerifyFeaturedItemService do
end end
it 'fetches the actor and creates the item' do it 'fetches the actor and creates the item' do
subject.call(collection_item) subject.call(collection_item, approval_uri)
expect(stubbed_service).to have_received(:call) expect(stubbed_service).to have_received(:call)
expect(verification_request).to have_been_requested expect(verification_request).to have_been_requested
expect(collection_item.account_id).to eq featured_account.id expect(collection_item.account_id).to eq featured_account.id
expect(collection_item).to be_accepted expect(collection_item).to be_accepted
expect(collection_item.approval_uri).to eq approval_uri
end end
end end
end end
@@ -77,7 +79,7 @@ RSpec.describe ActivityPub::VerifyFeaturedItemService do
end end
it 'creates item without attached account and in proper state' do it 'creates item without attached account and in proper state' do
subject.call(collection_item) subject.call(collection_item, approval_uri)
expect(collection_item.account_id).to be_nil expect(collection_item.account_id).to be_nil
expect(collection_item).to be_rejected expect(collection_item).to be_rejected

View File

@@ -12,13 +12,13 @@ RSpec.describe ActivityPub::VerifyFeaturedItemWorker do
before { stub_service } before { stub_service }
it 'sends the status to the service' do it 'sends the status to the service' do
worker.perform(collection_item.id) worker.perform(collection_item.id, 'https://example.com/authorizations/1')
expect(service).to have_received(:call).with(collection_item) expect(service).to have_received(:call).with(collection_item, 'https://example.com/authorizations/1')
end end
it 'returns nil for non-existent record' do it 'returns nil for non-existent record' do
result = worker.perform(123_123_123) result = worker.perform(123_123_123, 'https://example.com/authorizations/1')
expect(result).to be_nil expect(result).to be_nil
end end