about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--app/lib/activitypub/activity.rb32
-rw-r--r--app/lib/activitypub/activity/announce.rb4
-rw-r--r--app/lib/activitypub/activity/create.rb15
-rw-r--r--app/serializers/activitypub/activity_serializer.rb8
-rw-r--r--spec/lib/activitypub/activity/announce_spec.rb53
5 files changed, 86 insertions, 26 deletions
diff --git a/app/lib/activitypub/activity.rb b/app/lib/activitypub/activity.rb
index 919678618..7e4e19531 100644
--- a/app/lib/activitypub/activity.rb
+++ b/app/lib/activitypub/activity.rb
@@ -4,6 +4,9 @@ class ActivityPub::Activity
   include JsonLdHelper
   include Redisable
 
+  SUPPORTED_TYPES = %w(Note).freeze
+  CONVERTED_TYPES = %w(Image Video Article Page).freeze
+
   def initialize(json, account, **options)
     @json    = json
     @account = account
@@ -71,6 +74,18 @@ class ActivityPub::Activity
     @object_uri ||= value_or_id(@object)
   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)
     crawl_links(status)
 
@@ -120,6 +135,23 @@ class ActivityPub::Activity
     redis.setex("delete_upon_arrival:#{@account.id}:#{uri}", 6.hours.seconds, uri)
   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
     if object_uri.start_with?('http')
       return if ActivityPub::TagManager.instance.local_uri?(object_uri)
diff --git a/app/lib/activitypub/activity/announce.rb b/app/lib/activitypub/activity/announce.rb
index 34d1b7cbd..04afeea20 100644
--- a/app/lib/activitypub/activity/announce.rb
+++ b/app/lib/activitypub/activity/announce.rb
@@ -2,9 +2,7 @@
 
 class ActivityPub::Activity::Announce < ActivityPub::Activity
   def perform
-    original_status   = status_from_uri(object_uri)
-    original_status ||= fetch_remote_original_status
-
+    original_status = status_from_object
     return if original_status.nil? || delete_arrived_first?(@json['id']) || !announceable?(original_status)
 
     status = Status.find_by(account: @account, reblog: original_status)
diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb
index b49657d4b..9a3db51dd 100644
--- a/app/lib/activitypub/activity/create.rb
+++ b/app/lib/activitypub/activity/create.rb
@@ -1,9 +1,6 @@
 # frozen_string_literal: true
 
 class ActivityPub::Activity::Create < ActivityPub::Activity
-  SUPPORTED_TYPES = %w(Note).freeze
-  CONVERTED_TYPES = %w(Image Video Article Page).freeze
-
   def perform
     return if unsupported_object_type? || invalid_origin?(@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?
   end
 
-  def unsupported_object_type?
-    @object.is_a?(String) || !(supported_object_type? || converted_object_type?)
-  end
-
   def unsupported_media_type?(mime_type)
     mime_type.present? && !(MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES).include?(mime_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 skip_download?
     return @skip_download if defined?(@skip_download)
     @skip_download ||= DomainBlock.find_by(domain: @account.domain)&.reject_media?
diff --git a/app/serializers/activitypub/activity_serializer.rb b/app/serializers/activitypub/activity_serializer.rb
index 50c4f6a04..b51e8c544 100644
--- a/app/serializers/activitypub/activity_serializer.rb
+++ b/app/serializers/activitypub/activity_serializer.rb
@@ -3,8 +3,8 @@
 class ActivityPub::ActivitySerializer < ActiveModel::Serializer
   attributes :id, :type, :actor, :published, :to, :cc
 
-  has_one :proper, key: :object, serializer: ActivityPub::NoteSerializer, unless: :announce?
-  attribute :proper_uri, key: :object, if: :announce?
+  has_one :proper, key: :object, serializer: ActivityPub::NoteSerializer, unless: :owned_announce?
+  attribute :proper_uri, key: :object, if: :owned_announce?
   attribute :atom_uri, if: :announce?
 
   def id
@@ -42,4 +42,8 @@ class ActivityPub::ActivitySerializer < ActiveModel::Serializer
   def announce?
     object.reblog?
   end
+
+  def owned_announce?
+    announce? && object.account == object.proper.account && object.proper.private_visibility?
+  end
 end
diff --git a/spec/lib/activitypub/activity/announce_spec.rb b/spec/lib/activitypub/activity/announce_spec.rb
index 54dd52a60..1725c2843 100644
--- a/spec/lib/activitypub/activity/announce_spec.rb
+++ b/spec/lib/activitypub/activity/announce_spec.rb
@@ -1,7 +1,7 @@
 require 'rails_helper'
 
 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(:status)    { Fabricate(:status, account: recipient) }
 
@@ -11,19 +11,60 @@ RSpec.describe ActivityPub::Activity::Announce do
       id: 'foo',
       type: 'Announce',
       actor: ActivityPub::TagManager.instance.uri_for(sender),
-      object: ActivityPub::TagManager.instance.uri_for(status),
+      object: object_json,
     }.with_indifferent_access
   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
       subject.perform
     end
 
-    it 'creates a reblog by sender of status' do
-      expect(sender.reblogged?(status)).to be true
+    context 'a known status' do
+      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