diff --git a/app/controllers/activitypub/references_controller.rb b/app/controllers/activitypub/references_controller.rb new file mode 100644 index 0000000000..7cd903eaf4 --- /dev/null +++ b/app/controllers/activitypub/references_controller.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +class ActivityPub::ReferencesController < ActivityPub::BaseController + include SignatureVerification + include Authorization + include AccountOwnedConcern + + REFERENCES_LIMIT = 5 + + before_action :require_signature!, if: :authorized_fetch_mode? + before_action :set_status + + def index + expires_in 0, public: public_fetch_mode? + render json: references_collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json', skip_activities: true + end + + private + + def pundit_user + signed_request_account + end + + def set_status + @status = @account.statuses.find(params[:status_id]) + authorize @status, :show? + rescue Mastodon::NotPermittedError + not_found + end + + def load_statuses + cached_references + end + + def cached_references + cache_collection(Status.where(id: results).reorder(:id), Status) + end + + def results + @results ||= begin + references = @status.reference_objects.order(target_status_id: :asc) + references = references.where('target_status_id > ?', page_params[:min_id]) if page_params[:min_id].present? + references = references.limit(limit_param(REFERENCES_LIMIT)) + references.pluck(:target_status_id) + end + end + + def pagination_min_id + results.last + end + + def records_continue? + results.size == limit_param(REFERENCES_LIMIT) + end + + def references_collection_presenter + page = ActivityPub::CollectionPresenter.new( + id: ActivityPub::TagManager.instance.references_uri_for(@status, page_params), + type: :unordered, + part_of: ActivityPub::TagManager.instance.references_uri_for(@status), + items: load_statuses.map(&:uri), + next: next_page + ) + + return page if page_requested? + + ActivityPub::CollectionPresenter.new( + type: :unordered, + id: ActivityPub::TagManager.instance.references_uri_for(@status), + first: page + ) + end + + def page_requested? + truthy_param?(:page) + end + + def next_page + return unless records_continue? + + ActivityPub::TagManager.instance.references_uri_for(@status, page_params.merge(min_id: pagination_min_id)) + end + + def page_params + params_slice(:min_id, :limit).merge(page: true) + end +end diff --git a/app/helpers/context_helper.rb b/app/helpers/context_helper.rb index 7d982d8a40..aa9636db9e 100644 --- a/app/helpers/context_helper.rb +++ b/app/helpers/context_helper.rb @@ -25,6 +25,7 @@ module ContextHelper searchable_by: { 'fedibird' => 'http://fedibird.com/ns#', 'searchableBy' => { '@id' => 'fedibird:searchableBy', '@type' => '@id' } }, subscribable_by: { 'kmyblue' => 'http://kmy.blue/ns#', 'subscribableBy' => { '@id' => 'kmyblue:subscribableBy', '@type' => '@id' } }, other_setting: { 'fedibird' => 'http://fedibird.com/ns#', 'otherSetting' => 'fedibird:otherSetting' }, + references: { 'fedibird' => 'http://fedibird.com/ns#', 'references' => { '@id' => 'fedibird:references', '@type' => '@id' } }, olm: { 'toot' => 'http://joinmastodon.org/ns#', 'Device' => 'toot:Device', 'Ed25519Signature' => 'toot:Ed25519Signature', 'Ed25519Key' => 'toot:Ed25519Key', 'Curve25519Key' => 'toot:Curve25519Key', 'EncryptedMessage' => 'toot:EncryptedMessage', 'publicKeyBase64' => 'toot:publicKeyBase64', 'deviceId' => 'toot:deviceId', 'claim' => { '@type' => '@id', '@id' => 'toot:claim' }, 'fingerprintKey' => { '@type' => '@id', '@id' => 'toot:fingerprintKey' }, 'identityKey' => { '@type' => '@id', '@id' => 'toot:identityKey' }, 'devices' => { '@type' => '@id', '@id' => 'toot:devices' }, 'messageFranking' => 'toot:messageFranking', 'messageType' => 'toot:messageType', 'cipherText' => 'toot:cipherText' }, suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' }, }.freeze diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index b7ca0927c8..b9be271c53 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -473,7 +473,9 @@ class ActivityPub::Activity::Create < ActivityPub::Activity end def process_references! - references = @json['references'].nil? ? [] : ActivityPub::FetchReferencesService(@json['references']) + references = @json['references'].nil? ? [] : ActivityPub::FetchReferencesService.new.call(@json['references']) + quote = @json['quote'] || @json['quoteUrl'] || @json['quoteURL'] || @json['_misskey_quote'] + references << quote if quote ProcessReferencesWorker.perform_async(@status.id, [], references) end diff --git a/app/lib/activitypub/tag_manager.rb b/app/lib/activitypub/tag_manager.rb index e28a9c30b2..d971b44237 100644 --- a/app/lib/activitypub/tag_manager.rb +++ b/app/lib/activitypub/tag_manager.rb @@ -78,6 +78,12 @@ class ActivityPub::TagManager account_status_replies_url(target.account, target, page_params) end + def references_uri_for(target, page_params = nil) + raise ArgumentError, 'target must be a local activity' unless %i(note comment activity).include?(target.object_type) && target.local? + + account_status_references_url(target.account, target, page_params) + end + def followers_uri_for(target) target.local? ? account_followers_url(target) : target.followers_url.presence end diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb index e07eff2bf3..6ed9154e97 100644 --- a/app/serializers/activitypub/note_serializer.rb +++ b/app/serializers/activitypub/note_serializer.rb @@ -3,13 +3,13 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer include FormattingHelper - context_extensions :atom_uri, :conversation, :sensitive, :voters_count, :searchable_by + context_extensions :atom_uri, :conversation, :sensitive, :voters_count, :searchable_by, :references attributes :id, :type, :summary, :in_reply_to, :published, :url, :attributed_to, :to, :cc, :sensitive, :atom_uri, :in_reply_to_atom_uri, - :conversation, :searchable_by + :conversation, :searchable_by, :references attribute :content attribute :content_map, if: :language? @@ -64,6 +64,10 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer ) end + def references + ActivityPub::TagManager.instance.references_uri_for(object) + end + def language? object.language.present? end diff --git a/app/services/activitypub/fetch_references_service.rb b/app/services/activitypub/fetch_references_service.rb index d6537e7467..6ee0dc4f94 100644 --- a/app/services/activitypub/fetch_references_service.rb +++ b/app/services/activitypub/fetch_references_service.rb @@ -32,7 +32,7 @@ class ActivityPub::FetchReferencesService < BaseService all_items.concat(items) - break if all_items.size >= StatusReferenceValidator::LIMIT + break if all_items.size >= 5 collection = collection['next'].present? ? fetch_collection(collection['next']) : nil end @@ -42,7 +42,8 @@ class ActivityPub::FetchReferencesService < BaseService def fetch_collection(collection_or_uri) return collection_or_uri if collection_or_uri.is_a?(Hash) - return if invalid_origin?(collection_or_uri) + return if unsupported_uri_scheme?(collection_or_uri) + return if ActivityPub::TagManager.instance.local_uri?(collection_or_uri) fetch_resource_without_id_validation(collection_or_uri, nil, true) end diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index fd07ffc242..2e7aaa7ee6 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -45,6 +45,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService create_edits! end + update_references! download_media_files! queue_poll_notifications! @@ -240,6 +241,13 @@ class ActivityPub::ProcessStatusUpdateService < BaseService end end + def update_references! + references = @json['references'].nil? ? [] : ActivityPub::FetchReferencesService.new.call(@json['references']) + quote = @json['quote'] || @json['quoteUrl'] || @json['quoteURL'] || @json['_misskey_quote'] + references << quote if quote + ProcessReferencesWorker.perform_async(@status.id, [], references) + end + def expected_type? equals_or_includes_any?(@json['type'], %w(Note Question)) end diff --git a/config/routes.rb b/config/routes.rb index e69f964211..d0e9e3b804 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -99,6 +99,7 @@ Rails.application.routes.draw do end resources :replies, only: [:index], module: :activitypub + resources :references, only: [:index], module: :activitypub end resources :followers, only: [:index], controller: :follower_accounts