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:
		
							parent
							
								
									186024a058
								
							
						
					
					
						commit
						a46ab86adf
					
				
					 6 changed files with 47 additions and 1 deletions
				
			
		| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue