Whitelists support

This commit is contained in:
Aaron Suggs 2012-07-26 17:29:09 -04:00
parent 885b19c633
commit 9284a08cc3
5 changed files with 130 additions and 27 deletions

View file

@ -1,29 +1,83 @@
require 'rack'
module Rack
class Attack
class << self
module Rack::Attack
require 'rack/attack/cache'
attr_reader :blocks, :throttles, :whitelists
class << self
def block(name, &block)
(@blocks ||= {})[name] = block
end
def throttle
end
def whitelist
end
attr_reader :cache, :notifier
def whitelist(name, &block)
(@whitelists ||= {})[name] = block
end
def initialize(app)
def block(name, &block)
(@blocks ||= {})[name] = block
end
def throttle
end
def whitelists; @whitelists ||= {}; end
def blocks; @blocks ||= {}; end
def throttles; @throttles ||= {}; end
def new(app)
@cache = Cache.new
@notifier = ActiveSupport::Notifications if defined?(ActiveSupport::Notifications)
@app = app
self
end
def call(env)
puts 'Rack attack!'
@app.call(env)
req = Rack::Request.new(env)
if whitelisted?(req)
return @app.call(env)
end
if blocked?(req)
blocked_response
elsif throttled?(req)
else
@app.call(env)
end
end
def whitelisted?(req)
whitelists.any? do |name, block|
block[req].tap{ |match|
instrument(:type => :whitelist, :name => name, :request => req) if match
}
end
end
def blocked?(req)
blocks.any? do |name, block|
block[req].tap { |match|
instrument(:type => :block, :name => name, :request => req) if match
}
end
end
def throttled?(req)
false
end
def instrument(payload)
notifier.instrument('rack.attack', payload) if notifier
end
def blocked_response
[503, {}, ['Blocked']]
end
def throttled_response
[503, {}, ['Throttled']]
end
def clear!
@whitelists, @blocks, @throttles = {}, {}, {}
end
end

21
lib/rack/attack/cache.rb Normal file
View file

@ -0,0 +1,21 @@
module Rack
module Attack
class Cache
attr_accessor :store
def initialize
@store = ::Rails.cache if defined?(::Rails.cache)
end
def count(key, expires_in)
result = store.increment(1, :expires_in => expires_in)
# NB: Some stores return nil when incrementing uninitialized values
if result.nil?
store.write(key, 1, :expires_in => expires_in)
end
result || 1
end
end
end
end

View file

@ -1,5 +1,5 @@
module Rack
class Attack
module Attack
VERSION = '0.0.1'
end
end

View file

@ -24,6 +24,7 @@ Gem::Specification.new do |s|
s.add_dependency 'rack'
s.add_development_dependency 'minitest'
s.add_development_dependency 'rack-test'
s.add_development_dependency 'activesupport', '>= 3.0.0'
s.add_development_dependency 'debugger', '~> 1.1.3'
end

View file

@ -3,10 +3,6 @@ require_relative 'spec_helper'
describe 'Rack::Attack' do
include Rack::Test::Methods
before do
Rack::Attack.block("ip 1.2.3.4") {|req| req.ip == '1.2.3.4' }
end
def app
Rack::Builder.new {
use Rack::Attack
@ -14,14 +10,45 @@ describe 'Rack::Attack' do
}.to_app
end
it 'has a block' do
Rack::Attack.blocks.class.must_equal Hash
def self.allow_ok_requests
it "must allow ok requests" do
get '/', {}, 'REMOTE_ADDR' => '127.0.0.1'
last_response.status.must_equal 200
last_response.body.must_equal 'Hello World'
end
end
it "says hello" do
get '/'
last_response.status.must_equal 200
last_response.body.must_equal 'Hello World'
after { Rack::Attack.clear! }
allow_ok_requests
describe 'with a block' do
before do
@bad_ip = '1.2.3.4'
Rack::Attack.block("ip #{@bad_ip}") {|req| req.ip == @bad_ip }
end
it('has a block') { Rack::Attack.blocks.key?("ip #{@bad_ip}") }
it "should block bad requests" do
get '/', {}, 'REMOTE_ADDR' => @bad_ip
last_response.status.must_equal 503
end
allow_ok_requests
describe "and with a whitelist" do
before do
@good_ua = 'GoodUA'
Rack::Attack.whitelist("good ua") {|req| req.user_agent == @good_ua }
end
it('has a whitelist'){ Rack::Attack.whitelists.key?("good ua") }
it "should allow whitelists before blocks" do
get '/', {}, 'REMOTE_ADDR' => @bad_ip, 'HTTP_USER_AGENT' => @good_ua
last_response.status.must_equal 200
end
end
end