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 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 "rack::attack:req/ip:#{req.ip}" is incremented and checked.
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.
@@ -8,12 +8,12 @@ module Rack
8
8
  @prefix = 'rack::attack'
9
9
  end
10
10
 
11
- def count(unprefixed_key, expires_in)
12
- key = "#{prefix}:#{unprefixed_key}"
13
- result = store.increment(key, 1, :expires_in => expires_in)
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, :expires_in => expires_in)
16
+ store.write(key, 1)
17
17
  end
18
18
  result || 1
19
19
  end
@@ -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'] = {:count => count, :period => period, :limit => limit}
35
+ req.env['rack.attack.match_data'] = data
29
36
  Rack::Attack.instrument(req)
30
37
  end
31
38
  end
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  module Attack
3
- VERSION = '1.2.0'
3
+ VERSION = '1.3.0'
4
4
  end
5
5
  end
@@ -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 => 1) { |req| req.ip }
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
- Rack::Attack.cache.store.read('rack::attack:ip/sec:1.2.3.4').must_equal 1
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 => 1})
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 '1'
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.2.0
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-06 00:00:00.000000000 Z
12
+ date: 2012-08-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack