rack-attack 5.4.0 → 6.2.0
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 +4 -4
- data/README.md +78 -27
- data/Rakefile +3 -1
- data/bin/setup +8 -0
- data/lib/rack/attack.rb +137 -148
- data/lib/rack/attack/allow2ban.rb +2 -0
- data/lib/rack/attack/blocklist.rb +3 -1
- data/lib/rack/attack/cache.rb +9 -4
- data/lib/rack/attack/check.rb +5 -2
- data/lib/rack/attack/fail2ban.rb +2 -0
- data/lib/rack/attack/path_normalizer.rb +22 -18
- data/lib/rack/attack/railtie.rb +21 -0
- data/lib/rack/attack/request.rb +2 -0
- data/lib/rack/attack/safelist.rb +3 -1
- data/lib/rack/attack/store_proxy.rb +12 -24
- data/lib/rack/attack/store_proxy/active_support_redis_store_proxy.rb +39 -0
- data/lib/rack/attack/store_proxy/dalli_proxy.rb +27 -13
- data/lib/rack/attack/store_proxy/mem_cache_store_proxy.rb +21 -0
- data/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb +23 -9
- data/lib/rack/attack/store_proxy/redis_proxy.rb +16 -10
- data/lib/rack/attack/store_proxy/redis_store_proxy.rb +5 -5
- data/lib/rack/attack/throttle.rb +12 -8
- data/lib/rack/attack/track.rb +9 -6
- data/lib/rack/attack/version.rb +3 -1
- data/spec/acceptance/allow2ban_spec.rb +2 -0
- data/spec/acceptance/blocking_ip_spec.rb +4 -2
- data/spec/acceptance/blocking_spec.rb +45 -3
- data/spec/acceptance/blocking_subnet_spec.rb +4 -2
- data/spec/acceptance/cache_store_config_for_allow2ban_spec.rb +50 -39
- data/spec/acceptance/cache_store_config_for_fail2ban_spec.rb +38 -29
- data/spec/acceptance/cache_store_config_for_throttle_spec.rb +2 -0
- data/spec/acceptance/cache_store_config_with_rails_spec.rb +2 -0
- data/spec/acceptance/customizing_blocked_response_spec.rb +2 -0
- data/spec/acceptance/customizing_throttled_response_spec.rb +2 -0
- data/spec/acceptance/extending_request_object_spec.rb +2 -0
- data/spec/acceptance/fail2ban_spec.rb +2 -0
- data/spec/acceptance/rails_middleware_spec.rb +41 -0
- data/spec/acceptance/safelisting_ip_spec.rb +4 -2
- data/spec/acceptance/safelisting_spec.rb +57 -3
- data/spec/acceptance/safelisting_subnet_spec.rb +4 -2
- data/spec/acceptance/stores/active_support_dalli_store_spec.rb +3 -23
- data/spec/acceptance/stores/active_support_mem_cache_store_pooled_spec.rb +20 -0
- data/spec/acceptance/stores/active_support_mem_cache_store_spec.rb +4 -24
- data/spec/acceptance/stores/active_support_memory_store_spec.rb +3 -23
- data/spec/acceptance/stores/active_support_redis_cache_store_pooled_spec.rb +10 -24
- data/spec/acceptance/stores/active_support_redis_cache_store_spec.rb +9 -25
- data/spec/acceptance/stores/active_support_redis_store_spec.rb +4 -24
- data/spec/acceptance/stores/connection_pool_dalli_client_spec.rb +5 -23
- data/spec/acceptance/stores/dalli_client_spec.rb +3 -23
- data/spec/acceptance/stores/redis_spec.rb +1 -23
- data/spec/acceptance/stores/redis_store_spec.rb +3 -23
- data/spec/acceptance/throttling_spec.rb +7 -5
- data/spec/acceptance/track_spec.rb +5 -3
- data/spec/acceptance/track_throttle_spec.rb +5 -3
- data/spec/allow2ban_spec.rb +20 -15
- data/spec/fail2ban_spec.rb +20 -17
- data/spec/integration/offline_spec.rb +3 -1
- data/spec/rack_attack_dalli_proxy_spec.rb +2 -0
- data/spec/rack_attack_instrumentation_spec.rb +42 -0
- data/spec/rack_attack_path_normalizer_spec.rb +4 -2
- data/spec/rack_attack_request_spec.rb +2 -0
- data/spec/rack_attack_spec.rb +38 -34
- data/spec/rack_attack_throttle_spec.rb +50 -19
- data/spec/rack_attack_track_spec.rb +12 -7
- data/spec/spec_helper.rb +10 -8
- data/spec/support/cache_store_helper.rb +27 -1
- metadata +48 -28
- data/lib/rack/attack/store_proxy/mem_cache_proxy.rb +0 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ac5af22059fcc24c45b9732a806b13ef8a39b3ab425e713d22b7d0b1c9fbae11
|
4
|
+
data.tar.gz: fdd20e74080d4254d7910be3d1f0343580a2cedd79b18f2448fa753acd9259e2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1f2a7bd75ab8423dde30e482085b19cb5cfbf7347aed13c94da63d31784939075278cc0f891af450bd33e5ef3de4ea092441b26f2519e28ecb5cbe5c6a16d007
|
7
|
+
data.tar.gz: fbe8d0cc86c52be9a028a4fcb2f8f2399af143b6ccd77c7377cbe5f762bb344bdb80833c5e53221ffefad57d04ee132650b38cd5e9d44c5073e475ba894a1a3f
|
data/README.md
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
__Note__: You are viewing the development version README.
|
2
|
+
For the README consistent with the latest released version see https://github.com/kickstarter/rack-attack/blob/6-stable/README.md.
|
3
|
+
|
1
4
|
# Rack::Attack
|
2
5
|
|
3
6
|
*Rack middleware for blocking & throttling abusive requests*
|
@@ -9,10 +12,44 @@ See the [Backing & Hacking blog post](https://www.kickstarter.com/backing-and-ha
|
|
9
12
|
[](https://badge.fury.io/rb/rack-attack)
|
10
13
|
[](https://travis-ci.org/kickstarter/rack-attack)
|
11
14
|
[](https://codeclimate.com/github/kickstarter/rack-attack)
|
15
|
+
[](https://gitter.im/rack-attack/rack-attack)
|
16
|
+
|
17
|
+
## Table of contents
|
18
|
+
|
19
|
+
- [Getting started](#getting-started)
|
20
|
+
- [Installing](#installing)
|
21
|
+
- [Plugging into the application](#plugging-into-the-application)
|
22
|
+
- [Usage](#usage)
|
23
|
+
- [Safelisting](#safelisting)
|
24
|
+
- [`safelist_ip(ip_address_string)`](#safelist_ipip_address_string)
|
25
|
+
- [`safelist_ip(ip_subnet_string)`](#safelist_ipip_subnet_string)
|
26
|
+
- [`safelist(name, &block)`](#safelistname-block)
|
27
|
+
- [Blocking](#blocking)
|
28
|
+
- [`blocklist_ip(ip_address_string)`](#blocklist_ipip_address_string)
|
29
|
+
- [`blocklist_ip(ip_subnet_string)`](#blocklist_ipip_subnet_string)
|
30
|
+
- [`blocklist(name, &block)`](#blocklistname-block)
|
31
|
+
- [Fail2Ban](#fail2ban)
|
32
|
+
- [Allow2Ban](#allow2ban)
|
33
|
+
- [Throttling](#throttling)
|
34
|
+
- [`throttle(name, options, &block)`](#throttlename-options-block)
|
35
|
+
- [Tracks](#tracks)
|
36
|
+
- [Cache store configuration](#cache-store-configuration)
|
37
|
+
- [Customizing responses](#customizing-responses)
|
38
|
+
- [RateLimit headers for well-behaved clients](#ratelimit-headers-for-well-behaved-clients)
|
39
|
+
- [Logging & Instrumentation](#logging--instrumentation)
|
40
|
+
- [How it works](#how-it-works)
|
41
|
+
- [About Tracks](#about-tracks)
|
42
|
+
- [Testing](#testing)
|
43
|
+
- [Performance](#performance)
|
44
|
+
- [Motivation](#motivation)
|
45
|
+
- [Contributing](#contributing)
|
46
|
+
- [Code of Conduct](#code-of-conduct)
|
47
|
+
- [Development setup](#development-setup)
|
48
|
+
- [License](#license)
|
12
49
|
|
13
50
|
## Getting started
|
14
51
|
|
15
|
-
###
|
52
|
+
### Installing
|
16
53
|
|
17
54
|
Add this line to your application's Gemfile:
|
18
55
|
|
@@ -30,18 +67,23 @@ Or install it yourself as:
|
|
30
67
|
|
31
68
|
$ gem install rack-attack
|
32
69
|
|
33
|
-
###
|
70
|
+
### Plugging into the application
|
34
71
|
|
35
72
|
Then tell your ruby web application to use rack-attack as a middleware.
|
36
73
|
|
37
|
-
a) For __rails__ applications:
|
38
|
-
|
74
|
+
a) For __rails__ applications with versions >= 5.1 it is used by default. For older rails versions you should enable it explicitly:
|
39
75
|
```ruby
|
40
76
|
# In config/application.rb
|
41
77
|
|
42
78
|
config.middleware.use Rack::Attack
|
43
79
|
```
|
44
80
|
|
81
|
+
You can disable it permanently (like for specific environment) or temporarily (can be useful for specific test cases) by writing:
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
Rack::Attack.enabled = false
|
85
|
+
```
|
86
|
+
|
45
87
|
b) For __rack__ applications:
|
46
88
|
|
47
89
|
```ruby
|
@@ -55,8 +97,8 @@ __IMPORTANT__: By default, rack-attack won't perform any blocking or throttling,
|
|
55
97
|
|
56
98
|
## Usage
|
57
99
|
|
58
|
-
*Tip:*
|
59
|
-
[
|
100
|
+
*Tip:* If you just want to get going asap, then you can take our [example configuration](docs/example_configuration.md)
|
101
|
+
and tailor it to your needs, or check out the [advanced configuration](docs/advanced_configuration.md) examples.
|
60
102
|
|
61
103
|
Define rules by calling `Rack::Attack` public methods, in any file that runs when your application is being initialized. For rails applications this means creating a new file named `config/initializers/rack_attack.rb` and writing your rules there.
|
62
104
|
|
@@ -86,7 +128,7 @@ Rack::Attack.safelist_ip("5.6.7.0/24")
|
|
86
128
|
|
87
129
|
#### `safelist(name, &block)`
|
88
130
|
|
89
|
-
Name your custom safelist and make your ruby-block argument return a truthy value if you want the request to be
|
131
|
+
Name your custom safelist and make your ruby-block argument return a truthy value if you want the request to be allowed, and falsy otherwise.
|
90
132
|
|
91
133
|
The request object is a [Rack::Request](http://www.rubydoc.info/gems/rack/Rack/Request).
|
92
134
|
|
@@ -252,8 +294,9 @@ Rack::Attack.track("special_agent", limit: 6, period: 60) do |req|
|
|
252
294
|
end
|
253
295
|
|
254
296
|
# Track it using ActiveSupport::Notification
|
255
|
-
ActiveSupport::Notifications.subscribe("
|
256
|
-
|
297
|
+
ActiveSupport::Notifications.subscribe("track.rack_attack") do |name, start, finish, request_id, payload|
|
298
|
+
req = payload[:request]
|
299
|
+
if req.env['rack.attack.matched'] == "special_agent"
|
257
300
|
Rails.logger.info "special_agent: #{req.path}"
|
258
301
|
STATSD.increment("special_agent")
|
259
302
|
end
|
@@ -294,12 +337,12 @@ Rack::Attack.throttled_response = lambda do |env|
|
|
294
337
|
end
|
295
338
|
```
|
296
339
|
|
297
|
-
###
|
340
|
+
### RateLimit headers for well-behaved clients
|
298
341
|
|
299
342
|
While Rack::Attack's primary focus is minimizing harm from abusive clients, it
|
300
343
|
can also be used to return rate limit data that's helpful for well-behaved clients.
|
301
344
|
|
302
|
-
Here's an example response that includes conventional `
|
345
|
+
Here's an example response that includes conventional `RateLimit-*` headers:
|
303
346
|
|
304
347
|
```ruby
|
305
348
|
Rack::Attack.throttled_response = lambda do |env|
|
@@ -307,9 +350,9 @@ Rack::Attack.throttled_response = lambda do |env|
|
|
307
350
|
now = match_data[:epoch_time]
|
308
351
|
|
309
352
|
headers = {
|
310
|
-
'
|
311
|
-
'
|
312
|
-
'
|
353
|
+
'RateLimit-Limit' => match_data[:limit].to_s,
|
354
|
+
'RateLimit-Remaining' => '0',
|
355
|
+
'RateLimit-Reset' => (now + (match_data[:period] - now % match_data[:period])).to_s
|
313
356
|
}
|
314
357
|
|
315
358
|
[ 429, headers, ["Throttled\n"]]
|
@@ -320,18 +363,33 @@ end
|
|
320
363
|
For responses that did not exceed a throttle limit, Rack::Attack annotates the env with match data:
|
321
364
|
|
322
365
|
```ruby
|
323
|
-
request.env['rack.attack.throttle_data'][name] # => { :count
|
366
|
+
request.env['rack.attack.throttle_data'][name] # => { discriminator: d, count: n, period: p, limit: l, epoch_time: t }
|
324
367
|
```
|
325
368
|
|
326
369
|
## Logging & Instrumentation
|
327
370
|
|
328
371
|
Rack::Attack uses the [ActiveSupport::Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) API if available.
|
329
372
|
|
330
|
-
You can subscribe to
|
373
|
+
You can subscribe to `rack_attack` events and log it, graph it, etc.
|
374
|
+
|
375
|
+
To get notified about specific type of events, subscribe to the event name followed by the `rack_attack` namesapce.
|
376
|
+
E.g. for throttles use:
|
377
|
+
|
378
|
+
```ruby
|
379
|
+
ActiveSupport::Notifications.subscribe("throttle.rack_attack") do |name, start, finish, request_id, payload|
|
380
|
+
# request object available in payload[:request]
|
381
|
+
|
382
|
+
# Your code here
|
383
|
+
end
|
384
|
+
```
|
385
|
+
|
386
|
+
If you want to subscribe to every `rack_attack` event, use:
|
331
387
|
|
332
388
|
```ruby
|
333
|
-
ActiveSupport::Notifications.subscribe(
|
334
|
-
|
389
|
+
ActiveSupport::Notifications.subscribe(/rack_attack/) do |name, start, finish, request_id, payload|
|
390
|
+
# request object available in payload[:request]
|
391
|
+
|
392
|
+
# Your code here
|
335
393
|
end
|
336
394
|
```
|
337
395
|
|
@@ -344,7 +402,7 @@ The Rack::Attack middleware compares each request against *safelists*, *blocklis
|
|
344
402
|
* Otherwise, if the request matches any **throttle**, a counter is incremented in the Rack::Attack.cache. If any throttle's limit is exceeded, the request is blocked.
|
345
403
|
* Otherwise, all **tracks** are checked, and the request is allowed.
|
346
404
|
|
347
|
-
The algorithm is actually more concise in code: See [Rack::Attack.call](
|
405
|
+
The algorithm is actually more concise in code: See [Rack::Attack.call](lib/rack/attack.rb):
|
348
406
|
|
349
407
|
```ruby
|
350
408
|
def call(env)
|
@@ -365,7 +423,7 @@ end
|
|
365
423
|
|
366
424
|
Note: `Rack::Attack::Request` is just a subclass of `Rack::Request` so that you
|
367
425
|
can cleanly monkey patch helper methods onto the
|
368
|
-
[request object](
|
426
|
+
[request object](lib/rack/attack/request.rb).
|
369
427
|
|
370
428
|
### About Tracks
|
371
429
|
|
@@ -412,13 +470,6 @@ This project is intended to be a safe, welcoming space for collaboration, and co
|
|
412
470
|
|
413
471
|
Check out the [Development guide](docs/development.md).
|
414
472
|
|
415
|
-
## Mailing list
|
416
|
-
|
417
|
-
New releases of Rack::Attack are announced on
|
418
|
-
<rack.attack.announce@librelist.com>. To subscribe, just send an email to
|
419
|
-
<rack.attack.announce@librelist.com>. See the
|
420
|
-
[archives](http://librelist.com/browser/rack.attack.announce/).
|
421
|
-
|
422
473
|
## License
|
423
474
|
|
424
475
|
Copyright Kickstarter, PBC.
|
data/Rakefile
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "rubygems"
|
2
4
|
require "bundler/setup"
|
3
5
|
require 'bundler/gem_tasks'
|
@@ -24,4 +26,4 @@ Rake::TestTask.new(:test) do |t|
|
|
24
26
|
t.pattern = "spec/**/*_spec.rb"
|
25
27
|
end
|
26
28
|
|
27
|
-
task :
|
29
|
+
task default: [:rubocop, :test]
|
data/bin/setup
ADDED
data/lib/rack/attack.rb
CHANGED
@@ -1,187 +1,176 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rack'
|
2
4
|
require 'forwardable'
|
3
5
|
require 'rack/attack/path_normalizer'
|
4
6
|
require 'rack/attack/request'
|
5
7
|
require "ipaddr"
|
6
8
|
|
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
|
-
safelist(name, &block)
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
9
|
+
require 'rack/attack/railtie' if defined?(::Rails)
|
10
|
+
|
11
|
+
module Rack
|
12
|
+
class Attack
|
13
|
+
class Error < StandardError; end
|
14
|
+
class MisconfiguredStoreError < Error; end
|
15
|
+
class MissingStoreError < Error; end
|
16
|
+
|
17
|
+
autoload :Cache, 'rack/attack/cache'
|
18
|
+
autoload :Check, 'rack/attack/check'
|
19
|
+
autoload :Throttle, 'rack/attack/throttle'
|
20
|
+
autoload :Safelist, 'rack/attack/safelist'
|
21
|
+
autoload :Blocklist, 'rack/attack/blocklist'
|
22
|
+
autoload :Track, 'rack/attack/track'
|
23
|
+
autoload :StoreProxy, 'rack/attack/store_proxy'
|
24
|
+
autoload :DalliProxy, 'rack/attack/store_proxy/dalli_proxy'
|
25
|
+
autoload :MemCacheStoreProxy, 'rack/attack/store_proxy/mem_cache_store_proxy'
|
26
|
+
autoload :RedisProxy, 'rack/attack/store_proxy/redis_proxy'
|
27
|
+
autoload :RedisStoreProxy, 'rack/attack/store_proxy/redis_store_proxy'
|
28
|
+
autoload :RedisCacheStoreProxy, 'rack/attack/store_proxy/redis_cache_store_proxy'
|
29
|
+
autoload :ActiveSupportRedisStoreProxy, 'rack/attack/store_proxy/active_support_redis_store_proxy'
|
30
|
+
autoload :Fail2Ban, 'rack/attack/fail2ban'
|
31
|
+
autoload :Allow2Ban, 'rack/attack/allow2ban'
|
32
|
+
|
33
|
+
class << self
|
34
|
+
attr_accessor :enabled, :notifier, :blocklisted_response, :throttled_response,
|
35
|
+
:anonymous_blocklists, :anonymous_safelists
|
36
|
+
|
37
|
+
def safelist(name = nil, &block)
|
38
|
+
safelist = Safelist.new(name, &block)
|
39
|
+
|
40
|
+
if name
|
41
|
+
safelists[name] = safelist
|
42
|
+
else
|
43
|
+
anonymous_safelists << safelist
|
44
|
+
end
|
45
|
+
end
|
41
46
|
|
42
|
-
|
43
|
-
|
44
|
-
ip_blocklist_proc = lambda { |request| IPAddr.new(ip_address).include?(IPAddr.new(request.ip)) }
|
45
|
-
@ip_blocklists << Blocklist.new(nil, ip_blocklist_proc)
|
46
|
-
end
|
47
|
+
def blocklist(name = nil, &block)
|
48
|
+
blocklist = Blocklist.new(name, &block)
|
47
49
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
50
|
+
if name
|
51
|
+
blocklists[name] = blocklist
|
52
|
+
else
|
53
|
+
anonymous_blocklists << blocklist
|
54
|
+
end
|
55
|
+
end
|
53
56
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
end
|
57
|
+
def blocklist_ip(ip_address)
|
58
|
+
anonymous_blocklists << Blocklist.new { |request| IPAddr.new(ip_address).include?(IPAddr.new(request.ip)) }
|
59
|
+
end
|
58
60
|
|
59
|
-
|
60
|
-
|
61
|
-
|
61
|
+
def safelist_ip(ip_address)
|
62
|
+
anonymous_safelists << Safelist.new { |request| IPAddr.new(ip_address).include?(IPAddr.new(request.ip)) }
|
63
|
+
end
|
62
64
|
|
63
|
-
|
64
|
-
|
65
|
-
|
65
|
+
def throttle(name, options, &block)
|
66
|
+
throttles[name] = Throttle.new(name, options, &block)
|
67
|
+
end
|
66
68
|
|
67
|
-
|
69
|
+
def track(name, options = {}, &block)
|
70
|
+
tracks[name] = Track.new(name, options, &block)
|
71
|
+
end
|
68
72
|
|
69
|
-
|
73
|
+
def safelists
|
74
|
+
@safelists ||= {}
|
75
|
+
end
|
70
76
|
|
71
|
-
|
77
|
+
def blocklists
|
78
|
+
@blocklists ||= {}
|
79
|
+
end
|
72
80
|
|
73
|
-
|
81
|
+
def throttles
|
82
|
+
@throttles ||= {}
|
83
|
+
end
|
74
84
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
end
|
85
|
+
def tracks
|
86
|
+
@tracks ||= {}
|
87
|
+
end
|
79
88
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
89
|
+
def safelisted?(request)
|
90
|
+
anonymous_safelists.any? { |safelist| safelist.matched_by?(request) } ||
|
91
|
+
safelists.any? { |_name, safelist| safelist.matched_by?(request) }
|
92
|
+
end
|
84
93
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
94
|
+
def blocklisted?(request)
|
95
|
+
anonymous_blocklists.any? { |blocklist| blocklist.matched_by?(request) } ||
|
96
|
+
blocklists.any? { |_name, blocklist| blocklist.matched_by?(request) }
|
97
|
+
end
|
89
98
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
99
|
+
def throttled?(request)
|
100
|
+
throttles.any? do |_name, throttle|
|
101
|
+
throttle.matched_by?(request)
|
102
|
+
end
|
103
|
+
end
|
94
104
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
105
|
+
def tracked?(request)
|
106
|
+
tracks.each_value do |track|
|
107
|
+
track.matched_by?(request)
|
108
|
+
end
|
109
|
+
end
|
99
110
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
111
|
+
def instrument(request)
|
112
|
+
if notifier
|
113
|
+
event_type = request.env["rack.attack.match_type"]
|
114
|
+
notifier.instrument("#{event_type}.rack_attack", request: request)
|
104
115
|
|
105
|
-
|
106
|
-
|
107
|
-
|
116
|
+
# Deprecated: Keeping just for backwards compatibility
|
117
|
+
notifier.instrument("rack.attack", request: request)
|
118
|
+
end
|
108
119
|
end
|
109
|
-
end
|
110
120
|
|
111
|
-
|
112
|
-
|
113
|
-
track.matched_by?(request)
|
121
|
+
def cache
|
122
|
+
@cache ||= Cache.new
|
114
123
|
end
|
115
|
-
end
|
116
|
-
|
117
|
-
def instrument(request)
|
118
|
-
notifier.instrument('rack.attack', request) if notifier
|
119
|
-
end
|
120
|
-
|
121
|
-
def cache
|
122
|
-
@cache ||= Cache.new
|
123
|
-
end
|
124
124
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
125
|
+
def clear_configuration
|
126
|
+
@safelists = {}
|
127
|
+
@blocklists = {}
|
128
|
+
@throttles = {}
|
129
|
+
@tracks = {}
|
130
|
+
self.anonymous_blocklists = []
|
131
|
+
self.anonymous_safelists = []
|
132
|
+
end
|
130
133
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
+
def clear!
|
135
|
+
warn "[DEPRECATION] Rack::Attack.clear! is deprecated. Please use Rack::Attack.clear_configuration instead"
|
136
|
+
clear_configuration
|
137
|
+
end
|
134
138
|
end
|
135
139
|
|
136
|
-
|
137
|
-
|
138
|
-
|
140
|
+
# Set defaults
|
141
|
+
@enabled = true
|
142
|
+
@anonymous_blocklists = []
|
143
|
+
@anonymous_safelists = []
|
144
|
+
@notifier = ActiveSupport::Notifications if defined?(ActiveSupport::Notifications)
|
145
|
+
@blocklisted_response = lambda { |_env| [403, { 'Content-Type' => 'text/plain' }, ["Forbidden\n"]] }
|
146
|
+
@throttled_response = lambda do |env|
|
147
|
+
retry_after = (env['rack.attack.match_data'] || {})[:period]
|
148
|
+
[429, { 'Content-Type' => 'text/plain', 'Retry-After' => retry_after.to_s }, ["Retry later\n"]]
|
139
149
|
end
|
140
150
|
|
141
|
-
def
|
142
|
-
|
143
|
-
blocklisted_response
|
151
|
+
def initialize(app)
|
152
|
+
@app = app
|
144
153
|
end
|
145
154
|
|
146
|
-
|
155
|
+
def call(env)
|
156
|
+
return @app.call(env) unless self.class.enabled
|
147
157
|
|
148
|
-
|
149
|
-
|
150
|
-
end
|
158
|
+
env['PATH_INFO'] = PathNormalizer.normalize_path(env['PATH_INFO'])
|
159
|
+
request = Rack::Attack::Request.new(env)
|
151
160
|
|
152
|
-
|
153
|
-
|
161
|
+
if safelisted?(request)
|
162
|
+
@app.call(env)
|
163
|
+
elsif blocklisted?(request)
|
164
|
+
self.class.blocklisted_response.call(env)
|
165
|
+
elsif throttled?(request)
|
166
|
+
self.class.throttled_response.call(env)
|
167
|
+
else
|
168
|
+
tracked?(request)
|
169
|
+
@app.call(env)
|
170
|
+
end
|
154
171
|
end
|
155
|
-
end
|
156
|
-
|
157
|
-
# Set defaults
|
158
|
-
@notifier = ActiveSupport::Notifications if defined?(ActiveSupport::Notifications)
|
159
|
-
@blocklisted_response = lambda { |_env| [403, { 'Content-Type' => 'text/plain' }, ["Forbidden\n"]] }
|
160
|
-
@throttled_response = lambda { |env|
|
161
|
-
retry_after = (env['rack.attack.match_data'] || {})[:period]
|
162
|
-
[429, { 'Content-Type' => 'text/plain', 'Retry-After' => retry_after.to_s }, ["Retry later\n"]]
|
163
|
-
}
|
164
|
-
|
165
|
-
def initialize(app)
|
166
|
-
@app = app
|
167
|
-
end
|
168
172
|
|
169
|
-
|
170
|
-
|
171
|
-
request = Rack::Attack::Request.new(env)
|
172
|
-
|
173
|
-
if safelisted?(request)
|
174
|
-
@app.call(env)
|
175
|
-
elsif blocklisted?(request)
|
176
|
-
self.class.blocklisted_response.call(env)
|
177
|
-
elsif throttled?(request)
|
178
|
-
self.class.throttled_response.call(env)
|
179
|
-
else
|
180
|
-
tracked?(request)
|
181
|
-
@app.call(env)
|
182
|
-
end
|
173
|
+
extend Forwardable
|
174
|
+
def_delegators self, :safelisted?, :blocklisted?, :throttled?, :tracked?
|
183
175
|
end
|
184
|
-
|
185
|
-
extend Forwardable
|
186
|
-
def_delegators self, :safelisted?, :blocklisted?, :throttled?, :tracked?
|
187
176
|
end
|