rack-attack 5.4.1 → 6.2.1

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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +78 -27
  3. data/Rakefile +3 -1
  4. data/lib/rack/attack.rb +138 -149
  5. data/lib/rack/attack/allow2ban.rb +2 -0
  6. data/lib/rack/attack/blocklist.rb +3 -1
  7. data/lib/rack/attack/cache.rb +9 -4
  8. data/lib/rack/attack/check.rb +5 -2
  9. data/lib/rack/attack/fail2ban.rb +2 -0
  10. data/lib/rack/attack/path_normalizer.rb +22 -18
  11. data/lib/rack/attack/railtie.rb +13 -0
  12. data/lib/rack/attack/request.rb +2 -0
  13. data/lib/rack/attack/safelist.rb +3 -1
  14. data/lib/rack/attack/store_proxy.rb +12 -14
  15. data/lib/rack/attack/store_proxy/active_support_redis_store_proxy.rb +39 -0
  16. data/lib/rack/attack/store_proxy/dalli_proxy.rb +27 -13
  17. data/lib/rack/attack/store_proxy/mem_cache_store_proxy.rb +3 -1
  18. data/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb +23 -9
  19. data/lib/rack/attack/store_proxy/redis_proxy.rb +16 -10
  20. data/lib/rack/attack/store_proxy/redis_store_proxy.rb +5 -5
  21. data/lib/rack/attack/throttle.rb +12 -8
  22. data/lib/rack/attack/track.rb +9 -6
  23. data/lib/rack/attack/version.rb +3 -1
  24. data/spec/acceptance/allow2ban_spec.rb +2 -0
  25. data/spec/acceptance/blocking_ip_spec.rb +4 -2
  26. data/spec/acceptance/blocking_spec.rb +45 -3
  27. data/spec/acceptance/blocking_subnet_spec.rb +4 -2
  28. data/spec/acceptance/cache_store_config_for_allow2ban_spec.rb +50 -39
  29. data/spec/acceptance/cache_store_config_for_fail2ban_spec.rb +38 -29
  30. data/spec/acceptance/cache_store_config_for_throttle_spec.rb +2 -0
  31. data/spec/acceptance/cache_store_config_with_rails_spec.rb +2 -0
  32. data/spec/acceptance/customizing_blocked_response_spec.rb +2 -0
  33. data/spec/acceptance/customizing_throttled_response_spec.rb +2 -0
  34. data/spec/acceptance/extending_request_object_spec.rb +2 -0
  35. data/spec/acceptance/fail2ban_spec.rb +2 -0
  36. data/spec/acceptance/rails_middleware_spec.rb +35 -0
  37. data/spec/acceptance/safelisting_ip_spec.rb +4 -2
  38. data/spec/acceptance/safelisting_spec.rb +57 -3
  39. data/spec/acceptance/safelisting_subnet_spec.rb +4 -2
  40. data/spec/acceptance/stores/active_support_dalli_store_spec.rb +2 -0
  41. data/spec/acceptance/stores/active_support_mem_cache_store_pooled_spec.rb +1 -3
  42. data/spec/acceptance/stores/active_support_mem_cache_store_spec.rb +2 -0
  43. data/spec/acceptance/stores/active_support_memory_store_spec.rb +2 -0
  44. data/spec/acceptance/stores/active_support_redis_cache_store_pooled_spec.rb +9 -1
  45. data/spec/acceptance/stores/active_support_redis_cache_store_spec.rb +8 -1
  46. data/spec/acceptance/stores/active_support_redis_store_spec.rb +3 -1
  47. data/spec/acceptance/stores/connection_pool_dalli_client_spec.rb +5 -3
  48. data/spec/acceptance/stores/dalli_client_spec.rb +2 -0
  49. data/spec/acceptance/stores/redis_store_spec.rb +2 -0
  50. data/spec/acceptance/throttling_spec.rb +7 -5
  51. data/spec/acceptance/track_spec.rb +5 -3
  52. data/spec/acceptance/track_throttle_spec.rb +5 -3
  53. data/spec/allow2ban_spec.rb +20 -15
  54. data/spec/fail2ban_spec.rb +20 -17
  55. data/spec/integration/offline_spec.rb +3 -1
  56. data/spec/rack_attack_dalli_proxy_spec.rb +2 -0
  57. data/spec/rack_attack_instrumentation_spec.rb +42 -0
  58. data/spec/rack_attack_path_normalizer_spec.rb +4 -2
  59. data/spec/rack_attack_request_spec.rb +2 -0
  60. data/spec/rack_attack_spec.rb +38 -34
  61. data/spec/rack_attack_throttle_spec.rb +50 -19
  62. data/spec/rack_attack_track_spec.rb +12 -7
  63. data/spec/spec_helper.rb +12 -8
  64. data/spec/support/cache_store_helper.rb +2 -0
  65. metadata +44 -28
  66. 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: 21f1ecf9854b74958cacdb31211735c29b9a2f98f1ee848e0f60a3f409651cc6
