rack-attack 5.4.1 → 6.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +78 -27
- data/Rakefile +3 -1
- data/lib/rack/attack.rb +138 -149
- 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 +13 -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 -14
- 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 +3 -1
- 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 +35 -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 +2 -0
- data/spec/acceptance/stores/active_support_mem_cache_store_pooled_spec.rb +1 -3
- data/spec/acceptance/stores/active_support_mem_cache_store_spec.rb +2 -0
- data/spec/acceptance/stores/active_support_memory_store_spec.rb +2 -0
- data/spec/acceptance/stores/active_support_redis_cache_store_pooled_spec.rb +9 -1
- data/spec/acceptance/stores/active_support_redis_cache_store_spec.rb +8 -1
- data/spec/acceptance/stores/active_support_redis_store_spec.rb +3 -1
- data/spec/acceptance/stores/connection_pool_dalli_client_spec.rb +5 -3
- data/spec/acceptance/stores/dalli_client_spec.rb +2 -0
- data/spec/acceptance/stores/redis_store_spec.rb +2 -0
- 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 +12 -8
- data/spec/support/cache_store_helper.rb +2 -0
- metadata +44 -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: 55b137d5d1174ac14bcbc494ba7c6753652c23348fef73c5ac712884e299a8d0
|
4
|
+
data.tar.gz: 9aae493ec090c669ea43f7a5f9bc8a10e6786266477a4dd65b05495c7559e469
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2e283d8c8b2e5ffcd99435561230c6019a7a1f9637927d4ca29e0df2c9af9522cb84e60dddd040ab3429b1cc697213d80adb4a9b4cb7587933060ad6e426fabd
|
7
|
+
data.tar.gz: 1ef81c28f633e8c146a54a0930c706a3158aab568328743e0a3b0953bcbcba0bfb4a32a3e589ea1292c001ec6fd8d52a9c6de3cc4063b466045b692e92d27318
|
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
|
[![Gem Version](https://badge.fury.io/rb/rack-attack.svg)](https://badge.fury.io/rb/rack-attack)
|
10
13
|
[![Build Status](https://travis-ci.org/kickstarter/rack-attack.svg?branch=master)](https://travis-ci.org/kickstarter/rack-attack)
|
11
14
|
[![Code Climate](https://codeclimate.com/github/kickstarter/rack-attack.svg)](https://codeclimate.com/github/kickstarter/rack-attack)
|
15
|
+
[![Join the chat at https://gitter.im/rack-attack/rack-attack](https://badges.gitter.im/rack-attack/rack-attack.svg)](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/lib/rack/attack.rb
CHANGED
@@ -1,188 +1,177 @@
|
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
42
46
|
|
43
|
-
|
44
|
-
|
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
|
47
|
+
def blocklist(name = nil, &block)
|
48
|
+
blocklist = Blocklist.new(name, &block)
|
48
49
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
50
|
+
if name
|
51
|
+
blocklists[name] = blocklist
|
52
|
+
else
|
53
|
+
anonymous_blocklists << blocklist
|
54
|
+
end
|
55
|
+
end
|
54
56
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
end
|
57
|
+
def blocklist_ip(ip_address)
|
58
|
+
anonymous_blocklists << Blocklist.new { |request| IPAddr.new(ip_address).include?(IPAddr.new(request.ip)) }
|
59
|
+
end
|
59
60
|
|
60
|
-
|
61
|
-
|
62
|
-
|
61
|
+
def safelist_ip(ip_address)
|
62
|
+
anonymous_safelists << Safelist.new { |request| IPAddr.new(ip_address).include?(IPAddr.new(request.ip)) }
|
63
|
+
end
|
63
64
|
|
64
|
-
|
65
|
-
|
66
|
-
|
65
|
+
def throttle(name, options, &block)
|
66
|
+
throttles[name] = Throttle.new(name, options, &block)
|
67
|
+
end
|
67
68
|
|
68
|
-
|
69
|
+
def track(name, options = {}, &block)
|
70
|
+
tracks[name] = Track.new(name, options, &block)
|
71
|
+
end
|
69
72
|
|
70
|
-
|
73
|
+
def safelists
|
74
|
+
@safelists ||= {}
|
75
|
+
end
|
71
76
|
|
72
|
-
|
77
|
+
def blocklists
|
78
|
+
@blocklists ||= {}
|
79
|
+
end
|
73
80
|
|
74
|
-
|
81
|
+
def throttles
|
82
|
+
@throttles ||= {}
|
83
|
+
end
|
75
84
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
end
|
85
|
+
def tracks
|
86
|
+
@tracks ||= {}
|
87
|
+
end
|
80
88
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
89
|
+
def safelisted?(request)
|
90
|
+
anonymous_safelists.any? { |safelist| safelist.matched_by?(request) } ||
|
91
|
+
safelists.any? { |_name, safelist| safelist.matched_by?(request) }
|
92
|
+
end
|
85
93
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
94
|
+
def blocklisted?(request)
|
95
|
+
anonymous_blocklists.any? { |blocklist| blocklist.matched_by?(request) } ||
|
96
|
+
blocklists.any? { |_name, blocklist| blocklist.matched_by?(request) }
|
97
|
+
end
|
90
98
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
99
|
+
def throttled?(request)
|
100
|
+
throttles.any? do |_name, throttle|
|
101
|
+
throttle.matched_by?(request)
|
102
|
+
end
|
103
|
+
end
|
95
104
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
105
|
+
def tracked?(request)
|
106
|
+
tracks.each_value do |track|
|
107
|
+
track.matched_by?(request)
|
108
|
+
end
|
109
|
+
end
|
100
110
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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)
|
105
115
|
|
106
|
-
|
107
|
-
|
108
|
-
|
116
|
+
# Deprecated: Keeping just for backwards compatibility
|
117
|
+
notifier.instrument("rack.attack", request: request)
|
118
|
+
end
|
109
119
|
end
|
110
|
-
end
|
111
120
|
|
112
|
-
|
113
|
-
|
114
|
-
track.matched_by?(request)
|
121
|
+
def cache
|
122
|
+
@cache ||= Cache.new
|
115
123
|
end
|
116
|
-
end
|
117
|
-
|
118
|
-
def instrument(request)
|
119
|
-
notifier.instrument('rack.attack', request) if notifier
|
120
|
-
end
|
121
|
-
|
122
|
-
def cache
|
123
|
-
@cache ||= Cache.new
|
124
|
-
end
|
125
124
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
125
|
+
def clear_configuration
|
126
|
+
@safelists = {}
|
127
|
+
@blocklists = {}
|
128
|
+
@throttles = {}
|
129
|
+
@tracks = {}
|
130
|
+
self.anonymous_blocklists = []
|
131
|
+
self.anonymous_safelists = []
|
132
|
+
end
|
131
133
|
|
132
|
-
|
133
|
-
|
134
|
-
|
134
|
+
def clear!
|
135
|
+
warn "[DEPRECATION] Rack::Attack.clear! is deprecated. Please use Rack::Attack.clear_configuration instead"
|
136
|
+
clear_configuration
|
137
|
+
end
|
135
138
|
end
|
136
139
|
|
137
|
-
|
138
|
-
|
139
|
-
|
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"]]
|
140
149
|
end
|
141
150
|
|
142
|
-
def
|
143
|
-
|
144
|
-
blocklisted_response
|
151
|
+
def initialize(app)
|
152
|
+
@app = app
|
145
153
|
end
|
146
154
|
|
147
|
-
|
155
|
+
def call(env)
|
156
|
+
return @app.call(env) if !self.class.enabled || env["rack.attack.called"]
|
148
157
|
|
149
|
-
|
150
|
-
|
151
|
-
|
158
|
+
env["rack.attack.called"] = true
|
159
|
+
env['PATH_INFO'] = PathNormalizer.normalize_path(env['PATH_INFO'])
|
160
|
+
request = Rack::Attack::Request.new(env)
|
152
161
|
|
153
|
-
|
154
|
-
|
162
|
+
if safelisted?(request)
|
163
|
+
@app.call(env)
|
164
|
+
elsif blocklisted?(request)
|
165
|
+
self.class.blocklisted_response.call(env)
|
166
|
+
elsif throttled?(request)
|
167
|
+
self.class.throttled_response.call(env)
|
168
|
+
else
|
169
|
+
tracked?(request)
|
170
|
+
@app.call(env)
|
171
|
+
end
|
155
172
|
end
|
156
|
-
end
|
157
|
-
|
158
|
-
# Set defaults
|
159
|
-
@notifier = ActiveSupport::Notifications if defined?(ActiveSupport::Notifications)
|
160
|
-
@blocklisted_response = lambda { |_env| [403, { 'Content-Type' => 'text/plain' }, ["Forbidden\n"]] }
|
161
|
-
@throttled_response = lambda { |env|
|
162
|
-
retry_after = (env['rack.attack.match_data'] || {})[:period]
|
163
|
-
[429, { 'Content-Type' => 'text/plain', 'Retry-After' => retry_after.to_s }, ["Retry later\n"]]
|
164
|
-
}
|
165
|
-
|
166
|
-
def initialize(app)
|
167
|
-
@app = app
|
168
|
-
end
|
169
173
|
|
170
|
-
|
171
|
-
|
172
|
-
request = Rack::Attack::Request.new(env)
|
173
|
-
|
174
|
-
if safelisted?(request)
|
175
|
-
@app.call(env)
|
176
|
-
elsif blocklisted?(request)
|
177
|
-
self.class.blocklisted_response.call(env)
|
178
|
-
elsif throttled?(request)
|
179
|
-
self.class.throttled_response.call(env)
|
180
|
-
else
|
181
|
-
tracked?(request)
|
182
|
-
@app.call(env)
|
183
|
-
end
|
174
|
+
extend Forwardable
|
175
|
+
def_delegators self, :safelisted?, :blocklisted?, :throttled?, :tracked?
|
184
176
|
end
|
185
|
-
|
186
|
-
extend Forwardable
|
187
|
-
def_delegators self, :safelisted?, :blocklisted?, :throttled?, :tracked?
|
188
177
|
end
|