From 6b1e1899fd5ce13141423918222aacd0f7a875c8 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 15 Apr 2026 17:05:58 +0200 Subject: [PATCH] Change discoverable accounts to only allow followers to feature them if they are locked (#38672) --- app/models/account.rb | 2 +- .../account/interaction_policy_concern.rb | 2 +- .../activitypub/actor_serializer.rb | 10 +++++- spec/models/account_spec.rb | 31 ++++++++++++++++--- .../activitypub/actor_serializer_spec.rb | 12 +++++++ .../rest/account_serializer_spec.rb | 12 +++++++ 6 files changed, 62 insertions(+), 7 deletions(-) diff --git a/app/models/account.rb b/app/models/account.rb index 141a9fb7e6..59df236959 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -473,7 +473,7 @@ class Account < ApplicationRecord end def featureable_by?(other_account) - return discoverable? if local? + return discoverable? && (!locked? || followed_by?(other_account)) if local? feature_policy_for_account(other_account).in?(%i(automatic manual)) end diff --git a/app/models/concerns/account/interaction_policy_concern.rb b/app/models/concerns/account/interaction_policy_concern.rb index 8fe9eda1ba..3966053e05 100644 --- a/app/models/concerns/account/interaction_policy_concern.rb +++ b/app/models/concerns/account/interaction_policy_concern.rb @@ -64,6 +64,6 @@ module Account::InteractionPolicyConcern def local_feature_policy(kind) return [] if kind == :manual || !discoverable? - [:public] + [locked? ? :followers : :public] end end diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb index a8c639fe34..8212b607f6 100644 --- a/app/serializers/activitypub/actor_serializer.rb +++ b/app/serializers/activitypub/actor_serializer.rb @@ -176,7 +176,15 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer end def interaction_policy - uri = object.discoverable? ? ActivityPub::TagManager::COLLECTIONS[:public] : ActivityPub::TagManager.instance.uri_for(object) + uri = begin + if !object.discoverable? + ActivityPub::TagManager.instance.uri_for(object) + elsif object.locked? + ActivityPub::TagManager.instance.followers_uri_for(object) + else + ActivityPub::TagManager::COLLECTIONS[:public] + end + end { canFeature: { diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index a128a9b2f8..0d76b26887 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -772,19 +772,42 @@ RSpec.describe Account do end describe '#featureable_by?' do - subject { Fabricate.build(:account, domain: (local ? nil : 'example.com'), discoverable:, feature_approval_policy:) } + subject { Fabricate.build(:account, domain: (local ? nil : 'example.com'), discoverable:, locked:, feature_approval_policy:) } + let(:locked) { false } let(:local_account) { Fabricate(:account) } context 'when account is local' do let(:local) { true } - let(:feature_approval_policy) { nil } + let(:feature_approval_policy) { 0 } context 'when account is discoverable' do let(:discoverable) { true } - it 'returns `true`' do - expect(subject.featureable_by?(local_account)).to be true + context 'when the account is not locked' do + let(:locked) { false } + + it 'returns `true`' do + expect(subject.featureable_by?(local_account)).to be true + end + end + + context 'when the account is locked' do + let(:locked) { true } + + it 'returns `false`' do + expect(subject.featureable_by?(local_account)).to be false + end + + context 'when the other account is a follower' do + before do + local_account.follow!(subject) + end + + it 'returns `true`' do + expect(subject.featureable_by?(local_account)).to be true + end + end end end diff --git a/spec/serializers/activitypub/actor_serializer_spec.rb b/spec/serializers/activitypub/actor_serializer_spec.rb index 734da6673c..73702c979c 100644 --- a/spec/serializers/activitypub/actor_serializer_spec.rb +++ b/spec/serializers/activitypub/actor_serializer_spec.rb @@ -56,6 +56,18 @@ RSpec.describe ActivityPub::ActorSerializer do }, }) end + + context 'when actor is locked' do + let(:record) { Fabricate(:account, locked: true) } + + it 'includes an automatic policy allowing followers' do + expect(subject).to include('interactionPolicy' => { + 'canFeature' => { + 'automaticApproval' => [ActivityPub::TagManager.instance.followers_uri_for(record)], + }, + }) + end + end end context 'when actor is not discoverable' do diff --git a/spec/serializers/rest/account_serializer_spec.rb b/spec/serializers/rest/account_serializer_spec.rb index 998de6b0fb..0e3ce1caca 100644 --- a/spec/serializers/rest/account_serializer_spec.rb +++ b/spec/serializers/rest/account_serializer_spec.rb @@ -93,6 +93,18 @@ RSpec.describe REST::AccountSerializer do 'current_user' => 'automatic', }) end + + context 'when account is locked' do + let(:account) { Fabricate(:account, locked: true) } + + it 'includes a policy that allows featuring for followers' do + expect(subject['feature_approval']).to include({ + 'automatic' => ['followers'], + 'manual' => [], + 'current_user' => 'automatic', + }) + end + end end context 'when account is not discoverable' do