1require 'ipaddr'
 
2
  • Class "RateLimit" has 301 lines. It should have 300 or less. » roodi
3class RateLimit < ActiveRecord::Base
 
4  GLOB_PATTERN = /^(\*\*\.|\*\.)/
 
5  RECURSIVE_GLOB = "**."
 
6  RECURSIVE_PATTERN = "(?:[-a-z0-9]+\\.)+"
 
7  SINGLE_GLOB = "*."
 
8  SINGLE_PATTERN = "(?:[-a-z0-9]+\\.)"
 
 9  DOMAIN_PATTERN = /^(?:[a-z0-9][a-z0-9-]{0,61}[a-z0-9]\.)+[a-z]{2,}$/
 
 
11  validates :burst_rate, presence: true, numericality: { only_integer: true, greater_than: 0 }
 
12  validates :burst_period, presence: true, numericality: { only_integer: true, greater_than: 0 }
 
13  validates :sustained_rate, presence: true, numericality: { only_integer: true, greater_than: 0 }
 
14  validates :sustained_period, presence: true, numericality: { only_integer: true, greater_than: 0 }
 
15  validates :allowed_domains, length: { maximum: 10000, allow_blank: true }
 
16  validates :allowed_ips, length: { maximum: 10000, allow_blank: true }
 
17  validates :blocked_domains, length: { maximum: 50000, allow_blank: true }
 
18  validates :blocked_ips, length: { maximum: 50000, allow_blank: true }
 
19  validates :countries, length: { maximum: 2000, allow_blank: true }
 
20  validates :country_burst_rate, presence: true, numericality: { only_integer: true, greater_than: 0 }
 
21  validates :country_sustained_rate, presence: true, numericality: { only_integer: true, greater_than: 0 }
 
22  validates :threshold_for_form_entry, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
 
23  validates :threshold_for_logging_trending_items, presence: true, numericality: { only_integer: true, greater_than: 0 }
 
24  validates :threshold_for_notifying_trending_items, presence: true, numericality: { only_integer: true, greater_than: 0 }
 
25  validates :trending_items_notification_url, length: { maximum: 100, allow_blank: true }
 
26  validates :ignored_domains, length: { maximum: 10000, allow_blank: true }
 
  • Block cyclomatic complexity is 15. It should be 4 or less. » roodi
28  validate do
 
29    unless sustained_rate.nil? || burst_rate.nil?
 
30      if sustained_rate <= burst_rate
 
31        errors.add :sustained_rate, "Sustained rate must be greater than burst rate"
 
32      end
 
33    end
 
 
35    unless sustained_period.nil? || burst_period.nil?
 
36      if sustained_period <= burst_period
 
37        errors.add :sustained_period, "Sustained period must be greater than burst period"
 
38      end
 
39    end
 
 
41    unless country_sustained_rate.nil? || country_burst_rate.nil?
 
42      if country_sustained_rate <= country_burst_rate
 
43        errors.add :country_sustained_rate, "Country sustained rate must be greater than country burst rate"
 
44      end
 
45    end
 
 
47    begin
 
48      allowed_domains_list
 
49    rescue StandardError => e
 
50      errors.add :allowed_domains, :invalid
 
51    end
 
 
53    begin
 
54      allowed_ips_list
 
55    rescue StandardError => e
 
56      errors.add :allowed_ips, :invalid
 
57    end
 
 
59    begin
 
60      blocked_domains_list
 
61    rescue StandardError => e
 
62      errors.add :blocked_domains, :invalid
 
63    end
 
 
65    begin
 
66      blocked_ips_list
 
67    rescue StandardError => e
 
68      errors.add :blocked_ips, :invalid
 
69    end
 
 
71    begin
 
72      ignored_domains_list
 
73    rescue StandardError => e
 
74      errors.add :ignored_domains, :invalid
 
75    end
 
76  end
 
  • TooManyStatements - has approx 7 statements » reek
  • Complexity 1 » saikuro
78  def reload
 
79    @allowed_domains_list = nil
 
80    @blocked_domains_list = nil
 
81    @allowed_ips_list = nil
 
82    @blocked_ips_list = nil
 
83    @allowed_countries = nil
 
84    @ignored_domains_list = nil
 
 
86    super
 
87  end
 
  • DuplicateMethodCall - calls 'signature.domain' 2 times » reek
  • DuplicateMethodCall - calls 'signature.ip_address' 4 times » reek
  • TooManyStatements - has approx 8 statements » reek
  • Complexity 8 » saikuro
89  def exceeded?(signature)
 
90    return true unless threshold_reached?(signature)
 
91    return true if ip_blocked?(signature.ip_address)
 
92    return true if ip_geoblocked?(signature.ip_address)
 
93    return true if domain_blocked?(signature.domain)
 
