Updated

app/models / rate_limit.rb

D
312 lines of codes
43 methods
4.5 complexity/method
16 churn
193.97 complexity
68 duplications
require 'ipaddr' class RateLimit < ActiveRecord::Base
  1. RateLimit has no descriptive comment
  2. RateLimit has 6 constants
  3. RateLimit has at least 6 instance variables
  4. RateLimit has at least 43 methods
GLOB_PATTERN = /^(\*\*\.|\*\.)/ RECURSIVE_GLOB = "**." RECURSIVE_PATTERN = "(?:[-a-z0-9]+\\.)+" SINGLE_GLOB = "*." SINGLE_PATTERN = "(?:[-a-z0-9]+\\.)" DOMAIN_PATTERN = /^(?:[a-z0-9][a-z0-9-]{0,61}[a-z0-9]\.)+[a-z]{2,}$/ validates :burst_rate, presence: true, numericality: { only_integer: true, greater_than: 0 } validates :burst_period, presence: true, numericality: { only_integer: true, greater_than: 0 } validates :sustained_rate, presence: true, numericality: { only_integer: true, greater_than: 0 } validates :sustained_period, presence: true, numericality: { only_integer: true, greater_than: 0 } validates :allowed_domains, length: { maximum: 10000, allow_blank: true } validates :allowed_ips, length: { maximum: 10000, allow_blank: true } validates :blocked_domains, length: { maximum: 50000, allow_blank: true } validates :blocked_ips, length: { maximum: 50000, allow_blank: true } validates :countries, length: { maximum: 2000, allow_blank: true } validates :country_burst_rate, presence: true, numericality: { only_integer: true, greater_than: 0 } validates :country_sustained_rate, presence: true, numericality: { only_integer: true, greater_than: 0 } validates :threshold_for_form_entry, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 } validates :threshold_for_logging_trending_items, presence: true, numericality: { only_integer: true, greater_than: 0 } validates :threshold_for_notifying_trending_items, presence: true, numericality: { only_integer: true, greater_than: 0 } validates :trending_items_notification_url, length: { maximum: 100, allow_blank: true } validates :ignored_domains, length: { maximum: 10000, allow_blank: true } validate do unless sustained_rate.nil? || burst_rate.nil? if sustained_rate <= burst_rate errors.add :sustained_rate, "Sustained rate must be greater than burst rate" end end unless sustained_period.nil? || burst_period.nil? if sustained_period <= burst_period errors.add :sustained_period, "Sustained period must be greater than burst period" end end unless country_sustained_rate.nil? || country_burst_rate.nil? if country_sustained_rate <= country_burst_rate errors.add :country_sustained_rate, "Country sustained rate must be greater than country burst rate" end end begin allowed_domains_list rescue StandardError => e
  1. RateLimit has the variable name 'e' Locations: 0 1 2 3 4
errors.add :allowed_domains, :invalid end begin allowed_ips_list rescue StandardError => e
  1. RateLimit has the variable name 'e' Locations: 0 1 2 3 4
errors.add :allowed_ips, :invalid end begin blocked_domains_list rescue StandardError => e
  1. RateLimit has the variable name 'e' Locations: 0 1 2 3 4
errors.add :blocked_domains, :invalid end begin blocked_ips_list rescue StandardError => e
  1. RateLimit has the variable name 'e' Locations: 0 1 2 3 4
errors.add :blocked_ips, :invalid end begin ignored_domains_list rescue StandardError => e
  1. RateLimit has the variable name 'e' Locations: 0 1 2 3 4
errors.add :ignored_domains, :invalid end end def reload
  1. RateLimit#reload has approx 7 statements
@allowed_domains_list = nil @blocked_domains_list = nil @allowed_ips_list = nil @blocked_ips_list = nil @allowed_countries = nil @ignored_domains_list = nil super end def exceeded?(signature)
  1. RateLimit#exceeded? has approx 8 statements
