From c3a077442a525816736773f9aa7fe7a0bc8c432b Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Fri, 28 Mar 2014 14:53:51 +1100 Subject: [PATCH 1/7] Make Rack::Attack a class and Rack::Attack.new return an instance of that class, rather than Rack::Attack. --- lib/rack/attack.rb | 59 +++++++++++++++++----------------- lib/rack/attack/allow2ban.rb | 2 +- lib/rack/attack/blacklist.rb | 2 +- lib/rack/attack/cache.rb | 2 +- lib/rack/attack/check.rb | 2 +- lib/rack/attack/fail2ban.rb | 2 +- lib/rack/attack/store_proxy.rb | 2 +- lib/rack/attack/throttle.rb | 2 +- lib/rack/attack/track.rb | 2 +- lib/rack/attack/version.rb | 2 +- lib/rack/attack/whitelist.rb | 2 +- 11 files changed, 39 insertions(+), 40 deletions(-) diff --git a/lib/rack/attack.rb b/lib/rack/attack.rb index 91baafd..1ac1c6c 100644 --- a/lib/rack/attack.rb +++ b/lib/rack/attack.rb @@ -1,5 +1,6 @@ require 'rack' -module Rack::Attack + +class Rack::Attack autoload :Cache, 'rack/attack/cache' autoload :Check, 'rack/attack/check' autoload :Throttle, 'rack/attack/throttle' @@ -35,35 +36,6 @@ module Rack::Attack def throttles; @throttles ||= {}; end def tracks; @tracks ||= {}; end - def new(app) - @app = app - - # Set defaults - @notifier ||= ActiveSupport::Notifications if defined?(ActiveSupport::Notifications) - @blacklisted_response ||= lambda {|env| [403, {}, ["Forbidden\n"]] } - @throttled_response ||= lambda {|env| - retry_after = env['rack.attack.match_data'][:period] rescue nil - [429, {'Retry-After' => retry_after.to_s}, ["Retry later\n"]] - } - - self - end - - def call(env) - req = Rack::Request.new(env) - - if whitelisted?(req) - @app.call(env) - elsif blacklisted?(req) - blacklisted_response[env] - elsif throttled?(req) - throttled_response[env] - else - tracked?(req) - @app.call(env) - end - end - def whitelisted?(req) whitelists.any? do |name, whitelist| whitelist[req] @@ -101,4 +73,31 @@ module Rack::Attack end end + + # Set defaults + @notifier ||= ActiveSupport::Notifications if defined?(ActiveSupport::Notifications) + @blacklisted_response ||= lambda {|env| [403, {}, ["Forbidden\n"]] } + @throttled_response ||= lambda {|env| + retry_after = env['rack.attack.match_data'][:period] rescue nil + [429, {'Retry-After' => retry_after.to_s}, ["Retry later\n"]] + } + + def initialize(app) + @app = app + end + + def call(env) + req = Rack::Request.new(env) + + if self.class.whitelisted?(req) + @app.call(env) + elsif self.class.blacklisted?(req) + self.class.blacklisted_response[env] + elsif self.class.throttled?(req) + self.class.throttled_response[env] + else + self.class.tracked?(req) + @app.call(env) + end + end end diff --git a/lib/rack/attack/allow2ban.rb b/lib/rack/attack/allow2ban.rb index f5772c8..bf8c598 100644 --- a/lib/rack/attack/allow2ban.rb +++ b/lib/rack/attack/allow2ban.rb @@ -1,5 +1,5 @@ module Rack - module Attack + class Attack class Allow2Ban < Fail2Ban class << self protected diff --git a/lib/rack/attack/blacklist.rb b/lib/rack/attack/blacklist.rb index 4d2f666..c3a8341 100644 --- a/lib/rack/attack/blacklist.rb +++ b/lib/rack/attack/blacklist.rb @@ -1,5 +1,5 @@ module Rack - module Attack + class Attack class Blacklist < Check def initialize(name, block) super diff --git a/lib/rack/attack/cache.rb b/lib/rack/attack/cache.rb index 9c54505..e65fa84 100644 --- a/lib/rack/attack/cache.rb +++ b/lib/rack/attack/cache.rb @@ -1,5 +1,5 @@ module Rack - module Attack + class Attack class Cache attr_accessor :prefix diff --git a/lib/rack/attack/check.rb b/lib/rack/attack/check.rb index 79f3cd9..caea75a 100644 --- a/lib/rack/attack/check.rb +++ b/lib/rack/attack/check.rb @@ -1,5 +1,5 @@ module Rack - module Attack + class Attack class Check attr_reader :name, :block, :type def initialize(name, block) diff --git a/lib/rack/attack/fail2ban.rb b/lib/rack/attack/fail2ban.rb index be0cba9..443182d 100644 --- a/lib/rack/attack/fail2ban.rb +++ b/lib/rack/attack/fail2ban.rb @@ -1,5 +1,5 @@ module Rack - module Attack + class Attack class Fail2Ban class << self def filter(discriminator, options) diff --git a/lib/rack/attack/store_proxy.rb b/lib/rack/attack/store_proxy.rb index 6f6a507..4a6d8cc 100644 --- a/lib/rack/attack/store_proxy.rb +++ b/lib/rack/attack/store_proxy.rb @@ -1,7 +1,7 @@ require 'delegate' module Rack - module Attack + class Attack class StoreProxy def self.build(store) # RedisStore#increment needs different behavior, so detect that diff --git a/lib/rack/attack/throttle.rb b/lib/rack/attack/throttle.rb index fa82266..7ad6267 100644 --- a/lib/rack/attack/throttle.rb +++ b/lib/rack/attack/throttle.rb @@ -1,5 +1,5 @@ module Rack - module Attack + class Attack class Throttle MANDATORY_OPTIONS = [:limit, :period] attr_reader :name, :limit, :period, :block diff --git a/lib/rack/attack/track.rb b/lib/rack/attack/track.rb index c9d9152..3e516d7 100644 --- a/lib/rack/attack/track.rb +++ b/lib/rack/attack/track.rb @@ -1,5 +1,5 @@ module Rack - module Attack + class Attack class Track < Check def initialize(name, block) super diff --git a/lib/rack/attack/version.rb b/lib/rack/attack/version.rb index 8dc08be..c70dd50 100644 --- a/lib/rack/attack/version.rb +++ b/lib/rack/attack/version.rb @@ -1,5 +1,5 @@ module Rack - module Attack + class Attack VERSION = '3.0.0' end end diff --git a/lib/rack/attack/whitelist.rb b/lib/rack/attack/whitelist.rb index cd2699b..604268e 100644 --- a/lib/rack/attack/whitelist.rb +++ b/lib/rack/attack/whitelist.rb @@ -1,5 +1,5 @@ module Rack - module Attack + class Attack class Whitelist < Check def initialize(name, block) super From 332dd4ff9ea86bbabec3c145b05e05342dd50028 Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Wed, 2 Apr 2014 17:08:46 +1100 Subject: [PATCH 2/7] Delegate to class methods with forwardable --- lib/rack/attack.rb | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/rack/attack.rb b/lib/rack/attack.rb index 1ac1c6c..64c8b33 100644 --- a/lib/rack/attack.rb +++ b/lib/rack/attack.rb @@ -1,4 +1,5 @@ require 'rack' +require 'forwardable' class Rack::Attack autoload :Cache, 'rack/attack/cache' @@ -89,15 +90,21 @@ class Rack::Attack def call(env) req = Rack::Request.new(env) - if self.class.whitelisted?(req) + if whitelisted?(req) @app.call(env) - elsif self.class.blacklisted?(req) + elsif blacklisted?(req) self.class.blacklisted_response[env] - elsif self.class.throttled?(req) + elsif throttled?(req) self.class.throttled_response[env] else - self.class.tracked?(req) + tracked?(req) @app.call(env) end end + + extend Forwardable + def_delegators self, :whitelisted?, + :blacklisted?, + :throttled?, + :tracked? end From 93421efa5a741fb4793d5c090a7a6563ba332adf Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Wed, 2 Apr 2014 17:12:06 +1100 Subject: [PATCH 3/7] Tidy up defaults. We don't need to use ||= because this runs when the class gets loaded, and we won't have user supplied defaults yet. --- lib/rack/attack.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/rack/attack.rb b/lib/rack/attack.rb index 64c8b33..80a8801 100644 --- a/lib/rack/attack.rb +++ b/lib/rack/attack.rb @@ -76,9 +76,9 @@ class Rack::Attack end # Set defaults - @notifier ||= ActiveSupport::Notifications if defined?(ActiveSupport::Notifications) - @blacklisted_response ||= lambda {|env| [403, {}, ["Forbidden\n"]] } - @throttled_response ||= lambda {|env| + @notifier = ActiveSupport::Notifications if defined?(ActiveSupport::Notifications) + @blacklisted_response = lambda {|env| [403, {}, ["Forbidden\n"]] } + @throttled_response = lambda {|env| retry_after = env['rack.attack.match_data'][:period] rescue nil [429, {'Retry-After' => retry_after.to_s}, ["Retry later\n"]] } From e445bd803af36834a61618ab6b8a8cb1d43412b9 Mon Sep 17 00:00:00 2001 From: Aaron Suggs Date: Thu, 3 Apr 2014 22:38:38 -0400 Subject: [PATCH 4/7] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5ccf9f..8d2c500 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## master (unreleased) * Implement proxy for Dalli with better Memcachier support. (thanks @hakanensari) + * Rack::Attack.new returns an instance to ease testing (thanks @stevehodgkiss) ## v3.0.0 - 15 March 2014 * Change default blacklisted response to 403 Forbidden (thanks @carpodaster). From 16f1cfc5783211b970332a919ce791adec5fd87b Mon Sep 17 00:00:00 2001 From: Tristan Dunn Date: Fri, 4 Apr 2014 14:41:57 -0500 Subject: [PATCH 5/7] Add a custom request class to allow for helper methods. Fixes #58. --- lib/rack/attack.rb | 5 +++-- lib/rack/attack/request.rb | 6 ++++++ spec/rack_attack_request_spec.rb | 19 +++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 lib/rack/attack/request.rb create mode 100644 spec/rack_attack_request_spec.rb diff --git a/lib/rack/attack.rb b/lib/rack/attack.rb index 80a8801..24a74b5 100644 --- a/lib/rack/attack.rb +++ b/lib/rack/attack.rb @@ -10,7 +10,8 @@ class Rack::Attack autoload :Track, 'rack/attack/track' autoload :StoreProxy,'rack/attack/store_proxy' autoload :Fail2Ban, 'rack/attack/fail2ban' - autoload :Allow2Ban, 'rack/attack/allow2ban' + autoload :Allow2Ban, 'rack/attack/allow2ban' + autoload :Request, 'rack/attack/request' class << self @@ -88,7 +89,7 @@ class Rack::Attack end def call(env) - req = Rack::Request.new(env) + req = Rack::Attack::Request.new(env) if whitelisted?(req) @app.call(env) diff --git a/lib/rack/attack/request.rb b/lib/rack/attack/request.rb new file mode 100644 index 0000000..603a126 --- /dev/null +++ b/lib/rack/attack/request.rb @@ -0,0 +1,6 @@ +module Rack + class Attack + class Request < ::Rack::Request + end + end +end diff --git a/spec/rack_attack_request_spec.rb b/spec/rack_attack_request_spec.rb new file mode 100644 index 0000000..ca3eadd --- /dev/null +++ b/spec/rack_attack_request_spec.rb @@ -0,0 +1,19 @@ +require_relative 'spec_helper' + +describe 'Rack::Attack' do + describe 'helpers' do + before do + class Rack::Attack::Request + def remote_ip + ip + end + end + + Rack::Attack.whitelist('valid IP') do |req| + req.remote_ip == "127.0.0.1" + end + end + + allow_ok_requests + end +end From d7f5a31e319c1e5907388a5b9f51d77a6463d457 Mon Sep 17 00:00:00 2001 From: Aaron Suggs Date: Fri, 4 Apr 2014 15:49:08 -0400 Subject: [PATCH 6/7] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d2c500..bb5e7c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## master (unreleased) * Implement proxy for Dalli with better Memcachier support. (thanks @hakanensari) * Rack::Attack.new returns an instance to ease testing (thanks @stevehodgkiss) + * Use Rack::Attack::Request subclass of Rack::Request for easier extending (thanks @tristandunn) ## v3.0.0 - 15 March 2014 * Change default blacklisted response to 403 Forbidden (thanks @carpodaster). From 66231dc5b225d1d934ccb199f84df17625ed140b Mon Sep 17 00:00:00 2001 From: Aaron Suggs Date: Fri, 4 Apr 2014 16:16:31 -0400 Subject: [PATCH 7/7] v3.1.0 --- lib/rack/attack/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rack/attack/version.rb b/lib/rack/attack/version.rb index c70dd50..1246847 100644 --- a/lib/rack/attack/version.rb +++ b/lib/rack/attack/version.rb @@ -1,5 +1,5 @@ module Rack class Attack - VERSION = '3.0.0' + VERSION = '3.1.0' end end