94    return false if ip_allowed?(signature.ip_address)
 
95    return false if domain_allowed?(signature.domain)
 
 
97    if use_country_rate?(signature.ip_address)
 
98      country_rate_exceeded?(signature)
 
 99    else
 
100      rate_exceeded?(signature)
 
101    end
 
102  end
 
  • Complexity 1 » saikuro
104  def ignore_domain?(domain)
 
105    domain_blocked?(domain) || domain_allowed?(domain)
 
106  end
 
  • Complexity 1 » saikuro
108  def ignore_ip?(ip)
 
109    ip_blocked?(ip) || ip_allowed?(ip) || ip_geoblocked?(ip)
 
110  end
 
  • Complexity 1 » saikuro
112  def allowed_domains=(value)
 
113    @allowed_domains_list = nil
 
114    super(normalize_lines(value))
 
115  end
 
  • Complexity 1 » saikuro
117  def allowed_domains_list
 
118    @allowed_domains_list ||= build_allowed_domains
 
119  end
 
  • Complexity 1 » saikuro
121  def blocked_domains=(value)
 
122    @blocked_domains_list = nil
 
123    super(normalize_lines(value))
 
124  end
 
  • Complexity 1 » saikuro
126  def blocked_domains_list
 
127    @blocked_domains_list ||= build_blocked_domains
 
128  end
 
  • Complexity 1 » saikuro
130  def allowed_ips=(value)
 
131    @allowed_ips_list = nil
 
132    super(normalize_lines(value))
 
133  end
 
  • Complexity 1 » saikuro
135  def allowed_ips_list
 
136    @allowed_ips_list ||= build_allowed_ips
 
137  end
 
  • Complexity 1 » saikuro
139  def blocked_ips=(value)
 
140    @blocked_ips_list = nil
 
141    super(normalize_lines(value))
 
142  end
 
  • Complexity 1 » saikuro
144  def blocked_ips_list
 
145    @blocked_ips_list ||= build_blocked_ips
 
146  end
 
  • Complexity 1 » saikuro
148  def allowed_countries
 
149    @allowed_countries ||= build_allowed_countries
 
150  end
 
  • Complexity 1 » saikuro
152  def countries=(value)
 
153    @allowed_countries = nil
 
154    super(normalize_lines(value))
 
155  end
 
  • Complexity 1 » saikuro
157  def ignored_domains=(value)
 
158    @ignored_domains_list = nil
 
159    super(normalize_lines(value))
 
160  end
 
  • Complexity 1 » saikuro
162  def ignored_domains_list
 
163    @ignored_domains_list ||= build_ignored_domains
 
164  end
 
 
166  private
 
  • UtilityFunction - doesn't depend on instance state (maybe move it to another class?) » reek
  • Complexity 1 » saikuro
168  def strip_comments(list)
 