return true unless threshold_reached?(signature) return true if ip_blocked?(signature.ip_address)
  1. RateLimit#exceeded? calls 'signature.ip_address' 4 times Locations: 0 1 2 3
return true if ip_geoblocked?(signature.ip_address)
  1. RateLimit#exceeded? calls 'signature.ip_address' 4 times Locations: 0 1 2 3
return true if domain_blocked?(signature.domain)
  1. RateLimit#exceeded? calls 'signature.domain' 2 times Locations: 0 1
return false if ip_allowed?(signature.ip_address)
  1. RateLimit#exceeded? calls 'signature.ip_address' 4 times Locations: 0 1 2 3
return false if domain_allowed?(signature.domain)
  1. RateLimit#exceeded? calls 'signature.domain' 2 times Locations: 0 1
if use_country_rate?(signature.ip_address)
  1. RateLimit#exceeded? calls 'signature.ip_address' 4 times Locations: 0 1 2 3
country_rate_exceeded?(signature) else rate_exceeded?(signature) end end def ignore_domain?(domain) domain_blocked?(domain) || domain_allowed?(domain) end def ignore_ip?(ip) ip_blocked?(ip) || ip_allowed?(ip) || ip_geoblocked?(ip) end def allowed_domains=(value) @allowed_domains_list = nil super(normalize_lines(value)) end def allowed_domains_list @allowed_domains_list ||= build_allowed_domains end def blocked_domains=(value) @blocked_domains_list = nil super(normalize_lines(value)) end def blocked_domains_list @blocked_domains_list ||= build_blocked_domains end def allowed_ips=(value) @allowed_ips_list = nil super(normalize_lines(value)) end def allowed_ips_list @allowed_ips_list ||= build_allowed_ips end def blocked_ips=(value) @blocked_ips_list = nil super(normalize_lines(value)) end def blocked_ips_list @blocked_ips_list ||= build_blocked_ips end def allowed_countries @allowed_countries ||= build_allowed_countries end def countries=(value) @allowed_countries = nil super(normalize_lines(value)) end def ignored_domains=(value) @ignored_domains_list = nil super(normalize_lines(value)) end def ignored_domains_list @ignored_domains_list ||= build_ignored_domains end private def strip_comments(list)
  1. RateLimit#strip_comments doesn't depend on instance state (maybe move it to another class?)
