rack-attack 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
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
|