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 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