simple-rack-bouncer 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,57 @@
1
+ = Bouncer
2
+
3
+ A simple blacklist based access filtering middleware for Rack.
4
+
5
+ It allows for easy filtering of IP addresses and User Agent strings.
6
+ This filter is primarily aimed at quickly blocking abusive users or bots.
7
+ It is suitable for websites with moderate traffic suffering from a handful of abusive clients and
8
+ will come handy for anyone who doesn't want to fiddle with obscure web server configuration directives.
9
+
10
+ == Usage
11
+
12
+ For instance, to add this middleware to your Rails app:
13
+
14
+ # Require the gem in your Gemfile and then run the bundle command
15
+ gem 'simple-rack-bouncer'
16
+
17
+ # Add the middleware to the stack and configure it
18
+ config.middleware.add Rack::SimpleRackBouncer, :deny_ip_address => ["1.2.3.4", /^10\.0\./], :deny_user_agent => /msnbot/
19
+
20
+ The black list can be either a single or an array with a combination of strings, regular expressions and Proc objects.
21
+ If any of the given conditions are met, the client will be greeted with an HTTP 403 response (access denied).
22
+
23
+
24
+ == Similar Middleware
25
+
26
+ If you are after more advanced features, you may want to have a look at:
27
+
28
+ - {HttpBL}[http://github.com/bpalmen/httpbl/tree/master], an advanced IP filtering middleware
29
+ - {rack-useragent}[http://github.com/bebanjo/rack-useragent/tree/master], another User Agent filter
30
+ - {rack-honeypot}[http://github.com/sunlightlabs/rack-honeypot/tree/master], a spambot trap
31
+
32
+ If you are seriously under attack, you may want to have a look at {ModSecurity}[http://www.modsecurity.org/].
33
+
34
+ == MIT Licence
35
+
36
+ Copyright (c) 2009 Xavier Defrang
37
+
38
+ Permission is hereby granted, free of charge, to any person
39
+ obtaining a copy of this software and associated documentation
40
+ files (the "Software"), to deal in the Software without
41
+ restriction, including without limitation the rights to use,
42
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
43
+ copies of the Software, and to permit persons to whom the
44
+ Software is furnished to do so, subject to the following
45
+ conditions:
46
+
47
+ The above copyright notice and this permission notice shall be
48
+ included in all copies or substantial portions of the Software.
49
+
50
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
51
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
52
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
53
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
54
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
55
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
56
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
57
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,86 @@
1
+ module Rack
2
+ class SimpleRackBouncer
3
+
4
+ # See configure for available options
5
+ def initialize(app, options = {})
6
+ @app = app
7
+ configure(options)
8
+ end
9
+
10
+ # Accepted options are: :deny_user_agents and :deny_ip_address
11
+ # Both accept a single or an array of String, Regexp or Proc objects
12
+ # If a string is given, IP address will be matched by prefix (i.e. '127.0' will match '127.0.0.1' and '127.0.2.1')
13
+ def configure(options = {})
14
+ @user_agent_checks = [options[:deny_user_agent]].flatten.compact
15
+ @ip_address_checks = [options[:deny_ip_address]].flatten.compact
16
+ @redirect_url = [options[:redirect]].flatten.compact
17
+ end
18
+
19
+ def call(env)
20
+ dup._call(env)
21
+ end
22
+
23
+ def _call(env)
24
+ if access_denied?(env)
25
+ deny_access
26
+ else
27
+ @app.call(env)
28
+ end
29
+ end
30
+
31
+ def access_denied?(env)
32
+ ip_denied?(env['REMOTE_ADDR']) || user_agent_denied?(env['HTTP_USER_AGENT'])
33
+ end
34
+
35
+ def ip_denied?(ip)
36
+ ip && @ip_address_checks.any? { |check| match_ip?(check, ip) }
37
+ end
38
+
39
+ def match_ip?(check, ip)
40
+ case check
41
+ when Regexp
42
+ check =~ ip
43
+ when String
44
+ ip[0, check.size] == check
45
+ when Proc
46
+ check.call(ip)
47
+ else
48
+ false
49
+ end
50
+ end
51
+
52
+ def user_agent_denied?(ua)
53
+ ua ||= ''
54
+ @user_agent_checks.any? { |check| match_user_agent?(check, ua) }
55
+ end
56
+
57
+ def match_user_agent?(check, ua)
58
+ case check
59
+ when Regexp
60
+ check =~ ua
61
+ when String
62
+ ua == check
63
+ when Proc
64
+ check.call(ua)
65
+ else
66
+ false
67
+ end
68
+ end
69
+
70
+ # Used for testing
71
+ attr_reader :user_agent_checks
72
+ attr_reader :ip_address_checks
73
+
74
+ protected
75
+
76
+ def deny_access
77
+ return deny_access_via_redirect if @redirect_url
78
+ [403, {"Content-Type" => "text/html"}, ["<h1>403 Forbidden</h1>"]]
79
+ end
80
+
81
+ def deny_access_via_redirect
82
+ [301, {"Location" => @redirect_url}, ["<h1>403 Forbidden</h1>"]]
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1 @@
1
+ require 'rack/simple_rack_bouncer'
@@ -0,0 +1,118 @@
1
+
2
+ require 'test/unit'
3
+ require File.join(File.dirname(__FILE__), '../lib/bouncer.rb')
4
+
5
+ class App
6
+
7
+ def call(env)
8
+ [200, {"Content-Type" => "text/plain"}, "OK"]
9
+ end
10
+
11
+ end
12
+
13
+ class BouncerTest < Test::Unit::TestCase
14
+
15
+ EXAMPLE_CONFIGURATION = {
16
+ :deny_ip_address => ['127.0.0.1', /^10\.\d+\.\d+\.\d+/, lambda { |ip| ip =~ /\.13$/ } ],
17
+ :deny_user_agent => ["", "msnbot", /daodao/, lambda { |ua| ua.length == 3 } ],
18
+ }
19
+
20
+ def setup
21
+ @app = App.new
22
+ @bouncer = SimpleRackBouncer.new(@app)
23
+ end
24
+
25
+ def test_default_options
26
+ default_status_assertions
27
+ assert_nothing_raised { @bouncer.call({}) }
28
+ end
29
+
30
+ def test_configure_with_no_options
31
+ @bouncer.configure
32
+ default_status_assertions
33
+ end
34
+
35
+ def test_configure
36
+ @bouncer.configure(EXAMPLE_CONFIGURATION)
37
+ assert_equal 3, @bouncer.ip_address_checks.size
38
+ assert_equal 4, @bouncer.user_agent_checks.size
39
+ end
40
+
41
+ def test_match_ip
42
+ assert @bouncer.match_ip?("127.0.0.1", "127.0.0.1")
43
+ assert @bouncer.match_ip?("127.0", "127.0.0.1")
44
+ assert @bouncer.match_ip?("127.0", "127.0.2.1")
45
+ assert @bouncer.match_ip?(/^127/, "127.0.0.1")
46
+ assert @bouncer.match_ip?(/^127\.0\.0.\d+/, "127.0.0.1")
47
+ assert @bouncer.match_ip?(lambda { |ip| ip == '127.0.0.1' }, "127.0.0.1")
48
+ assert !@bouncer.match_ip?("127.0.0.1", "127.0.0.2")
49
+ assert !@bouncer.match_ip?("127.0", "127.1.0.0")
50
+ assert !@bouncer.match_ip?(/10\.0/, "127.1.0.0")
51
+ assert !@bouncer.match_ip?(lambda { |ip| false }, "127.0.0.1")
52
+ end
53
+
54
+ def test_match_user_agent
55
+ assert @bouncer.match_user_agent?("daodao larbin@unspecified.email", "daodao larbin@unspecified.email")
56
+ assert !@bouncer.match_user_agent?("daodao", "daodao larbin@unspecified.email")
57
+ assert @bouncer.match_user_agent?(/daodao/, "daodao larbin@unspecified.email")
58
+ assert @bouncer.match_user_agent?(/larbin/, "daodao larbin@unspecified.email")
59
+ end
60
+
61
+ def test_ip_denied
62
+ @bouncer.configure(EXAMPLE_CONFIGURATION)
63
+ assert @bouncer.ip_denied?('127.0.0.1')
64
+ assert @bouncer.ip_denied?('10.2.3.4')
65
+ assert @bouncer.ip_denied?('10.20.30.40')
66
+ assert !@bouncer.ip_denied?('127.0.0.2')
67
+ assert !@bouncer.ip_denied?(nil)
68
+ assert !@bouncer.ip_denied?('')
69
+ end
70
+
71
+ def test_user_agent_denied
72
+ @bouncer.configure(EXAMPLE_CONFIGURATION)
73
+ assert @bouncer.user_agent_denied?('')
74
+ assert @bouncer.user_agent_denied?(nil)
75
+ assert @bouncer.user_agent_denied?('msnbot')
76
+ assert @bouncer.user_agent_denied?('daodao larbin@unspecified.email')
77
+ assert @bouncer.user_agent_denied?('daodao')
78
+ assert @bouncer.user_agent_denied?('123')
79
+ assert !@bouncer.user_agent_denied?('GoogleBot')
80
+ assert !@bouncer.user_agent_denied?('Mozilla/5.0 (Windows; U; Windows NT 5.1; en) AppleWebKit/526.9 (KHTML, like Gecko) Version/4.0dp1 Safari/526.8')
81
+ end
82
+
83
+ def test_allowed_request
84
+ @bouncer.configure(EXAMPLE_CONFIGURATION)
85
+ @env = {
86
+ 'REMOTE_ADDR' => '1.2.3.4',
87
+ 'PATH_INFO' => '/path',
88
+ 'HTTP_USER_AGENT' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en) AppleWebKit/526.9 (KHTML, like Gecko) Version/4.0dp1 Safari/526.8'
89
+ }
90
+ assert_equal 200, @bouncer.call(@env).first
91
+ end
92
+
93
+ def test_denied_request
94
+ @bouncer.configure(EXAMPLE_CONFIGURATION)
95
+ @env = {
96
+ 'REMOTE_ADDR' => '1.2.3.4',
97
+ 'PATH_INFO' => '/path',
98
+ 'HTTP_USER_AGENT' => 'msnbot'
99
+ }
100
+ assert_equal 403, @bouncer.call(@env).first
101
+ end
102
+
103
+ def test_configuration_with_single_items
104
+ @bouncer.configure(:deny_ip_address => '127.0.0.1', :deny_user_agent => /msnbot/)
105
+ assert @bouncer.ip_denied?('127.0.0.1')
106
+ assert !@bouncer.ip_denied?('10.0.0.1')
107
+ assert @bouncer.user_agent_denied?('msnbot')
108
+ assert !@bouncer.user_agent_denied?('Safari')
109
+ end
110
+
111
+ protected
112
+
113
+ def default_status_assertions
114
+ assert @bouncer.ip_address_checks.empty?
115
+ assert @bouncer.user_agent_checks.empty?
116
+ end
117
+
118
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simple-rack-bouncer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.6
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ben Damman
9
+ - Xavier Defrang
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-04-14 00:00:00.000000000 Z
14
+ dependencies: []
15
+ description: Simple rack middleware for access filtering by IP or User Agent string
16
+ email: ben.damman@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files:
20
+ - README.rdoc
21
+ files:
22
+ - README.rdoc
23
+ - lib/simple-rack-bouncer.rb
24
+ - lib/rack/simple_rack_bouncer.rb
25
+ - test/bouncer_test.rb
26
+ homepage: http://github.com/typesend/simple-rack-bouncer
27
+ licenses: []
28
+ post_install_message:
29
+ rdoc_options:
30
+ - --main
31
+ - README.rdoc
32
+ - --inline-source
33
+ - --charset=UTF-8
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ! '>='
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ required_rubygems_version: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ requirements: []
49
+ rubyforge_project:
50
+ rubygems_version: 1.8.22
51
+ signing_key:
52
+ specification_version: 3
53
+ summary: Simple rack middleware for access filtering by IP address or User Agent string
54
+ test_files:
55
+ - test/bouncer_test.rb