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
		Add a link
		
	
		Reference in a new issue