Limit the number of people that can be followed from one account (#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 #2311
This commit is contained in:
		@@ -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) }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								app/validators/follow_limit_validator.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								app/validators/follow_limit_validator.rb
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
@@ -37,6 +37,8 @@ class ImportWorker
 | 
			
		||||
  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
 | 
			
		||||
 
 | 
			
		||||
@@ -917,6 +917,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}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,20 @@ RSpec.describe Follow, type: :model do
 | 
			
		||||
      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
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user