rack-defense 0.1.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.
- checksums.yaml +7 -0
- data/README.md +129 -0
- data/Rakefile +7 -0
- data/lib/rack/defense.rb +70 -0
- data/lib/rack/defense/throttle_counter.rb +36 -0
- data/lib/rack/defense/version.rb +5 -0
- data/spec/defense_ban_spec.rb +35 -0
- data/spec/defense_throttle_spec.rb +119 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/throttle_counter_spec.rb +48 -0
- metadata +154 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3ece635cf91bb8348db49468754161d10d7263d8
|
4
|
+
data.tar.gz: 9e83f747514c973078fde57e36f370d1429e8188
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c2c434565856a67d7dba649a8d17fc140242e086038c4f61fc6f08e05a6215779f8f76f0baf2fcc0a0fd5e9ff2fcacedb0ba9cc5e737ef21fbd417b29792e2fe
|
7
|
+
data.tar.gz: 78d36f081d9084174c7922d48977f48cf2e38eabf800ddbcff5d17388eb312175d0c32e63765eef393b0054568a535bd716eee06fb955316d80615b53d511c70
|
data/README.md
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
Rack::Defense
|
2
|
+
=============
|
3
|
+
|
4
|
+
A Rack middleware for throttling and filtering requests.
|
5
|
+
|
6
|
+
[](https://codeclimate.com/github/Sinbadsoft/rack-defense) [](https://travis-ci.org/Sinbadsoft/rack-defense)
|
7
|
+
[](https://gemnasium.com/Sinbadsoft/rack-defense)
|
8
|
+
|
9
|
+
Rack::Defense is a Rack middleware that allows you to easily add request rate limiting and request filtering to your Rack based application (Ruby On Rails, Sinatra etc.).
|
10
|
+
|
11
|
+
* Throttling (aka rate limiting) happens on __sliding window__ using the provided period, request criteria and maximum request number. It uses Redis to track the request rate.
|
12
|
+
|
13
|
+
* Request filtering allows to reject requests based on provided critera.
|
14
|
+
|
15
|
+
Rack::Defense has a small footprint and only two dependencies: [rack](https://github.com/rack/rack) and [redis](https://github.com/redis/redis-rb).
|
16
|
+
|
17
|
+
## Getting started
|
18
|
+
|
19
|
+
Install the rack-defense gem; or add it to you Gemfile with bundler:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
# In your Gemfile
|
23
|
+
gem 'rack-defense'
|
24
|
+
```
|
25
|
+
Tell your app to use the Rack::Defense middleware. For Rails 3+ apps:
|
26
|
+
```
|
27
|
+
# In config/application.rb
|
28
|
+
config.middleware.use Rack::Defense
|
29
|
+
```
|
30
|
+
|
31
|
+
Or for Rackup files:
|
32
|
+
```
|
33
|
+
# In config.ru
|
34
|
+
use Rack::Defense
|
35
|
+
```
|
36
|
+
|
37
|
+
Add a `rack-defense.rb` file to `config/initalizers/`:
|
38
|
+
```ruby
|
39
|
+
# In config/initializers/rack-defense.rb
|
40
|
+
Rack::Defense.setup do |config|
|
41
|
+
# your configuration here
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
## Throttling
|
46
|
+
The Rack::Defense middleware evaluates the throttling criterias (lambadas) against the incoming request. If the return value is falsy, the request is not throttled. Otherwise, the returned value is used as a key to throttle the request. The returned key could be the request IP, user name, API token or any discriminator to throttle the requests against.
|
47
|
+
|
48
|
+
### Examples
|
49
|
+
|
50
|
+
Throttle POST requests for path `/login` with a maximum rate of 3 request per minute per IP
|
51
|
+
```ruby
|
52
|
+
Rack::Defense.setup do |config|
|
53
|
+
config.throttle('login', 3, 60 * 1000) do |req|
|
54
|
+
req.ip if req.path == '/login' && req.post?
|
55
|
+
end
|
56
|
+
end
|
57
|
+
```
|
58
|
+
|
59
|
+
Throttle GET requests for path `/image` with a maximum rate of 50 request per second per API token
|
60
|
+
```ruby
|
61
|
+
Rack::Defense.setup do |config|
|
62
|
+
config.throttle('api', 50, 1000) do |req|
|
63
|
+
req.env['HTTP_AUTHORIZATION'] if %r{^/api/} =~ req.path
|
64
|
+
end
|
65
|
+
end
|
66
|
+
```
|
67
|
+
|
68
|
+
### Redis Configuration
|
69
|
+
|
70
|
+
Rack::Defense uses Redis to track request rates. By default, the `REDIS_URL` environment variable is used to setup the store. If not set, it falls back to host `127.0.0.1` port `6379`.
|
71
|
+
The redis store can be setup with either a connection url:
|
72
|
+
```ruby
|
73
|
+
Rack::Defense.setup do |config|
|
74
|
+
store = "redis://:p4ssw0rd@10.0.1.1:6380/15"
|
75
|
+
end
|
76
|
+
```
|
77
|
+
or directly with a connection object:
|
78
|
+
```ruby
|
79
|
+
Rack::Defense.setup do |config|
|
80
|
+
store = Redis.new(host: "10.0.1.1", port: 6380, db: 15)
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
84
|
+
## Filtering
|
85
|
+
|
86
|
+
Rack::Defense can reject requests based on arbitrary properties of the request. Matching requests are filtered.
|
87
|
+
|
88
|
+
### Examples
|
89
|
+
Allow only a whitelist of ips for a given path:
|
90
|
+
```ruby
|
91
|
+
Rack::Defense.setup do |config|
|
92
|
+
config.ban('ip_whitelist') do |req|
|
93
|
+
req.path == '/protected' && !['192.168.0.1', '127.0.0.1'].include?(req.ip)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
```
|
97
|
+
|
98
|
+
Allow only requests with a known API authorization token:
|
99
|
+
```ruby
|
100
|
+
Rack::Defense.setup do |config|
|
101
|
+
config.ban('allow_only_ip_list') do |req|
|
102
|
+
%r{^/api/} =~ req.path && Redis.current.sismember('apitokens', req.env['HTTP_AUTHORIZATION'])
|
103
|
+
end
|
104
|
+
end
|
105
|
+
```
|
106
|
+
|
107
|
+
## Response configuration
|
108
|
+
|
109
|
+
By default, Rack::Defense returns `429 Too Many Requests` and `403 Forbidden` respectively for throttled and banned requests. These responses can be fully configured in the setup:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
Rack::Defense.setup do |config|
|
113
|
+
config.banned_response =
|
114
|
+
->(env) { [404, {'Content-Type' => 'text/plain'}, ["Not Found"]] }
|
115
|
+
|
116
|
+
config.throttled_response =
|
117
|
+
->(env) { [503, {'Content-Type' => 'text/plain'}, ["Service Unavailable"]] }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
122
|
+
## License
|
123
|
+
|
124
|
+
Copyright [Sinbadsoft](http://www.sinbadsoft.com).
|
125
|
+
|
126
|
+
Licensed under the [MIT License](http://opensource.org/licenses/MIT).
|
127
|
+
|
128
|
+
|
129
|
+
|
data/Rakefile
ADDED
data/lib/rack/defense.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'redis'
|
3
|
+
|
4
|
+
class Rack::Defense
|
5
|
+
autoload :ThrottleCounter, 'rack/defense/throttle_counter'
|
6
|
+
|
7
|
+
class Config
|
8
|
+
attr_accessor :banned_response
|
9
|
+
attr_accessor :throttled_response
|
10
|
+
attr_reader :bans
|
11
|
+
attr_reader :throttles
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@throttles, @bans = {}, {}
|
15
|
+
self.banned_response = ->(env) { [403, {'Content-Type' => 'text/plain'}, ["Forbidden\n"]] }
|
16
|
+
self.throttled_response = ->(env) { [429, {'Content-Type' => 'text/plain'}, ["Retry later\n"]] }
|
17
|
+
end
|
18
|
+
|
19
|
+
def throttle(name, max_requests, period, &block)
|
20
|
+
counter = ThrottleCounter.new(name, max_requests, period, store)
|
21
|
+
throttles[name] = lambda do |req|
|
22
|
+
key = block.call(req)
|
23
|
+
key && counter.throttle?(key)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def ban(name, &block)
|
28
|
+
bans[name] = block
|
29
|
+
end
|
30
|
+
|
31
|
+
def store=(value)
|
32
|
+
@store = value.is_a?(String) ? Redis.new(url: value) : value
|
33
|
+
end
|
34
|
+
|
35
|
+
def store
|
36
|
+
# Redis.new uses REDIS_URL environment variable by default as URL.
|
37
|
+
# See https://github.com/redis/redis-rb
|
38
|
+
@store ||= Redis.new
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class << self
|
43
|
+
attr_accessor :config
|
44
|
+
|
45
|
+
def setup(&block)
|
46
|
+
self.config = Config.new
|
47
|
+
yield config
|
48
|
+
end
|
49
|
+
|
50
|
+
def ban?(req)
|
51
|
+
config.bans.any? { |name, filter| filter.call(req) }
|
52
|
+
end
|
53
|
+
|
54
|
+
def throttle?(req)
|
55
|
+
config.throttles.any? { |name, filter| filter.call(req) }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def initialize(app)
|
60
|
+
@app = app
|
61
|
+
end
|
62
|
+
|
63
|
+
def call(env)
|
64
|
+
klass, config = self.class, self.class.config
|
65
|
+
req = ::Rack::Request.new(env)
|
66
|
+
return config.banned_response.call(env) if klass.ban?(req)
|
67
|
+
return config.throttled_response.call(env) if klass.throttle?(req)
|
68
|
+
@app.call(env)
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Rack
|
2
|
+
class Defense
|
3
|
+
class ThrottleCounter
|
4
|
+
|
5
|
+
KEY_PREFIX = 'rack-defense'
|
6
|
+
|
7
|
+
attr_accessor :logger
|
8
|
+
attr_accessor :name
|
9
|
+
|
10
|
+
def initialize(name, max_requests, time_period, store)
|
11
|
+
@name, @max_requests, @time_period = name.to_s, max_requests.to_i, time_period.to_i
|
12
|
+
@store = store
|
13
|
+
end
|
14
|
+
|
15
|
+
def throttle?(key, timestamp=nil)
|
16
|
+
timestamp ||= (Time.now.utc.to_f * 1000).to_i
|
17
|
+
@store.eval SCRIPT,
|
18
|
+
["#{KEY_PREFIX}:#{@name}:#{key}"],
|
19
|
+
[timestamp, @max_requests, @time_period]
|
20
|
+
end
|
21
|
+
|
22
|
+
SCRIPT = <<-LUA_SCRIPT
|
23
|
+
local key = KEYS[1]
|
24
|
+
local timestamp, max_requests, time_period = tonumber(ARGV[1]), tonumber(ARGV[2]), tonumber(ARGV[3])
|
25
|
+
if redis.call('rpush', key, timestamp) <= max_requests then
|
26
|
+
return false
|
27
|
+
else
|
28
|
+
return (timestamp - tonumber(redis.call('lpop', key))) <= time_period
|
29
|
+
end
|
30
|
+
LUA_SCRIPT
|
31
|
+
|
32
|
+
private_constant :SCRIPT
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
describe 'Rack::Defense::ban' do
|
3
|
+
before do
|
4
|
+
#
|
5
|
+
# configure the Rack::Defense middleware with a ban
|
6
|
+
# strategy.
|
7
|
+
#
|
8
|
+
Rack::Defense.setup do |config|
|
9
|
+
# allow only given ips on path
|
10
|
+
config.ban('allow_only_ip_list') do |req|
|
11
|
+
req.path == '/protected' && !['192.168.0.1', '127.0.0.1'].include?(req.ip)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
it 'ban matching requests' do
|
16
|
+
check_request(:get, '/protected','192.168.0.2')
|
17
|
+
check_request(:post, '/protected','192.168.0.3')
|
18
|
+
check_request(:patch, '/protected','192.168.0.2')
|
19
|
+
check_request(:delete, '/protected','192.168.0.2')
|
20
|
+
end
|
21
|
+
it 'allow non matching request' do
|
22
|
+
check_request(:get, '/protected','192.168.0.1')
|
23
|
+
check_request(:get, '/protected','127.0.0.1')
|
24
|
+
check_request(:get, '/protectedx','192.168.0.5')
|
25
|
+
check_request(:post, '/allowed','192.168.0.5')
|
26
|
+
end
|
27
|
+
|
28
|
+
def check_request(verb, path, ip)
|
29
|
+
send verb, path, {}, 'REMOTE_ADDR' => ip
|
30
|
+
expected_status = path == '/protected' && !['192.168.0.1', '127.0.0.1'].include?(ip) ?
|
31
|
+
status_banned : status_ok
|
32
|
+
assert_equal expected_status, last_response.status
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Rack::Defense::throttle' do
|
4
|
+
PERIOD = 60 * 1000 # in milliseconds
|
5
|
+
|
6
|
+
before do
|
7
|
+
@start_time = Time.utc(2015, 10, 30, 21, 0, 0)
|
8
|
+
|
9
|
+
#
|
10
|
+
# configure the Rack::Defense middleware with two throttling
|
11
|
+
# strategies.
|
12
|
+
#
|
13
|
+
Rack::Defense.setup do |config|
|
14
|
+
# allow only 3 post requests on path '/login' per PERIOD per ip
|
15
|
+
config.throttle('login', 3, PERIOD) do |req|
|
16
|
+
req.ip if req.path == '/login' && req.post?
|
17
|
+
end
|
18
|
+
|
19
|
+
# allow only 50 get requests on path '/search' per PERIOD per ip
|
20
|
+
config.throttle('res', 30, PERIOD) do |req|
|
21
|
+
req.ip if req.path == '/search' && req.get?
|
22
|
+
end
|
23
|
+
|
24
|
+
# allow only 5 get requests on path /api/* per PERIOD per authorization token
|
25
|
+
config.throttle('api', 5, PERIOD) do |req|
|
26
|
+
req.env['HTTP_AUTHORIZATION'] if %r{^/api/} =~ req.path
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
it 'allow ok post' do
|
31
|
+
check_post_request
|
32
|
+
end
|
33
|
+
it 'allow ok get' do
|
34
|
+
check_get_request
|
35
|
+
end
|
36
|
+
it 'ban get requests higher than acceptable rate' do
|
37
|
+
10.times do |period|
|
38
|
+
50.times { |offset| check_get_request(offset + period*PERIOD) }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
it 'ban post requests higher than acceptable rate' do
|
42
|
+
10.times do |period|
|
43
|
+
7.times { |offset| check_post_request(offset + period*PERIOD) }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
it 'not have side effects between differrent throttle rules with mixed requests' do
|
47
|
+
10.times do |period|
|
48
|
+
50.times do |offset|
|
49
|
+
check_get_request(offset + period*PERIOD)
|
50
|
+
check_post_request(offset + period*PERIOD)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
it 'not have side effects between request filtered by the same rule but with different keys' do
|
55
|
+
10.times do |period|
|
56
|
+
50.times do |offset|
|
57
|
+
check_get_request(offset + period*PERIOD, ip='192.168.0.1')
|
58
|
+
check_get_request(offset + period*PERIOD, ip='192.168.0.2')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
it 'allow unfiltered requests' do
|
63
|
+
50.times do |offset|
|
64
|
+
time = @start_time + offset
|
65
|
+
Timecop.freeze(time) do
|
66
|
+
# the rule matches the '/search' path and not '/searchx'
|
67
|
+
get '/searchx', {}, 'REMOTE_ADDR' => '192.168.0.1'
|
68
|
+
assert_equal status_ok, last_response.status
|
69
|
+
|
70
|
+
# the rule matches only get requests and not post
|
71
|
+
post '/search', {}, 'REMOTE_ADDR' => '192.168.0.1'
|
72
|
+
assert_equal status_ok, last_response.status
|
73
|
+
end
|
74
|
+
end
|
75
|
+
10.times do |offset|
|
76
|
+
time = @start_time + offset
|
77
|
+
Timecop.freeze(time) do
|
78
|
+
# the rule matches only post and not get
|
79
|
+
get '/login', {}, 'REMOTE_ADDR' => '192.168.0.1'
|
80
|
+
assert_equal status_ok, last_response.status
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
it 'not have side effects between unfiltered and filtered requests' do
|
85
|
+
50.times do |offset|
|
86
|
+
time = @start_time + offset
|
87
|
+
Timecop.freeze(time) do
|
88
|
+
get '/searchx', {}, 'REMOTE_ADDR' => '192.168.0.1'
|
89
|
+
assert_equal status_ok, last_response.status
|
90
|
+
get '/search', {}, 'REMOTE_ADDR' => '192.168.0.1'
|
91
|
+
assert_equal offset < 30 ? status_ok : status_throttled, last_response.status
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
it 'should work with key in http header' do
|
96
|
+
10.times do |offset|
|
97
|
+
check_request(:get, '/api/action', offset, 5,
|
98
|
+
'192.168.0.1',
|
99
|
+
'HTTP_AUTHORIZATION' => 'token api_token_here')
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def check_get_request(time_offset=0, ip='192.168.0.1', path='/search')
|
104
|
+
check_request(:get, path, time_offset, 30, ip)
|
105
|
+
end
|
106
|
+
|
107
|
+
def check_post_request(time_offset=0, ip='192.168.0.1', path='/login')
|
108
|
+
check_request(:post, path, time_offset, 3, ip)
|
109
|
+
end
|
110
|
+
|
111
|
+
def check_request(verb, path, time_offset, max_requests, ip, headers={})
|
112
|
+
Timecop.freeze(@start_time + time_offset) do
|
113
|
+
send verb, path, {}, headers.merge('REMOTE_ADDR' => ip)
|
114
|
+
expected_status = (time_offset % PERIOD) >= max_requests ? status_throttled : status_ok
|
115
|
+
assert_equal expected_status, last_response.status, "offset #{time_offset}"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'rack/test'
|
3
|
+
require 'redis'
|
4
|
+
require 'timecop'
|
5
|
+
require 'rack/defense'
|
6
|
+
|
7
|
+
class MiniTest::Spec
|
8
|
+
include Rack::Test::Methods
|
9
|
+
|
10
|
+
def status_ok; 200 end
|
11
|
+
def status_throttled; 429 end
|
12
|
+
def status_banned; 403 end
|
13
|
+
|
14
|
+
def app
|
15
|
+
Rack::Builder.new {
|
16
|
+
use Rack::Defense
|
17
|
+
run ->(env) { [200, {}, ['Hello World']] }
|
18
|
+
}.to_app
|
19
|
+
end
|
20
|
+
|
21
|
+
before do
|
22
|
+
Timecop.safe_mode = true
|
23
|
+
keys = Redis.current.keys("#{Rack::Defense::ThrottleCounter::KEY_PREFIX}:*")
|
24
|
+
Redis.current.del *keys if keys.any?
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
describe Rack::Defense::ThrottleCounter do
|
4
|
+
before do
|
5
|
+
@counter = Rack::Defense::ThrottleCounter.new('upload_photo', 5, 10, Redis.current)
|
6
|
+
@key = '192.168.0.1'
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '.throttle?' do
|
10
|
+
it 'allow request number max_requests if after period' do
|
11
|
+
do_max_requests_minus_one
|
12
|
+
refute @counter.throttle? @key, 11
|
13
|
+
end
|
14
|
+
it 'block request number max_requests if in period' do
|
15
|
+
do_max_requests_minus_one
|
16
|
+
assert @counter.throttle? @key, 10
|
17
|
+
end
|
18
|
+
it 'allow consecutive valid periods' do
|
19
|
+
(0..20).each { |i| do_max_requests_minus_one(11 * i) }
|
20
|
+
end
|
21
|
+
it 'block consecutive invalid requests' do
|
22
|
+
do_max_requests_minus_one
|
23
|
+
(0..20).each { |i| assert @counter.throttle?(@key, 10 + i) }
|
24
|
+
end
|
25
|
+
it 'use a sliding window and not reset count after each full period' do
|
26
|
+
[5, 6, 7, 8, 9].each { |t| refute @counter.throttle?(@key, t), "timestamp #{t}" }
|
27
|
+
[12, 13, 14, 15].each { |t| assert @counter.throttle?(@key, t), "timestamp #{t}"}
|
28
|
+
end
|
29
|
+
it 'should unblock after blocking requests' do
|
30
|
+
do_max_requests_minus_one
|
31
|
+
assert @counter.throttle? @key, 10
|
32
|
+
assert @counter.throttle? @key, 11
|
33
|
+
refute @counter.throttle? @key, 16
|
34
|
+
end
|
35
|
+
it 'should include throttled(blocked) request into the request count' do
|
36
|
+
[0, 1, 2, 3, 4].each { |t| refute @counter.throttle?(@key, t), "timestamp #{t}" }
|
37
|
+
assert @counter.throttle? @key, 10
|
38
|
+
[16, 17, 18, 19].each { |t| refute @counter.throttle?(@key, t), "timestamp #{t}" }
|
39
|
+
assert @counter.throttle? @key, 20
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def do_max_requests_minus_one(offset=0)
|
44
|
+
[0, 2, 3, 5, 9].map { |t| t + offset }.each do |t|
|
45
|
+
refute @counter.throttle?(@key, t), "timestamp #{t}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
metadata
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-defense
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Chaker Nakhli
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-10-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rack
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.5.2
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.5'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.5.2
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: redis
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '3.1'
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 3.1.0
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '3.1'
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 3.1.0
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: rake
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - "~>"
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '10.3'
|
60
|
+
type: :development
|
61
|
+
prerelease: false
|
62
|
+
version_requirements: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - "~>"
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '10.3'
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: minitest
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - "~>"
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '5.4'
|
74
|
+
type: :development
|
75
|
+
prerelease: false
|
76
|
+
version_requirements: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - "~>"
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '5.4'
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: rack-test
|
83
|
+
requirement: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - "~>"
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0.6'
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - "~>"
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0.6'
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: timecop
|
97
|
+
requirement: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - "~>"
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0.7'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - "~>"
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0.7'
|
109
|
+
description: A rack middleware for throttling and filtering requests
|
110
|
+
email:
|
111
|
+
- chaker.nakhli@sinbadsoft.com
|
112
|
+
executables: []
|
113
|
+
extensions: []
|
114
|
+
extra_rdoc_files: []
|
115
|
+
files:
|
116
|
+
- README.md
|
117
|
+
- Rakefile
|
118
|
+
- lib/rack/defense.rb
|
119
|
+
- lib/rack/defense/throttle_counter.rb
|
120
|
+
- lib/rack/defense/version.rb
|
121
|
+
- spec/defense_ban_spec.rb
|
122
|
+
- spec/defense_throttle_spec.rb
|
123
|
+
- spec/spec_helper.rb
|
124
|
+
- spec/throttle_counter_spec.rb
|
125
|
+
homepage: http://github.com/sinbadsoft/rack-defense
|
126
|
+
licenses:
|
127
|
+
- MIT
|
128
|
+
metadata: {}
|
129
|
+
post_install_message:
|
130
|
+
rdoc_options:
|
131
|
+
- "--charset=UTF-8"
|
132
|
+
require_paths:
|
133
|
+
- lib
|
134
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 1.9.2
|
139
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - ">="
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '0'
|
144
|
+
requirements: []
|
145
|
+
rubyforge_project:
|
146
|
+
rubygems_version: 2.2.2
|
147
|
+
signing_key:
|
148
|
+
specification_version: 4
|
149
|
+
summary: Throttle and filter requests
|
150
|
+
test_files:
|
151
|
+
- spec/spec_helper.rb
|
152
|
+
- spec/throttle_counter_spec.rb
|
153
|
+
- spec/defense_ban_spec.rb
|
154
|
+
- spec/defense_throttle_spec.rb
|