rack-attack 1.2.0 → 1.3.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.
Potentially problematic release.
This version of rack-attack might be problematic. Click here for more details.
- data/README.md +8 -1
- data/lib/rack/attack/cache.rb +4 -4
- data/lib/rack/attack/throttle.rb +8 -1
- data/lib/rack/attack/version.rb +1 -1
- data/spec/rack_attack_spec.rb +11 -4
- metadata +2 -2
data/README.md
CHANGED
@@ -60,7 +60,9 @@ Note that `req` is a [Rack::Request](http://rack.rubyforge.org/doc/classes/Rack/
|
|
60
60
|
|
61
61
|
# Throttle requests to 5 requests per second per ip
|
62
62
|
Rack::Attack.throttle('req/ip', :limit => 5, :period => 1.second) do |req|
|
63
|
-
# If the return value is truthy, the cache key for
|
63
|
+
# If the return value is truthy, the cache key for
|
64
|
+
# "rack::attack:#{Time.now.to_i/1.second}:req/ip:#{req.ip}"
|
65
|
+
# is incremented and compared with the limit.
|
64
66
|
# If falsy, the cache key is neither incremented or checked.
|
65
67
|
req.ip
|
66
68
|
end
|
@@ -100,6 +102,11 @@ Similarly for blacklisted responses:
|
|
100
102
|
[ 503, {}, ['Blocked']]
|
101
103
|
end
|
102
104
|
|
105
|
+
For responses that did not exceed a throttle limit, Rack::Attack annotates the environment with match data.
|
106
|
+
For example, in out `reqs/ip` throttle above, a matching request would have:
|
107
|
+
|
108
|
+
request.env['rack.attack.throttle_data']['req/ip'] # => { :period => 1, :limit => 5, :count => n }
|
109
|
+
|
103
110
|
## Logging & Instrumentation
|
104
111
|
|
105
112
|
Rack::Attack uses the [ActiveSupport::Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) API if available.
|
data/lib/rack/attack/cache.rb
CHANGED
@@ -8,12 +8,12 @@ module Rack
|
|
8
8
|
@prefix = 'rack::attack'
|
9
9
|
end
|
10
10
|
|
11
|
-
def count(unprefixed_key,
|
12
|
-
key = "#{prefix}:#{unprefixed_key}"
|
13
|
-
result = store.increment(key, 1
|
11
|
+
def count(unprefixed_key, period)
|
12
|
+
key = "#{prefix}:#{Time.now.to_i/period}:#{unprefixed_key}"
|
13
|
+
result = store.increment(key, 1)
|
14
14
|
# NB: Some stores return nil when incrementing uninitialized values
|
15
15
|
if result.nil?
|
16
|
-
store.write(key, 1
|
16
|
+
store.write(key, 1)
|
17
17
|
end
|
18
18
|
result || 1
|
19
19
|
end
|
data/lib/rack/attack/throttle.rb
CHANGED
@@ -21,11 +21,18 @@ module Rack
|
|
21
21
|
|
22
22
|
key = "#{name}:#{discriminator}"
|
23
23
|
count = cache.count(key, period)
|
24
|
+
data = {
|
25
|
+
:count => count,
|
26
|
+
:period => period,
|
27
|
+
:limit => limit
|
28
|
+
}
|
29
|
+
(req.env['rack.attack.throttle_data'] ||= {})[name] = data
|
30
|
+
|
24
31
|
(count > limit).tap do |throttled|
|
25
32
|
if throttled
|
26
33
|
req.env['rack.attack.matched'] = name
|
27
34
|
req.env['rack.attack.match_type'] = :throttle
|
28
|
-
req.env['rack.attack.match_data'] =
|
35
|
+
req.env['rack.attack.match_data'] = data
|
29
36
|
Rack::Attack.instrument(req)
|
30
37
|
end
|
31
38
|
end
|
data/lib/rack/attack/version.rb
CHANGED
data/spec/rack_attack_spec.rb
CHANGED
@@ -67,8 +67,9 @@ describe 'Rack::Attack' do
|
|
67
67
|
|
68
68
|
describe 'with a throttle' do
|
69
69
|
before do
|
70
|
+
@period = 60 # Use a long period; failures due to cache key rotation less likely
|
70
71
|
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
|
71
|
-
Rack::Attack.throttle('ip/sec', :limit => 1, :period =>
|
72
|
+
Rack::Attack.throttle('ip/sec', :limit => 1, :period => @period) { |req| req.ip }
|
72
73
|
end
|
73
74
|
|
74
75
|
it('should have a throttle'){ Rack::Attack.throttles.key?('ip/sec') }
|
@@ -77,7 +78,13 @@ describe 'Rack::Attack' do
|
|
77
78
|
describe 'a single request' do
|
78
79
|
before { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' }
|
79
80
|
it 'should set the counter for one request' do
|
80
|
-
|
81
|
+
key = "rack::attack:#{Time.now.to_i/@period}:ip/sec:1.2.3.4"
|
82
|
+
Rack::Attack.cache.store.read(key).must_equal 1
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should populate throttle data' do
|
86
|
+
data = { :count => 1, :limit => 1, :period => @period }
|
87
|
+
last_request.env['rack.attack.throttle_data']['ip/sec'].must_equal data
|
81
88
|
end
|
82
89
|
end
|
83
90
|
describe "with 2 requests" do
|
@@ -90,10 +97,10 @@ describe 'Rack::Attack' do
|
|
90
97
|
it 'should tag the env' do
|
91
98
|
last_request.env['rack.attack.matched'].must_equal 'ip/sec'
|
92
99
|
last_request.env['rack.attack.match_type'].must_equal :throttle
|
93
|
-
last_request.env['rack.attack.match_data'].must_equal({:count => 2, :limit => 1, :period =>
|
100
|
+
last_request.env['rack.attack.match_data'].must_equal({:count => 2, :limit => 1, :period => @period})
|
94
101
|
end
|
95
102
|
it 'should set a Retry-After header' do
|
96
|
-
last_response.headers['Retry-After'].must_equal
|
103
|
+
last_response.headers['Retry-After'].must_equal @period.to_s
|
97
104
|
end
|
98
105
|
end
|
99
106
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-attack
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-08-
|
12
|
+
date: 2012-08-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rack
|