Alternative handling of private self-boosts (#9998)
* When self-boosting, embed original toot into Announce serialization * Process unknown self-boosts from Announce object if it is more than an URI * Add some self-boost specs * Only serialize private toots in self-Announces
This commit is contained in:
		
							parent
							
								
									169b9d4428
								
							
						
					
					
						commit
						6a5307a573
					
				@ -4,6 +4,9 @@ class ActivityPub::Activity
 | 
				
			|||||||
  include JsonLdHelper
 | 
					  include JsonLdHelper
 | 
				
			||||||
  include Redisable
 | 
					  include Redisable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  SUPPORTED_TYPES = %w(Note).freeze
 | 
				
			||||||
 | 
					  CONVERTED_TYPES = %w(Image Video Article Page).freeze
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def initialize(json, account, **options)
 | 
					  def initialize(json, account, **options)
 | 
				
			||||||
    @json    = json
 | 
					    @json    = json
 | 
				
			||||||
    @account = account
 | 
					    @account = account
 | 
				
			||||||
@ -71,6 +74,18 @@ class ActivityPub::Activity
 | 
				
			|||||||
    @object_uri ||= value_or_id(@object)
 | 
					    @object_uri ||= value_or_id(@object)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def unsupported_object_type?
 | 
				
			||||||
 | 
					    @object.is_a?(String) || !(supported_object_type? || converted_object_type?)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def supported_object_type?
 | 
				
			||||||
 | 
					    equals_or_includes_any?(@object['type'], SUPPORTED_TYPES)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def converted_object_type?
 | 
				
			||||||
 | 
					    equals_or_includes_any?(@object['type'], CONVERTED_TYPES)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def distribute(status)
 | 
					  def distribute(status)
 | 
				
			||||||
    crawl_links(status)
 | 
					    crawl_links(status)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -120,6 +135,23 @@ class ActivityPub::Activity
 | 
				
			|||||||
    redis.setex("delete_upon_arrival:#{@account.id}:#{uri}", 6.hours.seconds, uri)
 | 
					    redis.setex("delete_upon_arrival:#{@account.id}:#{uri}", 6.hours.seconds, uri)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def status_from_object
 | 
				
			||||||
 | 
					    # If the status is already known, return it
 | 
				
			||||||
 | 
					    status = status_from_uri(object_uri)
 | 
				
			||||||
 | 
					    return status unless status.nil?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # If the boosted toot is embedded and it is a self-boost, handle it like a Create
 | 
				
			||||||
 | 
					    unless unsupported_object_type?
 | 
				
			||||||
 | 
					      actor_id = value_or_id(first_of_value(@object['attributedTo'])) || @account.uri
 | 
				
			||||||
 | 
					      if actor_id == @account.uri
 | 
				
			||||||
 | 
					        return ActivityPub::Activity.factory({ 'type' => 'Create', 'actor' => actor_id, 'object' => @object }, @account).perform
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # If the status is not from the actor, try to fetch it
 | 
				
			||||||
 | 
					    return fetch_remote_original_status if value_or_id(first_of_value(@json['attributedTo'])) == @account.uri
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def fetch_remote_original_status
 | 
					  def fetch_remote_original_status
 | 
				
			||||||
    if object_uri.start_with?('http')
 | 
					    if object_uri.start_with?('http')
 | 
				
			||||||
      return if ActivityPub::TagManager.instance.local_uri?(object_uri)
 | 
					      return if ActivityPub::TagManager.instance.local_uri?(object_uri)
 | 
				
			||||||
 | 
				
			|||||||
@ -2,9 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class ActivityPub::Activity::Announce < ActivityPub::Activity
 | 
					class ActivityPub::Activity::Announce < ActivityPub::Activity
 | 
				
			||||||
  def perform
 | 
					  def perform
 | 
				
			||||||
    original_status   = status_from_uri(object_uri)
 | 
					    original_status = status_from_object
 | 
				
			||||||
    original_status ||= fetch_remote_original_status
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return if original_status.nil? || delete_arrived_first?(@json['id']) || !announceable?(original_status)
 | 
					    return if original_status.nil? || delete_arrived_first?(@json['id']) || !announceable?(original_status)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    status = Status.find_by(account: @account, reblog: original_status)
 | 
					    status = Status.find_by(account: @account, reblog: original_status)
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,6 @@
 | 
				
			|||||||
# frozen_string_literal: true
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ActivityPub::Activity::Create < ActivityPub::Activity
 | 
					class ActivityPub::Activity::Create < ActivityPub::Activity
 | 
				
			||||||
  SUPPORTED_TYPES = %w(Note).freeze
 | 
					 | 
				
			||||||
  CONVERTED_TYPES = %w(Image Video Article Page).freeze
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def perform
 | 
					  def perform
 | 
				
			||||||
    return if unsupported_object_type? || invalid_origin?(@object['id'])
 | 
					    return if unsupported_object_type? || invalid_origin?(@object['id'])
 | 
				
			||||||
    return if Tombstone.exists?(uri: @object['id'])
 | 
					    return if Tombstone.exists?(uri: @object['id'])
 | 
				
			||||||
@ -318,22 +315,10 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
 | 
				
			|||||||
    @object['nameMap'].is_a?(Hash) && !@object['nameMap'].empty?
 | 
					    @object['nameMap'].is_a?(Hash) && !@object['nameMap'].empty?
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def unsupported_object_type?
 | 
					 | 
				
			||||||
    @object.is_a?(String) || !(supported_object_type? || converted_object_type?)
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def unsupported_media_type?(mime_type)
 | 
					  def unsupported_media_type?(mime_type)
 | 
				
			||||||
    mime_type.present? && !(MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES).include?(mime_type)
 | 
					    mime_type.present? && !(MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES).include?(mime_type)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def supported_object_type?
 | 
					 | 
				
			||||||
    equals_or_includes_any?(@object['type'], SUPPORTED_TYPES)
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def converted_object_type?
 | 
					 | 
				
			||||||
    equals_or_includes_any?(@object['type'], CONVERTED_TYPES)
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def skip_download?
 | 
					  def skip_download?
 | 
				
			||||||
    return @skip_download if defined?(@skip_download)
 | 
					    return @skip_download if defined?(@skip_download)
 | 
				
			||||||
    @skip_download ||= DomainBlock.find_by(domain: @account.domain)&.reject_media?
 | 
					    @skip_download ||= DomainBlock.find_by(domain: @account.domain)&.reject_media?
 | 
				
			||||||
 | 
				
			|||||||
@ -3,8 +3,8 @@
 | 
				
			|||||||
class ActivityPub::ActivitySerializer < ActiveModel::Serializer
 | 
					class ActivityPub::ActivitySerializer < ActiveModel::Serializer
 | 
				
			||||||
  attributes :id, :type, :actor, :published, :to, :cc
 | 
					  attributes :id, :type, :actor, :published, :to, :cc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  has_one :proper, key: :object, serializer: ActivityPub::NoteSerializer, unless: :announce?
 | 
					  has_one :proper, key: :object, serializer: ActivityPub::NoteSerializer, unless: :owned_announce?
 | 
				
			||||||
  attribute :proper_uri, key: :object, if: :announce?
 | 
					  attribute :proper_uri, key: :object, if: :owned_announce?
 | 
				
			||||||
  attribute :atom_uri, if: :announce?
 | 
					  attribute :atom_uri, if: :announce?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def id
 | 
					  def id
 | 
				
			||||||
@ -42,4 +42,8 @@ class ActivityPub::ActivitySerializer < ActiveModel::Serializer
 | 
				
			|||||||
  def announce?
 | 
					  def announce?
 | 
				
			||||||
    object.reblog?
 | 
					    object.reblog?
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def owned_announce?
 | 
				
			||||||
 | 
					    announce? && object.account == object.proper.account && object.proper.private_visibility?
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
require 'rails_helper'
 | 
					require 'rails_helper'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RSpec.describe ActivityPub::Activity::Announce do
 | 
					RSpec.describe ActivityPub::Activity::Announce do
 | 
				
			||||||
  let(:sender)    { Fabricate(:account) }
 | 
					  let(:sender)    { Fabricate(:account, followers_url: 'http://example.com/followers') }
 | 
				
			||||||
  let(:recipient) { Fabricate(:account) }
 | 
					  let(:recipient) { Fabricate(:account) }
 | 
				
			||||||
  let(:status)    { Fabricate(:status, account: recipient) }
 | 
					  let(:status)    { Fabricate(:status, account: recipient) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -11,19 +11,60 @@ RSpec.describe ActivityPub::Activity::Announce do
 | 
				
			|||||||
      id: 'foo',
 | 
					      id: 'foo',
 | 
				
			||||||
      type: 'Announce',
 | 
					      type: 'Announce',
 | 
				
			||||||
      actor: ActivityPub::TagManager.instance.uri_for(sender),
 | 
					      actor: ActivityPub::TagManager.instance.uri_for(sender),
 | 
				
			||||||
      object: ActivityPub::TagManager.instance.uri_for(status),
 | 
					      object: object_json,
 | 
				
			||||||
    }.with_indifferent_access
 | 
					    }.with_indifferent_access
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe '#perform' do
 | 
					  subject { described_class.new(json, sender) }
 | 
				
			||||||
    subject { described_class.new(json, sender) }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  before do
 | 
				
			||||||
 | 
					    sender.update(uri: ActivityPub::TagManager.instance.uri_for(sender))
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe '#perform' do
 | 
				
			||||||
    before do
 | 
					    before do
 | 
				
			||||||
      subject.perform
 | 
					      subject.perform
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it 'creates a reblog by sender of status' do
 | 
					    context 'a known status' do
 | 
				
			||||||
      expect(sender.reblogged?(status)).to be true
 | 
					      let(:object_json) do
 | 
				
			||||||
 | 
					        ActivityPub::TagManager.instance.uri_for(status)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it 'creates a reblog by sender of status' do
 | 
				
			||||||
 | 
					        expect(sender.reblogged?(status)).to be true
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context 'self-boost of a previously unknown status with missing attributedTo' do
 | 
				
			||||||
 | 
					      let(:object_json) do
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
 | 
				
			||||||
 | 
					          type: 'Note',
 | 
				
			||||||
 | 
					          content: 'Lorem ipsum',
 | 
				
			||||||
 | 
					          to: 'http://example.com/followers',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it 'creates a reblog by sender of status' do
 | 
				
			||||||
 | 
					        expect(sender.reblogged?(sender.statuses.first)).to be true
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context 'self-boost of a previously unknown status with correct attributedTo' do
 | 
				
			||||||
 | 
					      let(:object_json) do
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
 | 
				
			||||||
 | 
					          type: 'Note',
 | 
				
			||||||
 | 
					          content: 'Lorem ipsum',
 | 
				
			||||||
 | 
					          attributedTo: ActivityPub::TagManager.instance.uri_for(sender),
 | 
				
			||||||
 | 
					          to: 'http://example.com/followers',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it 'creates a reblog by sender of status' do
 | 
				
			||||||
 | 
					        expect(sender.reblogged?(sender.statuses.first)).to be true
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user