4
- data.tar.gz: 934f2ac2af420277f6d31052b56af24d2cbe93412a272aa753c25ce26228d7b1
3
+ metadata.gz: 55b137d5d1174ac14bcbc494ba7c6753652c23348fef73c5ac712884e299a8d0
4
+ data.tar.gz: 9aae493ec090c669ea43f7a5f9bc8a10e6786266477a4dd65b05495c7559e469
5
5
  SHA512:
6
- metadata.gz: 32f63613544602a11c8af12a5ac764adf44ba22beda1dc8840bfc5431a32edd8ea1075eab6933f423405e71959ed0c5b5f8668d72262c5fb4787f89105259f5a
7
- data.tar.gz: aad3b2d1ace4369f2605d863df03c86d0e008c934c5f9c6741b71d2a3e363bcf37c93685e436ff48c917a512d39b8fd059fb3db09f38250c609f5fa22244cda4
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
- ### 1. Installing
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
- ### 2. Plugging into the application
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:* The example in the wiki is a great way to get started:
59
- [Example Configuration](https://github.com/kickstarter/rack-attack/wiki/Example-Configuration)
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 blocked, and falsy otherwise.
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("rack.attack") do |name, start, finish, request_id, req|
256
- if req.env['rack.attack.matched'] == "special_agent" && req.env['rack.attack.match_type'] == :track
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
- ### X-RateLimit headers for well-behaved clients
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 `X-RateLimit-*` headers:
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
- 'X-RateLimit-Limit' => match_data[:limit].to_s,
311
- 'X-RateLimit-Remaining' => '0',
312
- 'X-RateLimit-Reset' => (now + (match_data[:period] - now % match_data[:period])).to_s
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 => n, :period => p, :limit => l, :epoch_time => t }
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 'rack.attack' events and log it, graph it, etc:
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('rack.attack') do |name, start, finish, request_id, req|
334
- puts req.inspect
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](https://github.com/kickstarter/rack-attack/blob/master/lib/rack/attack.rb):
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](https://github.com/kickstarter/rack-attack/blob/master/lib/rack/attack/request.rb).
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 :default => [:rubocop, :test]
29
+ task default: [:rubocop, :test]
@@ -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
- class Rack::Attack
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'
26
-
27
- class << self
28
- attr_accessor :notifier, :blocklisted_response, :throttled_response
29
-
30
- def safelist(name, &block)
31
- self.safelists[name] = Safelist.new(name, block)
32
- end
33
-
34
- def whitelist(name, &block)
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
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
- 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
47
+ def blocklist(name = nil, &block)
48
+ blocklist = Blocklist.new(name, &block)
48
49
 
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)
53
- end
50
+ if name
51
+ blocklists[name] = blocklist
52
+ else
53
+ anonymous_blocklists << blocklist
54
+ end
55
+ end
54
56
 
55
- def blacklist(name, &block)
56
- warn "[DEPRECATION] 'Rack::Attack.blacklist' is deprecated. Please use 'blocklist' instead."
57
- blocklist(name, &block)
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
- def throttle(name, options, &block)
61
- self.throttles[name] = Throttle.new(name, options, block)
62
- end
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
- def track(name, options = {}, &block)
65
- self.tracks[name] = Track.new(name, options, block)
66
- end
65
+ def throttle(name, options, &block)
66
+ throttles[name] = Throttle.new(name, options, &block)
67
+ end
67
68
 
