2
2
mirror of https://github.com/mastodon/mastodon synced 2025-04-12 00:56:38 +02:00

Store incoming quote policies

This commit is contained in:
Claire 2025-04-08 12:06:22 +02:00
parent d26219d1ed
commit 8dd7306395
4 changed files with 154 additions and 3 deletions

View File

@ -79,7 +79,12 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
end
def process_status_params
@status_parser = ActivityPub::Parser::StatusParser.new(@json, followers_collection: @account.followers_url, object: @object)
@status_parser = ActivityPub::Parser::StatusParser.new(
@json,
followers_collection: @account.followers_url,
actor_uri: ActivityPub::TagManager.instance.uri_for(@account),
object: @object
)
attachment_ids = process_attachments.take(Status::MEDIA_ATTACHMENTS_LIMIT).map(&:id)
@ -101,6 +106,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
media_attachment_ids: attachment_ids,
ordered_media_attachment_ids: attachment_ids,
poll: process_poll,
quote_approval_policy: @status_parser.quote_policy,
}
end

View File

@ -8,6 +8,7 @@ class ActivityPub::Parser::StatusParser
# @param [Hash] json
# @param [Hash] options
# @option options [String] :followers_collection
# @option options [String] :actor_uri
# @option options [Hash] :object
def initialize(json, **options)
@json = json
@ -101,6 +102,31 @@ class ActivityPub::Parser::StatusParser
@object.dig(:shares, :totalItems)
end
def quote_policy
flags = 0
policy = @object.dig('interactionPolicy', 'canQuote')
return flags if policy.blank?
allowed_actors = as_array(policy['automaticApproval']) + as_array(policy['manualApproval'])
allowed_actors.uniq!
flags |= Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] if allowed_actors.delete('as:Public') || allowed_actors.delete('Public') || allowed_actors.delete('https://www.w3.org/ns/activitystreams#Public')
flags |= Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] if allowed_actors.delete(@options[:followers_collection])
# TODO: we don't actually store that collection URI
# flags |= Status::QUOTE_APPROVAL_POLICY_FLAGS[:followed]
# Remove the special-meaning actor URI
allowed_actors.delete(@options[:actor_uri])
# Tagged users are always allowed, so remove them
allowed_actors -= as_array(@object['tag']).filter_map { |tag| tag['href'] if equals_or_includes?(tag['type'], 'Mention') }
# Any unrecognized actor is marked as unknown
flags |= Status::QUOTE_APPROVAL_POLICY_FLAGS[:unknown] unless allowed_actors.empty?
flags
end
private
def raw_language_code

View File

@ -10,7 +10,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
@activity_json = activity_json
@json = object_json
@status_parser = ActivityPub::Parser::StatusParser.new(@json)
@status_parser = ActivityPub::Parser::StatusParser.new(@json, followers_collection: status.account.followers_url, actor_uri: ActivityPub::TagManager.instance.uri_for(status.account))
@uri = @status_parser.uri
@status = status
@account = status.account
@ -40,6 +40,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
Status.transaction do
record_previous_edit!
update_media_attachments!
update_interaction_policies!
update_poll!
update_immediate_attributes!
update_metadata!
@ -61,12 +62,17 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
def handle_implicit_update!
with_redis_lock("create:#{@uri}") do
update_interaction_policies!
update_poll!(allow_significant_changes: false)
queue_poll_notifications!
update_counts!
end
end
def update_interaction_policies!
@status.quote_approval_policy = @status_parser.quote_policy
end
def update_media_attachments!
previous_media_attachments = @status.media_attachments.to_a
previous_media_attachments_ids = @status.ordered_media_attachment_ids || previous_media_attachments.map(&:id)

View File

@ -7,10 +7,11 @@ RSpec.describe ActivityPub::Parser::StatusParser do
let(:sender) { Fabricate(:account, followers_url: 'http://example.com/followers', domain: 'example.com', uri: 'https://example.com/actor') }
let(:follower) { Fabricate(:account, username: 'bob') }
let(:context) { 'https://www.w3.org/ns/activitystreams' }
let(:json) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
'@context': context,
id: [ActivityPub::TagManager.instance.uri_for(sender), '#foo'].join,
type: 'Create',
actor: ActivityPub::TagManager.instance.uri_for(sender),
@ -47,4 +48,116 @@ RSpec.describe ActivityPub::Parser::StatusParser do
language: :en
)
end
describe '#quote_policy' do
subject do
described_class
.new(
json,
actor_uri: ActivityPub::TagManager.instance.uri_for(sender),
followers_collection: sender.followers_url
).quote_policy
end
let(:context) do
[
'https://www.w3.org/ns/activitystreams',
{
gts: 'https://gotosocial.org/ns#',
interactionPolicy: {
'@id': 'gts:interactionPolicy',
'@type': '@id',
},
canQuote: {
'@id': 'gts:canQuote',
'@type': '@id',
},
automaticApproval: {
'@id': 'gts:automaticApproval',
'@type': '@id',
},
manualApproval: {
'@id': 'gts:manualApproval',
'@type': '@id',
},
},
]
end
context 'when nobody is allowed to quote' do
let(:object_json) do
{
id: [ActivityPub::TagManager.instance.uri_for(sender), 'post1'].join('/'),
type: 'Note',
to: [
'https://www.w3.org/ns/activitystreams#Public',
ActivityPub::TagManager.instance.uri_for(follower),
],
interactionPolicy: {
canQuote: {
automaticApproval: ActivityPub::TagManager.instance.uri_for(sender),
},
},
content: 'bleh',
published: 1.hour.ago.utc.iso8601,
updated: 1.hour.ago.utc.iso8601,
}
end
it 'returns a policy not allowing anyone to quote' do
expect(subject).to eq 0
end
end
context 'when everybody is allowed to quote' do
let(:object_json) do
{
id: [ActivityPub::TagManager.instance.uri_for(sender), 'post1'].join('/'),
type: 'Note',
to: [
'https://www.w3.org/ns/activitystreams#Public',
ActivityPub::TagManager.instance.uri_for(follower),
],
interactionPolicy: {
canQuote: {
automaticApproval: 'https://www.w3.org/ns/activitystreams#Public',
},
},
content: 'bleh',
published: 1.hour.ago.utc.iso8601,
updated: 1.hour.ago.utc.iso8601,
}
end
it 'returns a policy not allowing anyone to quote' do
expect(subject).to eq Status::QUOTE_APPROVAL_POLICY_FLAGS[:public]
end
end
context 'when everybody is allowed to quote but only followers are automatically approved' do
let(:object_json) do
{
id: [ActivityPub::TagManager.instance.uri_for(sender), 'post1'].join('/'),
type: 'Note',
to: [
'https://www.w3.org/ns/activitystreams#Public',
ActivityPub::TagManager.instance.uri_for(follower),
],
interactionPolicy: {
canQuote: {
automaticApproval: sender.followers_url,
manualApproval: 'https://www.w3.org/ns/activitystreams#Public',
},
},
content: 'bleh',
published: 1.hour.ago.utc.iso8601,
updated: 1.hour.ago.utc.iso8601,
}
end
it 'returns a policy allowing everyone including followers' do
expect(subject).to eq Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] | Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers]
end
end
end
end