rack-honeypot 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'http://rubygems.org/'
2
+
3
+ gem 'rake'
4
+ gem 'unindentable', '0.0.4'
5
+ gem 'rack'
6
+ gem 'mocha'
7
+ gem 'rack-test'
data/LICENSE.md ADDED
@@ -0,0 +1,10 @@
1
+ Copyright (c) 2009, Sunlight Foundation
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
+
6
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+ * Neither the name of Sunlight Foundation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
9
+
10
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # Honeypot, a Rack Middleware for trapping spambots
2
+
3
+ Written by Luigi Montanez of the Sunlight Labs, with contributions from Luc Castera. Copyright 2009.
4
+
5
+ This middleware acts as a spam trap. It inserts, into every outputted `<form>`, a text field that a spambot will really want to fill in, but is actually not used by the app. The field is hidden to humans via CSS, and includes a warning label for screenreading software.
6
+
7
+ In the `<body>`:
8
+
9
+ <form>
10
+ <div class='phonetoy'>
11
+ <label for='email'>Don't fill in this field</label>
12
+ <input type='text' name='email' value=''/>
13
+ </div>
14
+ [...]
15
+
16
+ In the `<head>`:
17
+
18
+ <style type='text/css' media='all'>
19
+ div.phonetoy {
20
+ display:none;
21
+ }
22
+ </style>
23
+
24
+ Then, for incoming requests, the middleware will check if the text field has been set to an unexpected value. If it has, that means a spambot has altered the field, and the spambot is booted to a dead end blank page.
25
+
26
+ ## Dependencies
27
+
28
+ You will need to install these RubyGems:
29
+
30
+ * unindentable: http://github.com/sunlightlabs/ruby-unindentable/tree/master
31
+ * rack-test: http://github.com/brynary/rack-test/tree/master
32
+
33
+ ## Configuration
34
+
35
+ To use in your Rails app, place `honeypot.rb` in `lib/rack`.
36
+
37
+ Then in `environment.rb`:
38
+
39
+ config.middleware.use "Rack::Honeypot"
40
+
41
+ That's all there is to it. Fire up your app, View Source on a page with a form, and see the magic.
42
+
43
+ There are a few options you can pass in:
44
+
45
+ * `:class_name` is the class assigned to the parent div of the honeypot. Defaults to "phonetoy", an anagram of honeypot.
46
+ * `:label` is the warning label displayed to those with CSS disabled. Defaults to "Don't fill in this field".
47
+ * `:input_name` is the name of the form field. Ensure that this is tempting to a spambot if you modify it. Defaults to "email".
48
+ * `:input_value` is the value of the form field that would only be modified by a spambot. Defaults to blank.
49
+
50
+ If you want to modify the options used, simply do:
51
+
52
+ config.middleware.use "Rack::Honeypot", :input_name => "firstname"
53
+
54
+
55
+ ## Tests
56
+
57
+ To run the tests:
58
+
59
+ sudo gem install rack-test
60
+ cd test
61
+ ruby test_honeypot.rb
62
+
63
+
64
+ ## Props
65
+
66
+ Based on [django-honeypot](http://github.com/sunlightlabs/django-honeypot) by James Turk.
67
+
68
+ Credit to Geoff Buesing for a first stab at this [idea in Rack](http://mad.ly/2009/05/01/honeypot-filter-as-a-rack-middleware/).
69
+
70
+ See LICENSE.md for proper reuse guidelines.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |task|
5
+ task.test_files = FileList['test/test_*.rb']
6
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,68 @@
1
+ require 'unindentable'
2
+
3
+ module Rack
4
+ class Honeypot
5
+ include Unindentable
6
+
7
+ def initialize(app, options={})
8
+ @app = app
9
+ @class_name = options[:class_name] || "phonetoy"
10
+ @label = options[:label] || "Don't fill in this field"
11
+ @input_name = options[:input_name] || "email"
12
+ @input_value = options[:input_value] || ""
13
+ @logger = options[:logger]
14
+ end
15
+
16
+ def call(env)
17
+ if spambot_submission?(Rack::Request.new(env).params)
18
+ @logger.warn("[Rack::Honeypot] Spam bot detected; responded with null") unless @logger.nil?
19
+ send_to_dead_end
20
+ else
21
+ status, headers, response = @app.call(env)
22
+ new_body = insert_honeypot(build_response_body(response))
23
+ new_headers = recalculate_body_length(headers, new_body)
24
+ [status, new_headers, new_body]
25
+ end
26
+ end
27
+
28
+ def spambot_submission?(form_hash)
29
+ form_hash && form_hash[@input_name] && form_hash[@input_name] != @input_value
30
+ end
31
+
32
+ def send_to_dead_end
33
+ [200, {'Content-Type' => 'text/html', "Content-Length" => "0"}, []]
34
+ end
35
+
36
+ def build_response_body(response)
37
+ response_body = ""
38
+ response.each { |part| response_body += part }
39
+ response_body
40
+ end
41
+
42
+ def recalculate_body_length(headers, body)
43
+ new_headers = headers
44
+ new_headers["Content-Length"] = body.length.to_s
45
+ new_headers
46
+ end
47
+
48
+ def insert_honeypot(body)
49
+ css = unindent <<-BLOCK
50
+ <style type='text/css' media='all'>
51
+ div.#{@class_name} {
52
+ display:none;
53
+ }
54
+ </style>
55
+ BLOCK
56
+ div = unindent <<-BLOCK
57
+ <div class='#{@class_name}'>
58
+ <label for='#{@input_name}'>#{@label}</label>
59
+ <input type='text' name='#{@input_name}' value='#{@input_value}'/>
60
+ </div>
61
+ BLOCK
62
+ body.gsub!(/<\/head>/, css + "\n</head>")
63
+ body.gsub!(/<form(.*)>/, '<form\1>' + "\n" + div)
64
+ body
65
+ end
66
+
67
+ end
68
+ end
@@ -0,0 +1,87 @@
1
+ require 'rack/test'
2
+ require 'test/unit'
3
+ require 'mocha'
4
+ require 'unindentable'
5
+
6
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/rack/honeypot')
7
+
8
+ # To run this test, you need to have rack-test gem installed: sudo gem install rack-test
9
+
10
+ class HoneypotTest < Test::Unit::TestCase
11
+ include Rack::Test::Methods
12
+ include Unindentable
13
+
14
+ def setup
15
+ @logger = stub("logger", :warn => nil)
16
+ end
17
+
18
+ def app
19
+ content = unindent <<-BLOCK
20
+ <html>
21
+ <head>
22
+ </head>
23
+ <body>
24
+ <form></form>
25
+ Hello World!
26
+ </body>
27
+ </html>
28
+ BLOCK
29
+
30
+ hello_world_app = lambda do |env|
31
+ [
32
+ 200,
33
+ {
34
+ 'Content-Type' => 'text/plain',
35
+ 'Content-Length' => content.length.to_s
36
+ },
37
+ [content]
38
+ ]
39
+ end
40
+
41
+ Rack::Honeypot.new(hello_world_app, :input_name => 'honeypot_email', :logger => @logger)
42
+ end
43
+
44
+ def test_normal_request_should_go_through
45
+ get '/'
46
+ assert_equal 200, last_response.status
47
+ assert_not_equal '', last_response.body
48
+ end
49
+
50
+ def test_request_with_form_should_add_honeypot_css
51
+ get '/'
52
+ assert_equal 200, last_response.status
53
+ css = unindent <<-BLOCK
54
+ <style type='text/css' media='all'>
55
+ div.phonetoy {
56
+ display:none;
57
+ }
58
+ </style>
59
+ BLOCK
60
+ assert last_response.body.index(css)
61
+ end
62
+
63
+ def test_request_with_form_should_add_honeypot_div
64
+ get '/'
65
+ assert_equal 200, last_response.status
66
+
67
+ div = unindent <<-BLOCK
68
+ <div class='phonetoy'>
69
+ <label for='honeypot_email'>Don't fill in this field</label>
70
+ <input type='text' name='honeypot_email' value=''/>
71
+ </div>
72
+ BLOCK
73
+ assert last_response.body.index(div)
74
+ end
75
+
76
+ def test_spam_request_should_be_sent_to_dead_end
77
+ post '/', :honeypot_email => 'joe@example.com'
78
+ assert_equal 200, last_response.status
79
+ assert_equal '', last_response.body
80
+ end
81
+
82
+ def test_spam_request_should_be_logged
83
+ @logger.expects(:warn).with("[Rack::Honeypot] Spam bot detected; responded with null")
84
+ post '/', :honeypot_email => 'joe@example.com'
85
+ end
86
+
87
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-honeypot
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Luigi Montanez
14
+ - Luc Castera
15
+ - Daniel Schierbeck
16
+ autorequire:
17
+ bindir: bin
18
+ cert_chain: []
19
+
20
+ date: 2011-10-05 00:00:00 Z
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: rake
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ hash: 3
31
+ segments:
32
+ - 0
33
+ version: "0"
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: unindentable
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - "="
43
+ - !ruby/object:Gem::Version
44
+ hash: 23
45
+ segments:
46
+ - 0
47
+ - 0
48
+ - 4
49
+ version: 0.0.4
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: rack
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ hash: 3
61
+ segments:
62
+ - 0
63
+ version: "0"
64
+ type: :runtime
65
+ version_requirements: *id003
66
+ - !ruby/object:Gem::Dependency
67
+ name: rack-test
68
+ prerelease: false
69
+ requirement: &id004 !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ hash: 3
75
+ segments:
76
+ - 0
77
+ version: "0"
78
+ type: :runtime
79
+ version_requirements: *id004
80
+ description: This middleware acts as a spam trap. It inserts, into every outputted <form>, a text field that a spambot will really want to fill in, but is actually not used by the app. The field is hidden to humans via CSS, and includes a warning label for screenreading software.
81
+ email: luigi.montanez@gmail.com
82
+ executables: []
83
+
84
+ extensions: []
85
+
86
+ extra_rdoc_files:
87
+ - LICENSE.md
88
+ - README.md
89
+ files:
90
+ - Gemfile
91
+ - LICENSE.md
92
+ - README.md
93
+ - Rakefile
94
+ - VERSION
95
+ - lib/rack/honeypot.rb
96
+ - test/test_honeypot.rb
97
+ homepage: http://github.com/sunlightlabs/rack-honeypot
98
+ licenses: []
99
+
100
+ post_install_message:
101
+ rdoc_options: []
102
+
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ hash: 3
111
+ segments:
112
+ - 0
113
+ version: "0"
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ none: false
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ hash: 3
120
+ segments:
121
+ - 0
122
+ version: "0"
123
+ requirements: []
124
+
125
+ rubyforge_project:
126
+ rubygems_version: 1.8.6
127
+ signing_key:
128
+ specification_version: 3
129
+ summary: Middleware that functions as a spambot trap.
130
+ test_files: []
131
+