list.gsub(/#.*$/, '') end def strip_blank_lines(list)
  1. RateLimit#strip_blank_lines doesn't depend on instance state (maybe move it to another class?)
list.each_line.reject(&:blank?) end def build_allowed_domains
  1. Similar code found in 2 nodes Locations: 0 1
domains = strip_comments(allowed_domains) domains = strip_blank_lines(domains) domains.map{ |l| %r[\A#{convert_glob(l.strip)}\z] }
  1. RateLimit#build_allowed_domains has the variable name 'l'
end def domain_allowed?(domain) allowed_domains_list.any?{ |d| d === domain }
  1. RateLimit#domain_allowed? has the variable name 'd'
end def build_blocked_domains
  1. Similar code found in 2 nodes Locations: 0 1
domains = strip_comments(blocked_domains) domains = strip_blank_lines(domains) domains.map{ |l| %r[\A#{convert_glob(l.strip)}\z] }
  1. RateLimit#build_blocked_domains has the variable name 'l'
end def domain_blocked?(domain) blocked_domains_list.any?{ |d| d === domain }
  1. RateLimit#domain_blocked? has the variable name 'd'
end def build_allowed_ips
  1. Similar code found in 2 nodes Locations: 0 1
ips = strip_comments(allowed_ips) ips = strip_blank_lines(ips) ips.map{ |l| IPAddr.new(l.strip) }
  1. RateLimit#build_allowed_ips has the variable name 'l'
end def ip_allowed?(ip) allowed_ips_list.any?{ |i| i.include?(ip) }
  1. RateLimit#ip_allowed? has the variable name 'i'
end def build_blocked_ips
  1. Similar code found in 2 nodes Locations: 0 1
ips = strip_comments(blocked_ips) ips = strip_blank_lines(ips) ips.map{ |l| IPAddr.new(l.strip) }
  1. RateLimit#build_blocked_ips has the variable name 'l'
end def ip_blocked?(ip) blocked_ips_list.any?{ |i| i.include?(ip) }
  1. RateLimit#ip_blocked? has the variable name 'i'
end def build_ignored_domains strip_blank_lines(strip_comments(ignored_domains)).map { |d| validate_domain!(d.strip) }
  1. RateLimit#build_ignored_domains has the variable name 'd'
end def build_allowed_countries strip_blank_lines(strip_comments(countries)).map(&:strip) end def ip_geoblocked?(ip) geoblocking_enabled? && country_blocked?(ip) end def country_blocked?(ip) allowed_countries.exclude?(country_for_ip(ip)) end def country_for_ip(ip) result = geoip_db.lookup(ip) if result.found?
  1. RateLimit#country_for_ip refers to 'result' more than self (maybe move it to another class?) Locations: 0 1
result.country.name
  1. RateLimit#country_for_ip refers to 'result' more than self (maybe move it to another class?) Locations: 0 1
else "UNKNOWN" end end def geoip_db @geoip_db ||= MaxMindDB.new(ENV.fetch('GEOIP_DB_PATH')) end def convert_glob(pattern)
  1. RateLimit#convert_glob doesn't depend on instance state (maybe move it to another class?)
pattern.gsub(GLOB_PATTERN) do |match| if match == RECURSIVE_GLOB RECURSIVE_PATTERN elsif match == SINGLE_GLOB SINGLE_PATTERN end end end def validate_domain!(domain)
  1. RateLimit has missing safe method 'validate_domain!'
if domain =~ DOMAIN_PATTERN
  1. RateLimit#validate_domain! refers to 'domain' more than self (maybe move it to another class?) Locations: 0 1
domain else raise ArgumentError, "Invalid domain: #{domain.inspect}"
  1. RateLimit#validate_domain! refers to 'domain' more than self (maybe move it to another class?) Locations: 0 1
end end def normalize_lines(value)
  1. RateLimit#normalize_lines doesn't depend on instance state (maybe move it to another class?)
value.to_s.strip.gsub(/\r\n|\r/, "\n") end def use_country_rate?(ip) if country_rate_limits_enabled? allowed_countries.include?(country_for_ip(ip)) else false end end def country_rate_exceeded?(signature) country_burst_rate_exceeded?(signature) || country_sustained_rate_exceeded?(signature) end def country_burst_rate_exceeded?(signature) country_burst_rate < signature.rate(burst_period) end def country_sustained_rate_exceeded?(signature) country_sustained_rate < signature.rate(sustained_period) end def rate_exceeded?(signature) burst_rate_exceeded?(signature) || sustained_rate_exceeded?(signature) end def burst_rate_exceeded?(signature) burst_rate < signature.rate(burst_period) end def sustained_rate_exceeded?(signature) sustained_rate < signature.rate(sustained_period) end def threshold_reached?(signature) return true unless threshold_for_form_entry? return false unless signature.image_loaded_at?
  1. RateLimit#threshold_reached? refers to 'signature' more than self (maybe move it to another class?) Locations: 0 1 2 3
return false unless signature.form_requested_at?
  1. RateLimit#threshold_reached? refers to 'signature' more than self (maybe move it to another class?) Locations: 0 1 2 3
return false if signature.form_token_reused?
  1. RateLimit#threshold_reached? refers to 'signature' more than self (maybe move it to another class?) Locations: 0 1 2 3
signature.form_duration > threshold_for_form_entry
  1. RateLimit#threshold_reached? refers to 'signature' more than self (maybe move it to another class?) Locations: 0 1 2 3
end end