Skip to content
This repository has been archived by the owner on Jan 27, 2020. It is now read-only.

Commit

Permalink
Limit the number of people that can be followed from one account (mas…
Browse files Browse the repository at this point in the history
…todon#8807)

Configurable soft limit of 7,500, and above that, configurable
ratio of 1.1 * followers, controlled by:

- MAX_FOLLOWS_THRESHOLD
- MAX_FOLLOWS_RATIO

Fix mastodon#2311
  • Loading branch information
Gargron authored and abcang committed Sep 26, 2019
1 parent be0e3ce commit 10e9014
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 1 deletion.
1 change: 1 addition & 0 deletions app/models/follow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class Follow < ApplicationRecord
has_one :notification, as: :activity, dependent: :destroy

validates :account_id, uniqueness: { scope: :target_account_id }
validates_with FollowLimitValidator, on: :create

scope :recent, -> { reorder(id: :desc) }

Expand Down
1 change: 1 addition & 0 deletions app/models/follow_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class FollowRequest < ApplicationRecord
has_one :notification, as: :activity, dependent: :destroy

validates :account_id, uniqueness: { scope: :target_account_id }
validates_with FollowLimitValidator, on: :create

def authorize!
account.follow!(target_account, reblogs: show_reblogs, uri: uri)
Expand Down
27 changes: 27 additions & 0 deletions app/validators/follow_limit_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

class FollowLimitValidator < ActiveModel::Validator
LIMIT = ENV.fetch('MAX_FOLLOWS_THRESHOLD', 7_500).to_i
RATIO = ENV.fetch('MAX_FOLLOWS_RATIO', 1.1).to_f

def validate(follow)
return if follow.account.nil? || !follow.account.local?
follow.errors.add(:base, I18n.t('users.follow_limit_reached', limit: self.class.limit_for_account(follow.account))) if limit_reached?(follow.account)
end

class << self
def limit_for_account(account)
if account.following_count < LIMIT
LIMIT
else
account.followers_count * RATIO
end
end
end

private

def limit_reached?(account)
account.following_count >= self.class.limit_for_account(account)
end
end
4 changes: 3 additions & 1 deletion app/workers/import_worker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ def relationship_type
end

def import_rows
CSV.new(import_contents).reject(&:blank?)
rows = CSV.new(import_contents).reject(&:blank?)
rows = rows.take(FollowLimitValidator.limit_for_account(@import.account)) if @import.type == 'following'
rows
end
end
1 change: 1 addition & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,7 @@ en:
tips: Tips
title: Welcome aboard, %{name}!
users:
follow_limit_reached: You cannot follow more than %{limit} people
invalid_email: The e-mail address is invalid
invalid_otp_token: Invalid two-factor code
otp_lost_help_html: If you lost access to both, you may get in touch with %{email}
Expand Down
14 changes: 14 additions & 0 deletions spec/models/follow_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@
follow.valid?
expect(follow).to model_have_error_on_field(:target_account)
end

it 'is invalid if account already follows too many people' do
alice.update(following_count: FollowLimitValidator::LIMIT)

expect(subject).to_not be_valid
expect(subject).to model_have_error_on_field(:base)
end

it 'is valid if account is only on the brink of following too many people' do
alice.update(following_count: FollowLimitValidator::LIMIT - 1)

expect(subject).to be_valid
expect(subject).to_not model_have_error_on_field(:base)
end
end

describe 'recent' do
Expand Down

0 comments on commit 10e9014

Please sign in to comment.