Skip to content

Commit aa84ae7

Browse files
committed
Add ability to use multiple instances of middleware
1 parent 9bfec1a commit aa84ae7

File tree

2 files changed

+116
-103
lines changed

2 files changed

+116
-103
lines changed

lib/rack/attack.rb

Lines changed: 24 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class Error < StandardError; end
1414
class MisconfiguredStoreError < Error; end
1515
class MissingStoreError < Error; end
1616

17+
autoload :Configuration, 'rack/attack/configuration'
1718
autoload :Cache, 'rack/attack/cache'
1819
autoload :Check, 'rack/attack/check'
1920
autoload :Throttle, 'rack/attack/throttle'
@@ -31,82 +32,8 @@ class MissingStoreError < Error; end
3132
autoload :Allow2Ban, 'rack/attack/allow2ban'
3233

3334
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
46-
47-
def blocklist(name = nil, &block)
48-
blocklist = Blocklist.new(name, &block)
49-
50-
if name
51-
blocklists[name] = blocklist
52-
else
53-
anonymous_blocklists << blocklist
54-
end
55-
end
56-
57-
def blocklist_ip(ip_address)
58-
anonymous_blocklists << Blocklist.new { |request| IPAddr.new(ip_address).include?(IPAddr.new(request.ip)) }
59-
end
60-
61-
def safelist_ip(ip_address)
62-
anonymous_safelists << Safelist.new { |request| IPAddr.new(ip_address).include?(IPAddr.new(request.ip)) }
63-
end
64-
65-
def throttle(name, options, &block)
66-
throttles[name] = Throttle.new(name, options, &block)
67-
end
68-
69-
def track(name, options = {}, &block)
70-
tracks[name] = Track.new(name, options, &block)
71-
end
72-
73-
def safelists
74-
@safelists ||= {}
75-
end
76-
77-
def blocklists
78-
@blocklists ||= {}
79-
end
80-
81-
def throttles
82-
@throttles ||= {}
83-
end
84-
85-
def tracks
86-
@tracks ||= {}
87-
end
88-
89-
def safelisted?(request)
90-
anonymous_safelists.any? { |safelist| safelist.matched_by?(request) } ||
91-
safelists.any? { |_name, safelist| safelist.matched_by?(request) }
92-
end
93-
94-
def blocklisted?(request)
95-
anonymous_blocklists.any? { |blocklist| blocklist.matched_by?(request) } ||
96-
blocklists.any? { |_name, blocklist| blocklist.matched_by?(request) }
97-
end
98-
99-
def throttled?(request)
100-
throttles.any? do |_name, throttle|
101-
throttle.matched_by?(request)
102-
end
103-
end
104-
105-
def tracked?(request)
106-
tracks.each_value do |track|
107-
track.matched_by?(request)
108-
end
109-
end
35+
attr_accessor :enabled, :notifier
36+
attr_reader :config
11037

11138
def instrument(request)
11239
if notifier
@@ -122,34 +49,31 @@ def cache
12249
@cache ||= Cache.new
12350
end
12451

125-
def clear_configuration
126-
@safelists = {}
127-
@blocklists = {}
128-
@throttles = {}
129-
@tracks = {}
130-
self.anonymous_blocklists = []
131-
self.anonymous_safelists = []
132-
end
133-
13452
def clear!
13553
warn "[DEPRECATION] Rack::Attack.clear! is deprecated. Please use Rack::Attack.clear_configuration instead"
136-
clear_configuration
54+
@config.clear_configuration
13755
end
56+
57+
extend Forwardable
58+
def_delegators :@config, :safelist, :blocklist, :blocklist_ip, :safelist_ip, :throttle, :track,
59+
:blocklisted_response, :blocklisted_response=, :throttled_response, :throttled_response=,
60+
:clear_configuration, :safelists, :blocklists, :throttles, :tracks
13861
end
13962

14063
# Set defaults
14164
@enabled = true
142-
@anonymous_blocklists = []
143-
@anonymous_safelists = []
14465
@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"]]
149-
end
66+
@config = Configuration.new
15067

