Skip to content

Commit 8a094bd

Browse files
ClearlyClairehiyuki2578
authored andcommitted
Alternative handling of private self-boosts (mastodon#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
1 parent 6034720 commit 8a094bd

5 files changed

Lines changed: 86 additions & 26 deletions

File tree

app/lib/activitypub/activity.rb

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ class ActivityPub::Activity
44
include JsonLdHelper
55
include Redisable
66

7+
SUPPORTED_TYPES = %w(Note).freeze
8+
CONVERTED_TYPES = %w(Image Video Article Page).freeze
9+
710
def initialize(json, account, **options)
811
@json = json
912
@account = account
@@ -71,6 +74,18 @@ def object_uri
7174
@object_uri ||= value_or_id(@object)
7275
end
7376

77+
def unsupported_object_type?
78+
@object.is_a?(String) || !(supported_object_type? || converted_object_type?)
79+
end
80+
81+
def supported_object_type?
82+
equals_or_includes_any?(@object['type'], SUPPORTED_TYPES)
83+
end
84+
85+
def converted_object_type?
86+
equals_or_includes_any?(@object['type'], CONVERTED_TYPES)
87+
end
88+
7489
def distribute(status)
7590
crawl_links(status)
7691

@@ -120,6 +135,23 @@ def delete_later!(uri)
120135
redis.setex("delete_upon_arrival:#{@account.id}:#{uri}", 6.hours.seconds, uri)
121136
end
122137

138+
def status_from_object
139+
# If the status is already known, return it
140+
status = status_from_uri(object_uri)
141+
return status unless status.nil?
142+
143+
# If the boosted toot is embedded and it is a self-boost, handle it like a Create
144+
unless unsupported_object_type?
145+
actor_id = value_or_id(first_of_value(@object['attributedTo'])) || @account.uri
146+
if actor_id == @account.uri
147+
return ActivityPub::Activity.factory({ 'type' => 'Create', 'actor' => actor_id, 'object' => @object }, @account).perform
148+
end
149+
end
150+
151+
# If the status is not from the actor, try to fetch it
152+
return fetch_remote_original_status if value_or_id(first_of_value(@json['attributedTo'])) == @account.uri
153+
end
154+
123155
def fetch_remote_original_status
124156
if object_uri.start_with?('http')
125157
return if ActivityPub::TagManager.instance.local_uri?(object_uri)

app/lib/activitypub/activity/announce.rb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22

33
class ActivityPub::Activity::Announce < ActivityPub::Activity
44
def perform
5-
original_status = status_from_uri(object_uri)
6-
original_status ||= fetch_remote_original_status
7-
5+
original_status = status_from_object
86
return if original_status.nil? || delete_arrived_first?(@json['id']) || !announceable?(original_status)
97

108
status = Status.find_by(account: @account, reblog: original_status)

app/lib/activitypub/activity/create.rb

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
# frozen_string_literal: true
22

33
class ActivityPub::Activity::Create < ActivityPub::Activity
4-
SUPPORTED_TYPES = %w(Note).freeze
5-
CONVERTED_TYPES = %w(Image Video Article Page).freeze
6-
74
def perform
85
return if unsupported_object_type? || invalid_origin?(@object['id'])
96
return if Tombstone.exists?(uri: @object['id'])
@@ -318,22 +315,10 @@ def name_language_map?
318315
@object['nameMap'].is_a?(Hash) && !@object['nameMap'].empty?
319316
end
320317

321-
def unsupported_object_type?
322-
@object.is_a?(String) || !(supported_object_type? || converted_object_type?)
323-
end
324-
325318
def unsupported_media_type?(mime_type)
326319
mime_type.present? && !(MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES).include?(mime_type)
327320
end
328321

329-
def supported_object_type?
330-
equals_or_includes_any?(@object['type'], SUPPORTED_TYPES)
331-
end
332-
333-
def converted_object_type?
334-
equals_or_includes_any?(@object['type'], CONVERTED_TYPES)
335-
end
336-
337322
def skip_download?
338323
return @skip_download if defined?(@skip_download)
339324
@skip_download ||= DomainBlock.find_by(domain: @account.domain)&.reject_media?

app/serializers/activitypub/activity_serializer.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
class ActivityPub::ActivitySerializer < ActiveModel::Serializer
44
attributes :id, :type, :actor, :published, :to, :cc
55

6-
has_one :proper, key: :object, serializer: ActivityPub::NoteSerializer, unless: :announce?
7-
attribute :proper_uri, key: :object, if: :announce?
6+
has_one :proper, key: :object, serializer: ActivityPub::NoteSerializer, unless: :owned_announce?
7+
attribute :proper_uri, key: :object, if: :owned_announce?
88
attribute :atom_uri, if: :announce?
99

1010
def id
@@ -42,4 +42,8 @@ def atom_uri
4242
def announce?
4343
object.reblog?
4444
end
45+
46+
def owned_announce?
47+
announce? && object.account == object.proper.account && object.proper.private_visibility?
48+
end
4549
end
Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
require 'rails_helper'
22

33
RSpec.describe ActivityPub::Activity::Announce do
4-
let(:sender) { Fabricate(:account) }
4+
let(:sender) { Fabricate(:account, followers_url: 'http://example.com/followers') }
55
let(:recipient) { Fabricate(:account) }
66
let(:status) { Fabricate(:status, account: recipient) }
77

@@ -11,19 +11,60 @@
1111
id: 'foo',
1212
type: 'Announce',
1313
actor: ActivityPub::TagManager.instance.uri_for(sender),
14-
object: ActivityPub::TagManager.instance.uri_for(status),
14+
object: object_json,
1515
}.with_indifferent_access
1616
end
1717

18-
describe '#perform' do
19-
subject { described_class.new(json, sender) }
18+
subject { described_class.new(json, sender) }
19+
20+
before do
21+
sender.update(uri: ActivityPub::TagManager.instance.uri_for(sender))
22+
end
2023

24+
describe '#perform' do
2125
before do
2226
subject.perform
2327
end
2428

25-
it 'creates a reblog by sender of status' do
26-
expect(sender.reblogged?(status)).to be true
29+
context 'a known status' do
30+
let(:object_json) do
31+
ActivityPub::TagManager.instance.uri_for(status)
32+
end
33+
34+
it 'creates a reblog by sender of status' do
35+
expect(sender.reblogged?(status)).to be true
36+
end
37+
end
38+
39+
context 'self-boost of a previously unknown status with missing attributedTo' do
40+
let(:object_json) do
41+
{
42+
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
43+
type: 'Note',
44+
content: 'Lorem ipsum',
45+
to: 'http://example.com/followers',
46+
}
47+
end
48+
49+
it 'creates a reblog by sender of status' do
50+
expect(sender.reblogged?(sender.statuses.first)).to be true
51+
end
52+
end
53+
54+
context 'self-boost of a previously unknown status with correct attributedTo' do
55+
let(:object_json) do
56+
{
57+
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
58+
type: 'Note',
59+
content: 'Lorem ipsum',
60+
attributedTo: ActivityPub::TagManager.instance.uri_for(sender),
61+
to: 'http://example.com/followers',
62+
}
63+
end
64+
65+
it 'creates a reblog by sender of status' do
66+
expect(sender.reblogged?(sender.statuses.first)).to be true
67+
end
2768
end
2869
end
2970
end

0 commit comments

Comments
 (0)