mirror of
https://github.com/mastodon/mastodon.git
synced 2025-05-07 12:16:14 +00:00
Compare commits
5 Commits
17854b5f1d
...
cff91ec86b
Author | SHA1 | Date | |
---|---|---|---|
![]() |
cff91ec86b | ||
![]() |
8b34daf254 | ||
![]() |
a77d0fe7b8 | ||
![]() |
7ae47b974b | ||
![]() |
c2ba6b10d7 |
|
@ -72,6 +72,18 @@ module JsonLdHelper
|
||||||
!haystack.casecmp(needle).zero?
|
!haystack.casecmp(needle).zero?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def safe_prefetched_embed(account, object, context)
|
||||||
|
return unless object.is_a?(Hash)
|
||||||
|
|
||||||
|
# NOTE: Replacing the object's context by that of the parent activity is
|
||||||
|
# not sound, but it's consistent with the rest of the codebase
|
||||||
|
object = object.merge({ '@context' => context })
|
||||||
|
|
||||||
|
return if value_or_id(first_of_value(object['attributedTo'])) != account.uri || non_matching_uri_hosts?(account.uri, object['id'])
|
||||||
|
|
||||||
|
object
|
||||||
|
end
|
||||||
|
|
||||||
def canonicalize(json)
|
def canonicalize(json)
|
||||||
graph = RDF::Graph.new << JSON::LD::API.toRdf(json, documentLoader: method(:load_jsonld_context))
|
graph = RDF::Graph.new << JSON::LD::API.toRdf(json, documentLoader: method(:load_jsonld_context))
|
||||||
graph.dump(:normalize)
|
graph.dump(:normalize)
|
||||||
|
|
|
@ -204,7 +204,9 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||||
|
|
||||||
@quote.status = status
|
@quote.status = status
|
||||||
@quote.save
|
@quote.save
|
||||||
ActivityPub::VerifyQuoteService.new.call(@quote, fetchable_quoted_uri: @quote_uri, request_id: @options[:request_id])
|
|
||||||
|
embedded_quote = safe_prefetched_embed(@account, @status_parser.quoted_object, @json['context'])
|
||||||
|
ActivityPub::VerifyQuoteService.new.call(@quote, fetchable_quoted_uri: @quote_uri, prefetched_quoted_object: embedded_quote, request_id: @options[:request_id])
|
||||||
rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS
|
rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS
|
||||||
ActivityPub::RefetchAndVerifyQuoteWorker.perform_in(rand(30..600).seconds, @quote.id, @quote_uri, { 'request_id' => @options[:request_id] })
|
ActivityPub::RefetchAndVerifyQuoteWorker.perform_in(rand(30..600).seconds, @quote.id, @quote_uri, { 'request_id' => @options[:request_id] })
|
||||||
end
|
end
|
||||||
|
|
|
@ -95,11 +95,11 @@ class ActivityPub::Parser::StatusParser
|
||||||
end
|
end
|
||||||
|
|
||||||
def favourites_count
|
def favourites_count
|
||||||
@object.dig(:likes, :totalItems)
|
@object.dig('likes', 'totalItems')
|
||||||
end
|
end
|
||||||
|
|
||||||
def reblogs_count
|
def reblogs_count
|
||||||
@object.dig(:shares, :totalItems)
|
@object.dig('shares', 'totalItems')
|
||||||
end
|
end
|
||||||
|
|
||||||
def quote_policy
|
def quote_policy
|
||||||
|
@ -120,6 +120,11 @@ class ActivityPub::Parser::StatusParser
|
||||||
end.first
|
end.first
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# The inlined quote; out of the attributes we support, only `https://w3id.org/fep/044f#quote` explicitly supports inlined objects
|
||||||
|
def quoted_object
|
||||||
|
as_array(@object['quote']).first
|
||||||
|
end
|
||||||
|
|
||||||
def quote_approval_uri
|
def quote_approval_uri
|
||||||
as_array(@object['quoteAuthorization']).first
|
as_array(@object['quoteAuthorization']).first
|
||||||
end
|
end
|
||||||
|
|
|
@ -273,7 +273,6 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
||||||
def update_quote!
|
def update_quote!
|
||||||
return unless Mastodon::Feature.inbound_quotes_enabled?
|
return unless Mastodon::Feature.inbound_quotes_enabled?
|
||||||
|
|
||||||
quote = nil
|
|
||||||
quote_uri = @status_parser.quote_uri
|
quote_uri = @status_parser.quote_uri
|
||||||
|
|
||||||
if quote_uri.present?
|
if quote_uri.present?
|
||||||
|
@ -294,21 +293,23 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
||||||
quote = Quote.create(status: @status, approval_uri: approval_uri)
|
quote = Quote.create(status: @status, approval_uri: approval_uri)
|
||||||
@quote_changed = true
|
@quote_changed = true
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
if quote.present?
|
quote.save
|
||||||
begin
|
|
||||||
quote.save
|
fetch_and_verify_quote!(quote, quote_uri)
|
||||||
ActivityPub::VerifyQuoteService.new.call(quote, fetchable_quoted_uri: quote_uri, request_id: @request_id)
|
|
||||||
rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS
|
|
||||||
ActivityPub::RefetchAndVerifyQuoteWorker.perform_in(rand(30..600).seconds, quote.id, quote_uri, { 'request_id' => @request_id })
|
|
||||||
end
|
|
||||||
elsif @status.quote.present?
|
elsif @status.quote.present?
|
||||||
@status.quote.destroy!
|
@status.quote.destroy!
|
||||||
@quote_changed = true
|
@quote_changed = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def fetch_and_verify_quote!(quote, quote_uri)
|
||||||
|
embedded_quote = safe_prefetched_embed(@account, @status_parser.quoted_object, @activity_json['context'])
|
||||||
|
ActivityPub::VerifyQuoteService.new.call(quote, fetchable_quoted_uri: quote_uri, prefetched_quoted_object: embedded_quote, request_id: @request_id)
|
||||||
|
rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS
|
||||||
|
ActivityPub::RefetchAndVerifyQuoteWorker.perform_in(rand(30..600).seconds, quote.id, quote_uri, { 'request_id' => @request_id })
|
||||||
|
end
|
||||||
|
|
||||||
def update_counts!
|
def update_counts!
|
||||||
likes = @status_parser.favourites_count
|
likes = @status_parser.favourites_count
|
||||||
shares = @status_parser.reblogs_count
|
shares = @status_parser.reblogs_count
|
||||||
|
|
|
@ -4,15 +4,15 @@ class ActivityPub::VerifyQuoteService < BaseService
|
||||||
include JsonLdHelper
|
include JsonLdHelper
|
||||||
|
|
||||||
# Optionally fetch quoted post, and verify the quote is authorized
|
# Optionally fetch quoted post, and verify the quote is authorized
|
||||||
def call(quote, fetchable_quoted_uri: nil, prefetched_body: nil, request_id: nil)
|
def call(quote, fetchable_quoted_uri: nil, prefetched_quoted_object: nil, prefetched_approval: nil, request_id: nil)
|
||||||
@request_id = request_id
|
@request_id = request_id
|
||||||
@quote = quote
|
@quote = quote
|
||||||
@fetching_error = nil
|
@fetching_error = nil
|
||||||
|
|
||||||
fetch_quoted_post_if_needed!(fetchable_quoted_uri)
|
fetch_quoted_post_if_needed!(fetchable_quoted_uri, prefetched_body: prefetched_quoted_object)
|
||||||
return if fast_track_approval! || quote.approval_uri.blank?
|
return if fast_track_approval! || quote.approval_uri.blank?
|
||||||
|
|
||||||
@json = fetch_approval_object(quote.approval_uri, prefetched_body:)
|
@json = fetch_approval_object(quote.approval_uri, prefetched_body: prefetched_approval)
|
||||||
return quote.reject! if @json.nil?
|
return quote.reject! if @json.nil?
|
||||||
|
|
||||||
return if non_matching_uri_hosts?(quote.approval_uri, value_or_id(@json['attributedTo']))
|
return if non_matching_uri_hosts?(quote.approval_uri, value_or_id(@json['attributedTo']))
|
||||||
|
@ -68,11 +68,11 @@ class ActivityPub::VerifyQuoteService < BaseService
|
||||||
ActivityPub::TagManager.instance.uri_for(@quote.status) == value_or_id(@json['interactingObject'])
|
ActivityPub::TagManager.instance.uri_for(@quote.status) == value_or_id(@json['interactingObject'])
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_quoted_post_if_needed!(uri)
|
def fetch_quoted_post_if_needed!(uri, prefetched_body: nil)
|
||||||
return if uri.nil? || @quote.quoted_status.present?
|
return if uri.nil? || @quote.quoted_status.present?
|
||||||
|
|
||||||
status = ActivityPub::TagManager.instance.uri_to_resource(uri, Status)
|
status = ActivityPub::TagManager.instance.uri_to_resource(uri, Status)
|
||||||
status ||= ActivityPub::FetchRemoteStatusService.new.call(uri, on_behalf_of: @quote.account.followers.local.first, request_id: @request_id)
|
status ||= ActivityPub::FetchRemoteStatusService.new.call(uri, on_behalf_of: @quote.account.followers.local.first, prefetched_body:, request_id: @request_id)
|
||||||
|
|
||||||
@quote.update(quoted_status: status) if status.present?
|
@quote.update(quoted_status: status) if status.present?
|
||||||
rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS => e
|
rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS => e
|
||||||
|
|
|
@ -89,6 +89,37 @@ RSpec.describe ActivityPub::VerifyQuoteService do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with a valid activity for a post that cannot be fetched but is passed as fetched_quoted_object' do
|
||||||
|
let(:quoted_status) { nil }
|
||||||
|
|
||||||
|
let(:approval_interaction_target) { 'https://b.example.com/unknown-quoted' }
|
||||||
|
let(:prefetched_object) do
|
||||||
|
{
|
||||||
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
|
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',
|
||||||
|
}.with_indifferent_access
|
||||||
|
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', prefetched_quoted_object: prefetched_object) }
|
||||||
|
.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 but is inlined' do
|
context 'with a valid activity for a post that cannot be fetched but is inlined' do
|
||||||
let(:quoted_status) { nil }
|
let(:quoted_status) { nil }
|
||||||
|
|
||||||
|
@ -148,7 +179,7 @@ RSpec.describe ActivityPub::VerifyQuoteService do
|
||||||
|
|
||||||
context 'with a valid activity for already-fetched posts, with a pre-fetched approval' do
|
context 'with a valid activity for already-fetched posts, with a pre-fetched approval' do
|
||||||
it 'updates the status without fetching the activity' do
|
it 'updates the status without fetching the activity' do
|
||||||
expect { subject.call(quote, prefetched_body: Oj.dump(json)) }
|
expect { subject.call(quote, prefetched_approval: Oj.dump(json)) }
|
||||||
.to change(quote, :state).to('accepted')
|
.to change(quote, :state).to('accepted')
|
||||||
|
|
||||||
expect(a_request(:get, approval_uri))
|
expect(a_request(:get, approval_uri))
|
||||||
|
|
Loading…
Reference in New Issue
Block a user