68
- def safelists; @safelists ||= {}; end
69
+ def track(name, options = {}, &block)
70
+ tracks[name] = Track.new(name, options, &block)
71
+ end
69
72
 
70
- def blocklists; @blocklists ||= {}; end
73
+ def safelists
74
+ @safelists ||= {}
75
+ end
71
76
 
72
- def throttles; @throttles ||= {}; end
77
+ def blocklists
78
+ @blocklists ||= {}
79
+ end
73
80
 
74
- def tracks; @tracks ||= {}; end
81
+ def throttles
82
+ @throttles ||= {}
83
+ end
75
84
 
76
- def whitelists
77
- warn "[DEPRECATION] 'Rack::Attack.whitelists' is deprecated. Please use 'safelists' instead."
78
- safelists
79
- end
85
+ def tracks
86
+ @tracks ||= {}
87
+ end
80
88
 
81
- def blacklists
82
- warn "[DEPRECATION] 'Rack::Attack.blacklists' is deprecated. Please use 'blocklists' instead."
83
- blocklists
84
- end
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
- def safelisted?(request)
87
- ip_safelists.any? { |safelist| safelist.matched_by?(request) } ||
88
- safelists.any? { |_name, safelist| safelist.matched_by?(request) }
89
- end
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
- def whitelisted?(request)
92
- warn "[DEPRECATION] 'Rack::Attack.whitelisted?' is deprecated. Please use 'safelisted?' instead."
93
- safelisted?(request)
94
- end
99
+ def throttled?(request)
100
+ throttles.any? do |_name, throttle|
101
+ throttle.matched_by?(request)
102
+ end
103
+ end
95
104
 
96
- def blocklisted?(request)
97
- ip_blocklists.any? { |blocklist| blocklist.matched_by?(request) } ||
98
- blocklists.any? { |_name, blocklist| blocklist.matched_by?(request) }
99
- end
105
+ def tracked?(request)
106
+ tracks.each_value do |track|
107
+ track.matched_by?(request)
108
+ end
109
+ end
100
110
 
101
- def blacklisted?(request)
102
- warn "[DEPRECATION] 'Rack::Attack.blacklisted?' is deprecated. Please use 'blocklisted?' instead."
103
- blocklisted?(request)
104
- end
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
- def throttled?(request)
107
- throttles.any? do |_name, throttle|
108
- throttle.matched_by?(request)
116
+ # Deprecated: Keeping just for backwards compatibility
117
+ notifier.instrument("rack.attack", request: request)
118
+ end
109
119
  end
110
- end
111
120
 
112
- def tracked?(request)
113
- tracks.each_value do |track|
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
- def clear_configuration
127
- @safelists, @blocklists, @throttles, @tracks = {}, {}, {}, {}
128
- @ip_blocklists = []
129
- @ip_safelists = []
130
- end
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
- def clear!
133
- warn "[DEPRECATION] Rack::Attack.clear! is deprecated. Please use Rack::Attack.clear_configuration instead"
134
- clear_configuration
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
- def blacklisted_response=(res)
138
- warn "[DEPRECATION] 'Rack::Attack.blacklisted_response=' is deprecated. Please use 'blocklisted_response=' instead."
139
- self.blocklisted_response = res
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 blacklisted_response
143
- warn "[DEPRECATION] 'Rack::Attack.blacklisted_response' is deprecated. Please use 'blocklisted_response' instead."
144
- blocklisted_response
151
+ def initialize(app)
152
+ @app = app
145
153
  end
146
154
 
147
- private
155
+ def call(env)
156
+ return @app.call(env) if !self.class.enabled || env["rack.attack.called"]
148
157
 
149
- def ip_blocklists
150
- @ip_blocklists ||= []
151
- end
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
- def ip_safelists
154
- @ip_safelists ||= []
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
- def call(env)
171
- env['PATH_INFO'] = PathNormalizer.normalize_path(env['PATH_INFO'])
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