169    list.gsub(/#.*$/, '')
 
170  end
 
  • UtilityFunction - doesn't depend on instance state (maybe move it to another class?) » reek
  • Complexity 1 » saikuro
172  def strip_blank_lines(list)
 
173    list.each_line.reject(&:blank?)
 
174  end
 
  • UncommunicativeVariableName - has the variable name 'l' » reek
  • Complexity 2 » saikuro
176  def build_allowed_domains
 
177    domains = strip_comments(allowed_domains)
 
178    domains = strip_blank_lines(domains)
 
 
180    domains.map{ |l| %r[\A#{convert_glob(l.strip)}\z] }
 
181  end
 
  • UncommunicativeVariableName - has the variable name 'd' » reek
  • Complexity 2 » saikuro
183  def domain_allowed?(domain)
 
184    allowed_domains_list.any?{ |d| d === domain }
 
185  end
 
  • UncommunicativeVariableName - has the variable name 'l' » reek
  • Complexity 2 » saikuro
187  def build_blocked_domains
 
188    domains = strip_comments(blocked_domains)
 
189    domains = strip_blank_lines(domains)
 
 
191    domains.map{ |l| %r[\A#{convert_glob(l.strip)}\z] }
 
192  end
 
  • UncommunicativeVariableName - has the variable name 'd' » reek
  • Complexity 2 » saikuro
194  def domain_blocked?(domain)
 
195    blocked_domains_list.any?{ |d| d === domain }
 
196  end
 
  • UncommunicativeVariableName - has the variable name 'l' » reek
  • Complexity 2 » saikuro
198  def build_allowed_ips
 
199    ips = strip_comments(allowed_ips)
 
200    ips = strip_blank_lines(ips)
 
 
202    ips.map{ |l| IPAddr.new(l.strip) }
 
203  end
 
  • UncommunicativeVariableName - has the variable name 'i' » reek
  • Complexity 2 » saikuro
205  def ip_allowed?(ip)
 
206    allowed_ips_list.any?{ |i| i.include?(ip) }
 
207  end
 
  • UncommunicativeVariableName - has the variable name 'l' » reek
  • Complexity 2 » saikuro
209  def build_blocked_ips
 
210    ips = strip_comments(blocked_ips)
 
211    ips = strip_blank_lines(ips)
 
 
213    ips.map{ |l| IPAddr.new(l.strip) }
 
214  end
 
  • UncommunicativeVariableName - has the variable name 'i' » reek
  • Complexity 2 » saikuro
216  def ip_blocked?(ip)
 
217    blocked_ips_list.any?{ |i| i.include?(ip) }
 
218  end
 
  • UncommunicativeVariableName - has the variable name 'd' » reek
  • Complexity 2 » saikuro
220  def build_ignored_domains
 
221    strip_blank_lines(strip_comments(ignored_domains)).map { |d| validate_domain!(d.strip) }
 
222  end
 
  • Complexity 1 » saikuro
224  def build_allowed_countries
 
225    strip_blank_lines(strip_comments(countries)).map(&:strip)
 
226  end
 
  • Complexity 1 » saikuro
228  def ip_geoblocked?(ip)
 
229    geoblocking_enabled? && country_blocked?(ip)
 
230  end
 
  • Complexity 1 » saikuro
232  def country_blocked?(ip)
 
233    allowed_countries.exclude?(country_for_ip(ip))
 
234  end
 
  • FeatureEnvy - refers to 'result' more than self (maybe move it to another class?) » reek
  • Complexity 2 » saikuro
236  def country_for_ip(ip)
 
237    result = geoip_db.lookup(ip)
 
 
239    if result.found?
 
240      result.country.name
 
241    else
 
242      "UNKNOWN"
 
243    end
 
244  end
 
  • Complexity 1 » saikuro
246  def geoip_db
 
247    @geoip_db ||= MaxMindDB.new(ENV.fetch('GEOIP_DB_PATH'))
 
248  end
 
  • UtilityFunction - doesn't depend on instance state (maybe move it to another class?) » reek
  • Complexity 4 » saikuro
250  def convert_glob(pattern)
 
251    pattern.gsub(GLOB_PATTERN) do |match|
 
252      if match == RECURSIVE_GLOB
 
253        RECURSIVE_PATTERN
 
254      elsif match == SINGLE_GLOB
 
255        SINGLE_PATTERN
 
256      end
 
257    end
 
258  end
 
  • FeatureEnvy - refers to 'domain' more than self (maybe move it to another class?) » reek
  • Complexity 2 » saikuro
260  def validate_domain!(domain)
 
261    if domain =~ DOMAIN_PATTERN
 
262      domain
 
263    else
 
264      raise ArgumentError, "Invalid domain: #{domain.inspect}"
 
265    end
 
266  end
 
  • UtilityFunction - doesn't depend on instance state (maybe move it to another class?) » reek
  • Complexity 1 » saikuro
268  def normalize_lines(value)
 
269    value.to_s.strip.gsub(/\r\n|\r/, "\n")
 
270  end
 
  • Complexity 2 » saikuro
272  def use_country_rate?(ip)
 
273    if country_rate_limits_enabled?
 
274      allowed_countries.include?(country_for_ip(ip))
 
275    else
 
276      false
 
277    end
 
278  end
 
  • Complexity 1 » saikuro
280  def country_rate_exceeded?(signature)
 
281    country_burst_rate_exceeded?(signature) || country_sustained_rate_exceeded?(signature)
 
282  end
 
  • Complexity 1 » saikuro
284  def country_burst_rate_exceeded?(signature)
 
285    country_burst_rate < signature.rate(burst_period)
 
286  end
 
  • Complexity 1 » saikuro
288  def country_sustained_rate_exceeded?(signature)
 
289    country_sustained_rate < signature.rate(sustained_period)
 
290  end
 
  • Complexity 1 » saikuro
292  def rate_exceeded?(signature)
 
293    burst_rate_exceeded?(signature) || sustained_rate_exceeded?(signature)
 
294  end
 
  • Complexity 1 » saikuro
296  def burst_rate_exceeded?(signature)
 
297    burst_rate < signature.rate(burst_period)
 
298  end
 
  • Complexity 1 » saikuro
300  def sustained_rate_exceeded?(signature)
 
301    sustained_rate < signature.rate(sustained_period)
 
302  end
 
  • FeatureEnvy - refers to 'signature' more than self (maybe move it to another class?) » reek
  • Complexity 5 » saikuro
304  def threshold_reached?(signature)
 
305    return true unless threshold_for_form_entry?
 
306    return false unless signature.image_loaded_at?
 
307    return false unless signature.form_requested_at?
 
308    return false if signature.form_token_reused?
 
 
310    signature.form_duration > threshold_for_form_entry
 
311  end
 
312end