151-
def initialize(app)
68+
def initialize(app, &config_block)
15269
@app = app
70+
@config =
71+
if block_given?
72+
config = Configuration.new
73+
config.instance_exec(&config_block)
74+
else
75+
self.class.config
76+
end
15377
end
15478

15579
def call(env)
@@ -158,19 +82,16 @@ def call(env)
15882
env['PATH_INFO'] = PathNormalizer.normalize_path(env['PATH_INFO'])
15983
request = Rack::Attack::Request.new(env)
16084

161-
if safelisted?(request)
85+
if @config.safelisted?(request)
16286
@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)
87+
elsif @config.blocklisted?(request)
88+
@config.blocklisted_response.call(env)
89+
elsif @config.throttled?(request)
90+
@config.throttled_response.call(env)
16791
else
168-
tracked?(request)
92+
@config.tracked?(request)
16993
@app.call(env)
17094
end
17195
end
172-
173-
extend Forwardable
174-
def_delegators self, :safelisted?, :blocklisted?, :throttled?, :tracked?
17596
end
17697
end

lib/rack/attack/configuration.rb

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# frozen_string_literal: true
2+
3+
module Rack
4+
class Attack
5+
class Configuration
6+
attr_reader :safelists, :blocklists, :throttles, :anonymous_blocklists, :anonymous_safelists
7+
attr_accessor :blocklisted_response, :throttled_response
8+
9+
def initialize
10+
@safelists = {}
11+
@blocklists = {}
12+
@throttles = {}
13+
@tracks = {}
14+
@anonymous_blocklists = []
15+
@anonymous_safelists = []
16+
17+
@blocklisted_response = lambda { |_env| [403, { 'Content-Type' => 'text/plain' }, ["Forbidden\n"]] }
18+
@throttled_response = lambda do |env|
19+
retry_after = (env['rack.attack.match_data'] || {})[:period]
20+
[429, { 'Content-Type' => 'text/plain', 'Retry-After' => retry_after.to_s }, ["Retry later\n"]]
21+
end
22+
end
23+
24+
def safelist(name = nil, &block)
25+
safelist = Safelist.new(name, &block)
26+
27+
if name
28+
@safelists[name] = safelist
29+
else
30+
@anonymous_safelists << safelist
31+
end
32+
end
33+
34+
def blocklist(name = nil, &block)
35+
blocklist = Blocklist.new(name, &block)
36+
37+
if name
38+
@blocklists[name] = blocklist
39+
else
40+
@anonymous_blocklists << blocklist
41+
end
42+
end
43+
44+
def blocklist_ip(ip_address)
45+
@anonymous_blocklists << Blocklist.new { |request| IPAddr.new(ip_address).include?(IPAddr.new(request.ip)) }
46+
end
47+
48+
def safelist_ip(ip_address)
49+
@anonymous_safelists << Safelist.new { |request| IPAddr.new(ip_address).include?(IPAddr.new(request.ip)) }
50+
end
51+
52+
def throttle(name, options, &block)
53+
@throttles[name] = Throttle.new(name, options, &block)
54+
end
55+
56+
def track(name, options = {}, &block)
57+
@tracks[name] = Track.new(name, options, &block)
58+
end
59+
60+
def safelisted?(request)
61+
@anonymous_safelists.any? { |safelist| safelist.matched_by?(request) } ||
62+
@safelists.any? { |_name, safelist| safelist.matched_by?(request) }
63+
end
64+
65+
def blocklisted?(request)
66+
@anonymous_blocklists.any? { |blocklist| blocklist.matched_by?(request) } ||
67+
@blocklists.any? { |_name, blocklist| blocklist.matched_by?(request) }
68+
end
69+
70+
def throttled?(request)
71+
@throttles.any? do |_name, throttle|
72+
throttle.matched_by?(request)
73+
end
74+
end
75+
76+
def tracked?(request)
77+
@tracks.each_value do |track|
78+
track.matched_by?(request)
79+
end
80+
end
81+
82+
def clear_configuration
83+
@safelists = {}
84+
@blocklists = {}
85+
@throttles = {}
86+
@tracks = {}
87+
@anonymous_blocklists = []
88+
@anonymous_safelists = []
89+
end
90+
end
91+
end
92+
end

0 commit comments

Comments
 (0)