1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
|
# frozen_string_literal: true
require "ipaddr"
module Rack
class Attack
class Configuration
DEFAULT_BLOCKLISTED_RESPONDER = lambda { |_req| [403, { 'content-type' => 'text/plain' }, ["Forbidden\n"]] }
DEFAULT_THROTTLED_RESPONDER = lambda do |req|
if Rack::Attack.configuration.throttled_response_retry_after_header
match_data = req.env['rack.attack.match_data']
now = match_data[:epoch_time]
retry_after = match_data[:period] - (now % match_data[:period])
[429, { 'content-type' => 'text/plain', 'retry-after' => retry_after.to_s }, ["Retry later\n"]]
else
[429, { 'content-type' => 'text/plain' }, ["Retry later\n"]]
end
end
attr_reader :safelists, :blocklists, :throttles, :anonymous_blocklists, :anonymous_safelists
attr_accessor :blocklisted_responder, :throttled_responder, :throttled_response_retry_after_header
attr_reader :blocklisted_response, :throttled_response # Keeping these for backwards compatibility
def blocklisted_response=(responder)
warn "[DEPRECATION] Rack::Attack.blocklisted_response is deprecated. "\
"Please use Rack::Attack.blocklisted_responder instead."
@blocklisted_response = responder
end
def throttled_response=(responder)
warn "[DEPRECATION] Rack::Attack.throttled_response is deprecated. "\
"Please use Rack::Attack.throttled_responder instead"
@throttled_response = responder
end
def initialize
set_defaults
end
def safelist(name = nil, &block)
safelist = Safelist.new(name, &block)
if name
@safelists[name] = safelist
else
@anonymous_safelists << safelist
end
end
def blocklist(name = nil, &block)
blocklist = Blocklist.new(name, &block)
if name
@blocklists[name] = blocklist
else
@anonymous_blocklists << blocklist
end
end
def blocklist_ip(ip_address)
@anonymous_blocklists << Blocklist.new { |request| IPAddr.new(ip_address).include?(IPAddr.new(request.ip)) }
end
def safelist_ip(ip_address)
@anonymous_safelists << Safelist.new { |request| IPAddr.new(ip_address).include?(IPAddr.new(request.ip)) }
end
def throttle(name, options, &block)
@throttles[name] = Throttle.new(name, options, &block)
end
def track(name, options = {}, &block)
@tracks[name] = Track.new(name, options, &block)
end
def safelisted?(request)
@anonymous_safelists.any? { |safelist| safelist.matched_by?(request) } ||
@safelists.any? { |_name, safelist| safelist.matched_by?(request) }
end
def blocklisted?(request)
@anonymous_blocklists.any? { |blocklist| blocklist.matched_by?(request) } ||
@blocklists.any? { |_name, blocklist| blocklist.matched_by?(request) }
end
def throttled?(request)
@throttles.any? do |_name, throttle|
throttle.matched_by?(request)
end
end
def tracked?(request)
@tracks.each_value do |track|
track.matched_by?(request)
end
end
def clear_configuration
set_defaults
end
private
def set_defaults
@safelists = {}
@blocklists = {}
@throttles = {}
@tracks = {}
@anonymous_blocklists = []
@anonymous_safelists = []
@throttled_response_retry_after_header = false
@blocklisted_responder = DEFAULT_BLOCKLISTED_RESPONDER
@throttled_responder = DEFAULT_THROTTLED_RESPONDER
# Deprecated: Keeping these for backwards compatibility
@blocklisted_response = nil
@throttled_response = nil
end
end
end
end
|