rack-attack 4.3.1 → 5.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.md +230 -113
- data/Rakefile +11 -3
- data/bin/setup +8 -0
- data/lib/rack/attack.rb +121 -48
- data/lib/rack/attack/allow2ban.rb +2 -1
- data/lib/rack/attack/{whitelist.rb → blocklist.rb} +2 -3
- data/lib/rack/attack/cache.rb +24 -5
- data/lib/rack/attack/check.rb +6 -8
- data/lib/rack/attack/fail2ban.rb +3 -2
- data/lib/rack/attack/path_normalizer.rb +6 -11
- data/lib/rack/attack/request.rb +1 -1
- data/lib/rack/attack/{blacklist.rb → safelist.rb} +2 -4
- data/lib/rack/attack/store_proxy.rb +13 -12
- data/lib/rack/attack/store_proxy/dalli_proxy.rb +2 -3
- data/lib/rack/attack/store_proxy/mem_cache_proxy.rb +50 -0
- data/lib/rack/attack/store_proxy/mem_cache_store_proxy.rb +19 -0
- data/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb +35 -0
- data/lib/rack/attack/store_proxy/redis_proxy.rb +54 -0
- data/lib/rack/attack/store_proxy/redis_store_proxy.rb +5 -24
- data/lib/rack/attack/throttle.rb +16 -12
- data/lib/rack/attack/track.rb +3 -3
- data/lib/rack/attack/version.rb +1 -1
- data/spec/acceptance/allow2ban_spec.rb +71 -0
- data/spec/acceptance/blocking_ip_spec.rb +38 -0
- data/spec/acceptance/blocking_spec.rb +41 -0
- data/spec/acceptance/blocking_subnet_spec.rb +44 -0
- data/spec/acceptance/cache_store_config_for_allow2ban_spec.rb +126 -0
- data/spec/acceptance/cache_store_config_for_fail2ban_spec.rb +121 -0
- data/spec/acceptance/cache_store_config_for_throttle_spec.rb +48 -0
- data/spec/acceptance/cache_store_config_with_rails_spec.rb +31 -0
- data/spec/acceptance/customizing_blocked_response_spec.rb +41 -0
- data/spec/acceptance/customizing_throttled_response_spec.rb +59 -0
- data/spec/acceptance/extending_request_object_spec.rb +34 -0
- data/spec/acceptance/fail2ban_spec.rb +76 -0
- data/spec/acceptance/safelisting_ip_spec.rb +48 -0
- data/spec/acceptance/safelisting_spec.rb +53 -0
- data/spec/acceptance/safelisting_subnet_spec.rb +48 -0
- data/spec/acceptance/stores/active_support_dalli_store_spec.rb +19 -0
- data/spec/acceptance/stores/active_support_mem_cache_store_pooled_spec.rb +22 -0
- data/spec/acceptance/stores/active_support_mem_cache_store_spec.rb +18 -0
- data/spec/acceptance/stores/active_support_memory_store_spec.rb +16 -0
- data/spec/acceptance/stores/active_support_redis_cache_store_pooled_spec.rb +18 -0
- data/spec/acceptance/stores/active_support_redis_cache_store_spec.rb +18 -0
- data/spec/acceptance/stores/active_support_redis_store_spec.rb +18 -0
- data/spec/acceptance/stores/connection_pool_dalli_client_spec.rb +22 -0
- data/spec/acceptance/stores/dalli_client_spec.rb +19 -0
- data/spec/acceptance/stores/redis_spec.rb +20 -0
- data/spec/acceptance/stores/redis_store_spec.rb +18 -0
- data/spec/acceptance/throttling_spec.rb +159 -0
- data/spec/acceptance/track_spec.rb +27 -0
- data/spec/acceptance/track_throttle_spec.rb +53 -0
- data/spec/allow2ban_spec.rb +10 -9
- data/spec/fail2ban_spec.rb +12 -10
- data/spec/integration/offline_spec.rb +21 -23
- data/spec/rack_attack_dalli_proxy_spec.rb +0 -2
- data/spec/rack_attack_request_spec.rb +2 -2
- data/spec/rack_attack_spec.rb +53 -18
- data/spec/rack_attack_throttle_spec.rb +45 -13
- data/spec/rack_attack_track_spec.rb +11 -8
- data/spec/spec_helper.rb +35 -14
- data/spec/support/cache_store_helper.rb +82 -0
- metadata +161 -61
- data/spec/integration/rack_attack_cache_spec.rb +0 -119
data/Rakefile
CHANGED
|
@@ -2,6 +2,9 @@ require "rubygems"
|
|
|
2
2
|
require "bundler/setup"
|
|
3
3
|
require 'bundler/gem_tasks'
|
|
4
4
|
require 'rake/testtask'
|
|
5
|
+
require "rubocop/rake_task"
|
|
6
|
+
|
|
7
|
+
RuboCop::RakeTask.new
|
|
5
8
|
|
|
6
9
|
namespace :test do
|
|
7
10
|
Rake::TestTask.new(:units) do |t|
|
|
@@ -11,9 +14,14 @@ namespace :test do
|
|
|
11
14
|
Rake::TestTask.new(:integration) do |t|
|
|
12
15
|
t.pattern = "spec/integration/*_spec.rb"
|
|
13
16
|
end
|
|
17
|
+
|
|
18
|
+
Rake::TestTask.new(:acceptance) do |t|
|
|
19
|
+
t.pattern = "spec/acceptance/**/*_spec.rb"
|
|
20
|
+
end
|
|
14
21
|
end
|
|
15
22
|
|
|
16
|
-
|
|
17
|
-
|
|
23
|
+
Rake::TestTask.new(:test) do |t|
|
|
24
|
+
t.pattern = "spec/**/*_spec.rb"
|
|
25
|
+
end
|
|
18
26
|
|
|
19
|
-
task :default => :test
|
|
27
|
+
task :default => [:rubocop, :test]
|
data/bin/setup
ADDED
data/lib/rack/attack.rb
CHANGED
|
@@ -1,31 +1,60 @@
|
|
|
1
1
|
require 'rack'
|
|
2
2
|
require 'forwardable'
|
|
3
|
+
require 'rack/attack/path_normalizer'
|
|
4
|
+
require 'rack/attack/request'
|
|
5
|
+
require "ipaddr"
|
|
3
6
|
|
|
4
7
|
class Rack::Attack
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
autoload :
|
|
9
|
-
autoload :
|
|
10
|
-
autoload :
|
|
11
|
-
autoload :
|
|
12
|
-
autoload :
|
|
13
|
-
autoload :
|
|
14
|
-
autoload :
|
|
15
|
-
autoload :
|
|
16
|
-
autoload :
|
|
17
|
-
autoload :
|
|
8
|
+
class MisconfiguredStoreError < StandardError; end
|
|
9
|
+
class MissingStoreError < StandardError; end
|
|
10
|
+
|
|
11
|
+
autoload :Cache, 'rack/attack/cache'
|
|
12
|
+
autoload :Check, 'rack/attack/check'
|
|
13
|
+
autoload :Throttle, 'rack/attack/throttle'
|
|
14
|
+
autoload :Safelist, 'rack/attack/safelist'
|
|
15
|
+
autoload :Blocklist, 'rack/attack/blocklist'
|
|
16
|
+
autoload :Track, 'rack/attack/track'
|
|
17
|
+
autoload :StoreProxy, 'rack/attack/store_proxy'
|
|
18
|
+
autoload :DalliProxy, 'rack/attack/store_proxy/dalli_proxy'
|
|
19
|
+
autoload :MemCacheProxy, 'rack/attack/store_proxy/mem_cache_proxy'
|
|
20
|
+
autoload :MemCacheStoreProxy, 'rack/attack/store_proxy/mem_cache_store_proxy'
|
|
21
|
+
autoload :RedisProxy, 'rack/attack/store_proxy/redis_proxy'
|
|
22
|
+
autoload :RedisStoreProxy, 'rack/attack/store_proxy/redis_store_proxy'
|
|
23
|
+
autoload :RedisCacheStoreProxy, 'rack/attack/store_proxy/redis_cache_store_proxy'
|
|
24
|
+
autoload :Fail2Ban, 'rack/attack/fail2ban'
|
|
25
|
+
autoload :Allow2Ban, 'rack/attack/allow2ban'
|
|
18
26
|
|
|
19
27
|
class << self
|
|
28
|
+
attr_accessor :notifier, :blocklisted_response, :throttled_response
|
|
20
29
|
|
|
21
|
-
|
|
30
|
+
def safelist(name, &block)
|
|
31
|
+
self.safelists[name] = Safelist.new(name, block)
|
|
32
|
+
end
|
|
22
33
|
|
|
23
34
|
def whitelist(name, &block)
|
|
24
|
-
|
|
35
|
+
warn "[DEPRECATION] 'Rack::Attack.whitelist' is deprecated. Please use 'safelist' instead."
|
|
36
|
+
safelist(name, &block)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def blocklist(name, &block)
|
|
40
|
+
self.blocklists[name] = Blocklist.new(name, block)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def blocklist_ip(ip_address)
|
|
44
|
+
@ip_blocklists ||= []
|
|
45
|
+
ip_blocklist_proc = lambda { |request| IPAddr.new(ip_address).include?(IPAddr.new(request.ip)) }
|
|
46
|
+
@ip_blocklists << Blocklist.new(nil, ip_blocklist_proc)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def safelist_ip(ip_address)
|
|
50
|
+
@ip_safelists ||= []
|
|
51
|
+
ip_safelist_proc = lambda { |request| IPAddr.new(ip_address).include?(IPAddr.new(request.ip)) }
|
|
52
|
+
@ip_safelists << Safelist.new(nil, ip_safelist_proc)
|
|
25
53
|
end
|
|
26
54
|
|
|
27
55
|
def blacklist(name, &block)
|
|
28
|
-
|
|
56
|
+
warn "[DEPRECATION] 'Rack::Attack.blacklist' is deprecated. Please use 'blocklist' instead."
|
|
57
|
+
blocklist(name, &block)
|
|
29
58
|
end
|
|
30
59
|
|
|
31
60
|
def throttle(name, options, &block)
|
|
@@ -36,55 +65,102 @@ class Rack::Attack
|
|
|
36
65
|
self.tracks[name] = Track.new(name, options, block)
|
|
37
66
|
end
|
|
38
67
|
|
|
39
|
-
def
|
|
40
|
-
|
|
68
|
+
def safelists; @safelists ||= {}; end
|
|
69
|
+
|
|
70
|
+
def blocklists; @blocklists ||= {}; end
|
|
71
|
+
|
|
41
72
|
def throttles; @throttles ||= {}; end
|
|
73
|
+
|
|
42
74
|
def tracks; @tracks ||= {}; end
|
|
43
75
|
|
|
44
|
-
def
|
|
45
|
-
whitelists.
|
|
46
|
-
|
|
47
|
-
end
|
|
76
|
+
def whitelists
|
|
77
|
+
warn "[DEPRECATION] 'Rack::Attack.whitelists' is deprecated. Please use 'safelists' instead."
|
|
78
|
+
safelists
|
|
48
79
|
end
|
|
49
80
|
|
|
50
|
-
def
|
|
51
|
-
blacklists.
|
|
52
|
-
|
|
53
|
-
|
|
81
|
+
def blacklists
|
|
82
|
+
warn "[DEPRECATION] 'Rack::Attack.blacklists' is deprecated. Please use 'blocklists' instead."
|
|
83
|
+
blocklists
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def safelisted?(request)
|
|
87
|
+
ip_safelists.any? { |safelist| safelist.matched_by?(request) } ||
|
|
88
|
+
safelists.any? { |_name, safelist| safelist.matched_by?(request) }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def whitelisted?(request)
|
|
92
|
+
warn "[DEPRECATION] 'Rack::Attack.whitelisted?' is deprecated. Please use 'safelisted?' instead."
|
|
93
|
+
safelisted?(request)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def blocklisted?(request)
|
|
97
|
+
ip_blocklists.any? { |blocklist| blocklist.matched_by?(request) } ||
|
|
98
|
+
blocklists.any? { |_name, blocklist| blocklist.matched_by?(request) }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def blacklisted?(request)
|
|
102
|
+
warn "[DEPRECATION] 'Rack::Attack.blacklisted?' is deprecated. Please use 'blocklisted?' instead."
|
|
103
|
+
blocklisted?(request)
|
|
54
104
|
end
|
|
55
105
|
|
|
56
|
-
def throttled?(
|
|
57
|
-
throttles.any? do |
|
|
58
|
-
throttle
|
|
106
|
+
def throttled?(request)
|
|
107
|
+
throttles.any? do |_name, throttle|
|
|
108
|
+
throttle.matched_by?(request)
|
|
59
109
|
end
|
|
60
110
|
end
|
|
61
111
|
|
|
62
|
-
def tracked?(
|
|
63
|
-
tracks.each_value do |
|
|
64
|
-
|
|
112
|
+
def tracked?(request)
|
|
113
|
+
tracks.each_value do |track|
|
|
114
|
+
track.matched_by?(request)
|
|
65
115
|
end
|
|
66
116
|
end
|
|
67
117
|
|
|
68
|
-
def instrument(
|
|
69
|
-
notifier.instrument('rack.attack',
|
|
118
|
+
def instrument(request)
|
|
119
|
+
notifier.instrument('rack.attack', request) if notifier
|
|
70
120
|
end
|
|
71
121
|
|
|
72
122
|
def cache
|
|
73
123
|
@cache ||= Cache.new
|
|
74
124
|
end
|
|
75
125
|
|
|
126
|
+
def clear_configuration
|
|
127
|
+
@safelists, @blocklists, @throttles, @tracks = {}, {}, {}, {}
|
|
128
|
+
@ip_blocklists = []
|
|
129
|
+
@ip_safelists = []
|
|
130
|
+
end
|
|
131
|
+
|
|
76
132
|
def clear!
|
|
77
|
-
|
|
133
|
+
warn "[DEPRECATION] Rack::Attack.clear! is deprecated. Please use Rack::Attack.clear_configuration instead"
|
|
134
|
+
clear_configuration
|
|
78
135
|
end
|
|
79
136
|
|
|
137
|
+
def blacklisted_response=(res)
|
|
138
|
+
warn "[DEPRECATION] 'Rack::Attack.blacklisted_response=' is deprecated. Please use 'blocklisted_response=' instead."
|
|
139
|
+
self.blocklisted_response = res
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def blacklisted_response
|
|
143
|
+
warn "[DEPRECATION] 'Rack::Attack.blacklisted_response' is deprecated. Please use 'blocklisted_response' instead."
|
|
144
|
+
blocklisted_response
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
private
|
|
148
|
+
|
|
149
|
+
def ip_blocklists
|
|
150
|
+
@ip_blocklists ||= []
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def ip_safelists
|
|
154
|
+
@ip_safelists ||= []
|
|
155
|
+
end
|
|
80
156
|
end
|
|
81
157
|
|
|
82
158
|
# Set defaults
|
|
83
159
|
@notifier = ActiveSupport::Notifications if defined?(ActiveSupport::Notifications)
|
|
84
|
-
@
|
|
85
|
-
@throttled_response = lambda {|env|
|
|
160
|
+
@blocklisted_response = lambda { |_env| [403, { 'Content-Type' => 'text/plain' }, ["Forbidden\n"]] }
|
|
161
|
+
@throttled_response = lambda { |env|
|
|
86
162
|
retry_after = (env['rack.attack.match_data'] || {})[:period]
|
|
87
|
-
[429, {'Content-Type' => 'text/plain', 'Retry-After' => retry_after.to_s}, ["Retry later\n"]]
|
|
163
|
+
[429, { 'Content-Type' => 'text/plain', 'Retry-After' => retry_after.to_s }, ["Retry later\n"]]
|
|
88
164
|
}
|
|
89
165
|
|
|
90
166
|
def initialize(app)
|
|
@@ -93,23 +169,20 @@ class Rack::Attack
|
|
|
93
169
|
|
|
94
170
|
def call(env)
|
|
95
171
|
env['PATH_INFO'] = PathNormalizer.normalize_path(env['PATH_INFO'])
|
|
96
|
-
|
|
172
|
+
request = Rack::Attack::Request.new(env)
|
|
97
173
|
|
|
98
|
-
if
|
|
174
|
+
if safelisted?(request)
|
|
99
175
|
@app.call(env)
|
|
100
|
-
elsif
|
|
101
|
-
self.class.
|
|
102
|
-
elsif throttled?(
|
|
176
|
+
elsif blocklisted?(request)
|
|
177
|
+
self.class.blocklisted_response.call(env)
|
|
178
|
+
elsif throttled?(request)
|
|
103
179
|
self.class.throttled_response.call(env)
|
|
104
180
|
else
|
|
105
|
-
tracked?(
|
|
181
|
+
tracked?(request)
|
|
106
182
|
@app.call(env)
|
|
107
183
|
end
|
|
108
184
|
end
|
|
109
185
|
|
|
110
186
|
extend Forwardable
|
|
111
|
-
def_delegators self, :
|
|
112
|
-
:blacklisted?,
|
|
113
|
-
:throttled?,
|
|
114
|
-
:tracked?
|
|
187
|
+
def_delegators self, :safelisted?, :blocklisted?, :throttled?, :tracked?
|
|
115
188
|
end
|
|
@@ -3,11 +3,12 @@ module Rack
|
|
|
3
3
|
class Allow2Ban < Fail2Ban
|
|
4
4
|
class << self
|
|
5
5
|
protected
|
|
6
|
+
|
|
6
7
|
def key_prefix
|
|
7
8
|
'allow2ban'
|
|
8
9
|
end
|
|
9
10
|
|
|
10
|
-
# everything the same here except we
|
|
11
|
+
# everything is the same here except we only return true
|
|
11
12
|
# (blocking the request) if they have tripped the limit.
|
|
12
13
|
def fail!(discriminator, bantime, findtime, maxretry)
|
|
13
14
|
count = cache.count("#{key_prefix}:count:#{discriminator}", findtime)
|
data/lib/rack/attack/cache.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
module Rack
|
|
2
2
|
class Attack
|
|
3
3
|
class Cache
|
|
4
|
-
|
|
5
4
|
attr_accessor :prefix
|
|
5
|
+
attr_reader :last_epoch_time
|
|
6
6
|
|
|
7
7
|
def initialize
|
|
8
8
|
self.store = ::Rails.cache if defined?(::Rails.cache)
|
|
@@ -20,6 +20,9 @@ module Rack
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def read(unprefixed_key)
|
|
23
|
+
enforce_store_presence!
|
|
24
|
+
enforce_store_method_presence!(:read)
|
|
25
|
+
|
|
23
26
|
store.read("#{prefix}:#{unprefixed_key}")
|
|
24
27
|
end
|
|
25
28
|
|
|
@@ -39,22 +42,38 @@ module Rack
|
|
|
39
42
|
private
|
|
40
43
|
|
|
41
44
|
def key_and_expiry(unprefixed_key, period)
|
|
42
|
-
|
|
43
|
-
# Add 1 to expires_in to avoid timing error:
|
|
44
|
-
expires_in = (period - (
|
|
45
|
-
["#{prefix}:#{(
|
|
45
|
+
@last_epoch_time = Time.now.to_i
|
|
46
|
+
# Add 1 to expires_in to avoid timing error: https://git.io/i1PHXA
|
|
47
|
+
expires_in = (period - (@last_epoch_time % period) + 1).to_i
|
|
48
|
+
["#{prefix}:#{(@last_epoch_time / period).to_i}:#{unprefixed_key}", expires_in]
|
|
46
49
|
end
|
|
47
50
|
|
|
48
51
|
def do_count(key, expires_in)
|
|
52
|
+
enforce_store_presence!
|
|
53
|
+
enforce_store_method_presence!(:increment)
|
|
54
|
+
|
|
49
55
|
result = store.increment(key, 1, :expires_in => expires_in)
|
|
50
56
|
|
|
51
57
|
# NB: Some stores return nil when incrementing uninitialized values
|
|
52
58
|
if result.nil?
|
|
59
|
+
enforce_store_method_presence!(:write)
|
|
60
|
+
|
|
53
61
|
store.write(key, 1, :expires_in => expires_in)
|
|
54
62
|
end
|
|
55
63
|
result || 1
|
|
56
64
|
end
|
|
57
65
|
|
|
66
|
+
def enforce_store_presence!
|
|
67
|
+
if store.nil?
|
|
68
|
+
raise Rack::Attack::MissingStoreError
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def enforce_store_method_presence!(method_name)
|
|
73
|
+
if !store.respond_to?(method_name)
|
|
74
|
+
raise Rack::Attack::MisconfiguredStoreError, "Configured store #{store.class.name} doesn't respond to ##{method_name} method"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
58
77
|
end
|
|
59
78
|
end
|
|
60
79
|
end
|
data/lib/rack/attack/check.rb
CHANGED
|
@@ -7,17 +7,15 @@ module Rack
|
|
|
7
7
|
@type = options.fetch(:type, nil)
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
def
|
|
11
|
-
block
|
|
10
|
+
def matched_by?(request)
|
|
11
|
+
block.call(request).tap do |match|
|
|
12
12
|
if match
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
Rack::Attack.instrument(
|
|
13
|
+
request.env["rack.attack.matched"] = name
|
|
14
|
+
request.env["rack.attack.match_type"] = type
|
|
15
|
+
Rack::Attack.instrument(request)
|
|
16
16
|
end
|
|
17
|
-
|
|
17
|
+
end
|
|
18
18
|
end
|
|
19
|
-
|
|
20
19
|
end
|
|
21
20
|
end
|
|
22
21
|
end
|
|
23
|
-
|
data/lib/rack/attack/fail2ban.rb
CHANGED
|
@@ -8,7 +8,7 @@ module Rack
|
|
|
8
8
|
maxretry = options[:maxretry] or raise ArgumentError, "Must pass maxretry option"
|
|
9
9
|
|
|
10
10
|
if banned?(discriminator)
|
|
11
|
-
# Return true for
|
|
11
|
+
# Return true for blocklist
|
|
12
12
|
true
|
|
13
13
|
elsif yield
|
|
14
14
|
fail!(discriminator, bantime, findtime, maxretry)
|
|
@@ -27,6 +27,7 @@ module Rack
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
protected
|
|
30
|
+
|
|
30
31
|
def key_prefix
|
|
31
32
|
'fail2ban'
|
|
32
33
|
end
|
|
@@ -40,8 +41,8 @@ module Rack
|
|
|
40
41
|
true
|
|
41
42
|
end
|
|
42
43
|
|
|
43
|
-
|
|
44
44
|
private
|
|
45
|
+
|
|
45
46
|
def ban!(discriminator, bantime)
|
|
46
47
|
cache.write("#{key_prefix}:ban:#{discriminator}", 1, bantime)
|
|
47
48
|
end
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
class Rack::Attack
|
|
2
|
-
|
|
3
2
|
# When using Rack::Attack with a Rails app, developers expect the request path
|
|
4
3
|
# to be normalized. In particular, trailing slashes are stripped.
|
|
5
|
-
# (See
|
|
4
|
+
# (See https://git.io/v0rrR for implementation.)
|
|
6
5
|
#
|
|
7
6
|
# Look for an ActionDispatch utility class that Rails folks would expect
|
|
8
7
|
# to normalize request paths. If unavailable, use a fallback class that
|
|
@@ -15,13 +14,9 @@ class Rack::Attack
|
|
|
15
14
|
end
|
|
16
15
|
|
|
17
16
|
PathNormalizer = if defined?(::ActionDispatch::Journey::Router::Utils)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
else
|
|
24
|
-
FallbackPathNormalizer
|
|
25
|
-
end
|
|
26
|
-
|
|
17
|
+
# For Rails apps
|
|
18
|
+
::ActionDispatch::Journey::Router::Utils
|
|
19
|
+
else
|
|
20
|
+
FallbackPathNormalizer
|
|
21
|
+
end
|
|
27
22
|
end
|