bot-away 1.2.0 → 2.0.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.
- data/.travis.yml +14 -0
- data/History.txt +20 -0
- data/README.md +198 -0
- data/Rakefile +14 -94
- data/bot-away.gemspec +20 -87
- data/gemfiles/Gemfile.rails-3.0.x +8 -0
- data/gemfiles/Gemfile.rails-3.0.x.lock +121 -0
- data/gemfiles/Gemfile.rails-3.1.x +8 -0
- data/gemfiles/Gemfile.rails-3.1.x.lock +133 -0
- data/lib/bot-away.rb +15 -13
- data/lib/bot-away/action_dispatch/params_parser.rb +22 -0
- data/lib/bot-away/action_view/helpers/instance_tag.rb +36 -12
- data/lib/bot-away/param_parser.rb +2 -2
- data/lib/bot-away/railtie.rb +10 -0
- data/lib/bot-away/test_case.rb +58 -0
- data/lib/bot-away/test_case/controller_test_case.rb +11 -0
- data/lib/bot-away/test_case/instance_tag_test_case.rb +15 -0
- data/lib/bot-away/test_case/matchers.rb +20 -0
- data/lib/bot-away/test_case/matchers/honeypot_matcher.rb +30 -0
- data/lib/bot-away/test_case/matchers/obfuscation_matcher.rb +30 -0
- data/lib/bot-away/test_case/mock_object.rb +16 -0
- data/lib/bot-away/version.rb +12 -0
- data/lib/locale/honeypots.yml +6 -0
- data/spec/controllers/basic_form_view_spec.rb +112 -0
- data/spec/controllers/{test_controller_spec.rb → tests_controller_spec.rb} +29 -80
- data/spec/integration/params_post_spec.rb +42 -0
- data/spec/lib/action_view/helpers/instance_tag_spec.rb +94 -0
- data/spec/{views/lib → lib/action_view}/param_parser_spec.rb +10 -10
- data/spec/spec_helper.rb +37 -105
- data/spec/test_rails_app/app/controllers/tests_controller.rb +11 -0
- data/spec/test_rails_app/app/models/post.rb +13 -0
- data/spec/test_rails_app/app/views/tests/basic_form.html.erb +5 -0
- data/spec/test_rails_app/app/views/tests/model_form.html.erb +12 -0
- data/spec/test_rails_app/config/locales/bot-away-overrides.yml +6 -0
- data/spec/views/form_builder_spec.rb +118 -0
- metadata +94 -137
- data/Manifest.txt +0 -23
- data/README.rdoc +0 -179
- data/VERSION +0 -1
- data/lib/bot-away/action_dispatch/request.rb +0 -20
- data/spec/rspec_version.rb +0 -19
- data/spec/support/honeypot_matcher.rb +0 -30
- data/spec/support/obfuscation_helper.rb +0 -123
- data/spec/support/obfuscation_matcher.rb +0 -28
- data/spec/support/rails/mock_logger.rb +0 -21
- data/spec/support/test_controller.rb +0 -28
- data/spec/support/views/test/index.html.erb +0 -4
- data/spec/support/views/test/model_form.html.erb +0 -6
- data/spec/views/lib/action_view/helpers/instance_tag_spec.rb +0 -75
- data/spec/views/lib/disabled_for_spec.rb +0 -101
- data/spec/views/lib/form_builder_spec.rb +0 -56
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "post data with params" do
|
4
|
+
before do
|
5
|
+
visit '/tests/model_form'
|
6
|
+
# puts page.body
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "filling in a honeypot" do
|
10
|
+
before do
|
11
|
+
fill_in 'post[subject]', :with => "this is a subject"
|
12
|
+
click_button 'submit'
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should be considered a bot" do
|
16
|
+
page.should have_content('suspected_bot')
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should not include legit params" do
|
20
|
+
page.should_not have_content("subject:")
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should drop data from the honeypots" do
|
24
|
+
page.should_not have_content("this is a subject")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "filling in a legit field" do
|
29
|
+
before do
|
30
|
+
fill_in '00a1168ac1379bdbe9b59e678fe486b1', :with => "this is a subject"
|
31
|
+
click_button 'submit'
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should have kept legit data" do
|
35
|
+
page.should have_content('this is a subject')
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should not be considered a bot" do
|
39
|
+
page.should_not have_content('suspected_bot')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ActionView::Helpers::InstanceTag do
|
4
|
+
include BotAway::TestCase::InstanceTagTestCase
|
5
|
+
|
6
|
+
subject { default_instance_tag }
|
7
|
+
|
8
|
+
context "honeypot" do
|
9
|
+
subject { default_instance_tag.honeypot_tag(:input, :type => :text, :name => 'name') }
|
10
|
+
it { should be_html_safe }
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should have bdo in the instance tag" do
|
14
|
+
subject.tag(:input, :type => :text).to_s.should =~ /<bdo/
|
15
|
+
end
|
16
|
+
|
17
|
+
context "honeypot warning text" do
|
18
|
+
before { default_instance_tag.honeypot_index = 1 } # always produce the same honeypot
|
19
|
+
subject { default_instance_tag.honeypot_warning_tag }
|
20
|
+
|
21
|
+
it "should obfuscate honeypot warning text" do
|
22
|
+
# 'Leave this empty: ' reversed and then converted to HTML escaped unicode:
|
23
|
+
subject.should =~ /#{Regexp::escape ' :ytpme siht evaeL'}/
|
24
|
+
end
|
25
|
+
|
26
|
+
context "with honeypot warning obfuscation disabled" do
|
27
|
+
before { BotAway.obfuscate_honeypot_warning_messages = false }
|
28
|
+
|
29
|
+
it "should not obfuscate honeypot warning text" do
|
30
|
+
subject.should =~ /Leave this empty: /
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should use bdo to display honeypot warning text accurately" do
|
35
|
+
subject.should match(/<bdo dir=['"]rtl["']>.*?<\/bdo>/)
|
36
|
+
end
|
37
|
+
|
38
|
+
context "I18n" do
|
39
|
+
it "should allow overridden honeypot warning" do
|
40
|
+
default_instance_tag.honeypot_index = 2
|
41
|
+
CGI.unescapeHTML(default_instance_tag.honeypot_warning_message).reverse.should == "Overridden honeypot warning"
|
42
|
+
end
|
43
|
+
|
44
|
+
it "shold allow additional honeypot warnings" do
|
45
|
+
I18n.t('bot_away.number_of_honeypot_warning_messages').to_i.should == 4
|
46
|
+
|
47
|
+
default_instance_tag.honeypot_index = 4
|
48
|
+
CGI.unescapeHTML(default_instance_tag.honeypot_warning_message).reverse.should == "Additional honeypot warning"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "with a valid text area tag" do
|
54
|
+
subject do
|
55
|
+
dump { default_instance_tag.to_text_area_tag }
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should produce blank honeypot value" do
|
59
|
+
subject.should_not =~ /name="object_name\[method_name\]"[^>]+>method_value<\/textarea>/
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context "with a valid input type=text tag" do
|
64
|
+
before(:each) { @tag_options = ["input", {:type => 'text', 'name' => 'object_name[method_name]', 'id' => 'object_name_method_name', 'value' => 'method_value'}] }
|
65
|
+
|
66
|
+
it "should turn off autocomplete for honeypots" do
|
67
|
+
subject.honeypot_tag(*@tag_options).should =~ /autocomplete="off"/
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should obfuscate tag name" do
|
71
|
+
subject.obfuscated_tag(*@tag_options).should =~ /name="#{obfuscated_name}"/
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should obfuscate tag id" do
|
75
|
+
subject.obfuscated_tag(*@tag_options).should =~ /id="#{obfuscated_id}"/
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should include unobfuscated tag value" do
|
79
|
+
subject.obfuscated_tag(*@tag_options).should =~ /value="method_value"/
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should create honeypot name" do
|
83
|
+
subject.honeypot_tag(*@tag_options).should =~ /name="object_name\[method_name\]"/
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should create honeypot id" do
|
87
|
+
subject.honeypot_tag(*@tag_options).should =~ /id="object_name_method_name"/
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should create empty honeypot tag value" do
|
91
|
+
subject.honeypot_tag(*@tag_options).should =~ /value=""/
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -8,23 +8,23 @@ describe BotAway::ParamParser do
|
|
8
8
|
}.merge(honeypots).with_indifferent_access
|
9
9
|
end
|
10
10
|
|
11
|
-
before
|
11
|
+
before do
|
12
12
|
# Root level is encoded with 208.77.188.166/test/1234
|
13
13
|
# which resolves to a spinner digest of 86ba3fd99e851587a849ad9ed9817f9b
|
14
14
|
@ip = '208.77.188.166'
|
15
|
-
|
15
|
+
params('test' => { 'name' => '', 'posts' => [] })
|
16
16
|
end
|
17
17
|
|
18
|
-
subject {
|
18
|
+
subject { BotAway::ParamParser.new(@ip, @params) }
|
19
19
|
|
20
20
|
it "should default BotAway.dump_params => false" do
|
21
21
|
(!!BotAway.dump_params).should == false
|
22
22
|
end
|
23
23
|
|
24
24
|
context "with dump_params == true" do
|
25
|
-
before
|
26
|
-
after
|
27
|
-
|
25
|
+
before { BotAway.dump_params = true }
|
26
|
+
after { BotAway.dump_params = false }
|
27
|
+
|
28
28
|
it "should dump params as debug to Rails logger" do
|
29
29
|
@params = { 'test' => "hello", :posts => [1] }
|
30
30
|
Rails.logger.should_receive(:debug).exactly(3).times #with(@params.inspect)
|
@@ -53,8 +53,8 @@ describe BotAway::ParamParser do
|
|
53
53
|
end
|
54
54
|
|
55
55
|
context "with a filled honeypot" do
|
56
|
-
before
|
57
|
-
subject {
|
56
|
+
before { params({'test' => {'name' => 'colin', 'posts' => []}}) }
|
57
|
+
subject { BotAway::ParamParser.new(@ip, @params) }
|
58
58
|
|
59
59
|
it "drops all parameters" do
|
60
60
|
subject.params.should == { "suspected_bot" => true }
|
@@ -62,8 +62,8 @@ describe BotAway::ParamParser do
|
|
62
62
|
end
|
63
63
|
|
64
64
|
context "with a filled sub-honeypot" do
|
65
|
-
before
|
66
|
-
subject {
|
65
|
+
before { params({'test' => {'name' => '', 'posts' => [1, 2]}}) }
|
66
|
+
subject { BotAway::ParamParser.new(@ip, @params) }
|
67
67
|
|
68
68
|
it "drops all parameters" do
|
69
69
|
subject.params.should == { "suspected_bot" => true }
|
data/spec/spec_helper.rb
CHANGED
@@ -1,113 +1,45 @@
|
|
1
|
-
ENV['
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
module Helpers
|
25
|
-
module CsrfHelper
|
26
|
-
# Returns a meta tag with the cross-site request forgery protection token
|
27
|
-
# for forms to use. Place this in your head.
|
28
|
-
def csrf_meta_tag
|
29
|
-
if protect_against_forgery?
|
30
|
-
%(<meta name="csrf-param" content="#{Rack::Utils.escape_html(request_forgery_protection_token)}"/>\n<meta name="csrf-token" content="1234"/>).html_safe
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
class BotAwayApp < Rails::Application
|
38
|
-
config.session_store :cookie_store, :key => "_myapp_session", :secret => "12345"*6
|
39
|
-
paths.app.views = File.expand_path(File.join(File.dirname(__FILE__), "support/views"))
|
40
|
-
config.active_support.deprecation = :stderr
|
41
|
-
end
|
42
|
-
BotAwayApp.initialize!
|
43
|
-
BotAwayApp.routes.draw do
|
44
|
-
match '/:controller/:action'
|
1
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../gemfiles/Gemfile.rails-3.1.x", File.dirname(__FILE__))
|
2
|
+
require 'bundler'
|
3
|
+
Bundler.setup
|
4
|
+
|
5
|
+
ENV['RAILS_ENV'] = 'development'
|
6
|
+
|
7
|
+
require 'rails'
|
8
|
+
require 'active_support/secure_random'
|
9
|
+
require 'action_controller/railtie'
|
10
|
+
require 'action_mailer/railtie'
|
11
|
+
require 'active_resource/railtie'
|
12
|
+
require 'bot-away'
|
13
|
+
|
14
|
+
class BotAway::TestRailsApp < Rails::Application
|
15
|
+
base = File.expand_path("test_rails_app", File.dirname(__FILE__))
|
16
|
+
config.secret_token = "some secret phrase of at least 30 characters" * 30
|
17
|
+
config.active_support.deprecation = :log
|
18
|
+
config.paths['app/controllers'] = File.join(base, 'app/controllers')
|
19
|
+
config.paths['app/views'] = File.join(base, 'app/views')
|
20
|
+
config.paths['config/locales'] = File.join(base, 'config/locales/bot-away-overrides.yml')
|
21
|
+
if Rails::VERSION::MINOR == 0 # rails 3.0.x
|
22
|
+
config.paths.app.views = File.join(base, 'app/views')
|
23
|
+
config.paths.config.locales = File.join(base, 'config/locales/bot-away-overrides.yml')
|
45
24
|
end
|
46
|
-
|
47
|
-
RAILS_VERSION = "3.0"
|
48
25
|
end
|
49
26
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
attr_accessor :method_name
|
55
|
-
|
56
|
-
def id
|
57
|
-
1
|
58
|
-
end
|
59
|
-
|
60
|
-
# for testing grouped_collection_select
|
61
|
-
def object_name
|
62
|
-
[self]
|
63
|
-
end
|
64
|
-
|
65
|
-
def initialize
|
66
|
-
@method_name = 'method_value'
|
67
|
-
end
|
68
|
-
end
|
27
|
+
BotAway::TestRailsApp.initialize!
|
28
|
+
Rails.application.routes.draw { match '/:controller/:action' }
|
29
|
+
Rails.application.routes.finalize!
|
30
|
+
Dir[File.expand_path('test_rails_app/**/*.rb', File.dirname(__FILE__))].each { |f| require f }
|
69
31
|
|
32
|
+
require 'rspec/rails'
|
33
|
+
require 'capybara/rails'
|
70
34
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
35
|
+
RSpec.configure do |config|
|
36
|
+
config.before do
|
37
|
+
# for the CSRF token, which BA uses as a seed
|
38
|
+
SecureRandom.stub!(:base64).and_return("1234")
|
39
|
+
# reset any config changes
|
40
|
+
BotAway.reset!
|
41
|
+
enable_forgery_protection
|
76
42
|
end
|
77
|
-
|
78
|
-
if defined?(ActionController::TestProcess)
|
79
|
-
# note that this only matters for TEST requests; real ones use the params parser.
|
80
|
-
module ActionController::TestProcess
|
81
|
-
def process_with_deobfuscation(action, parameters = nil, session = nil, flash = nil, http_method = 'GET')
|
82
|
-
process_without_deobfuscation(action, parameters, session, flash, http_method)
|
83
|
-
@request.parameters.replace(BotAway::ParamParser.new(@request.ip, @request.params).params)
|
84
|
-
end
|
85
|
-
|
86
|
-
alias_method_chain :process, :deobfuscation
|
87
|
-
end
|
88
|
-
end
|
89
|
-
else
|
90
|
-
require 'rspec/rails'
|
91
|
-
|
92
|
-
# note that this only matters for TEST requests; real ones use the params parser.
|
93
|
-
module ActionController::TestCase::Behavior
|
94
|
-
def process_with_deobfuscation(action, parameters = nil, session = nil, flash = nil, http_method = 'GET')
|
95
|
-
process_without_deobfuscation(action, parameters, session, flash, http_method)
|
96
|
-
request.parameters.replace(BotAway::ParamParser.new(request.ip, request.params).params)
|
97
|
-
end
|
98
|
-
|
99
|
-
alias_method_chain :process, :deobfuscation
|
100
|
-
end
|
101
|
-
|
102
|
-
RSpec.configure do |config|
|
103
|
-
config.before(:each) do
|
104
|
-
BotAway.reset!
|
105
|
-
# BotAway doesn't work without forgery protection, and RSpec-Rails 2 disables it. Lost way too many hours on this.
|
106
|
-
ActionController::Base.allow_forgery_protection = true
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
43
|
|
111
|
-
|
112
|
-
require fi
|
44
|
+
config.include BotAway::TestCase
|
113
45
|
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ActionView::Helpers::FormBuilder do
|
4
|
+
include BotAway::TestCase::Matchers
|
5
|
+
|
6
|
+
it "should not create honeypots with default values" do
|
7
|
+
builder.text_field(method_name).should match(/name="object_name\[method_name\]"[^>]*?value=""/)
|
8
|
+
end
|
9
|
+
|
10
|
+
context "with BotAway.show_honeypots == true" do
|
11
|
+
before { BotAway.show_honeypots = true }
|
12
|
+
after { BotAway.show_honeypots = false }
|
13
|
+
|
14
|
+
it "should not disguise honeypots" do
|
15
|
+
builder.text_area(method_name).should_not match(/<\/div>/)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should not obfuscate names that have been explicitly ignored" do
|
20
|
+
BotAway.accepts_unfiltered_params 'method_name'
|
21
|
+
builder.text_field('method_name').should_not match(/name="#{obfuscated_name}/)
|
22
|
+
BotAway.unfiltered_params.delete 'method_name'
|
23
|
+
end
|
24
|
+
|
25
|
+
shared_examples_for "an obfuscated tag" do
|
26
|
+
it { should be_obfuscated }
|
27
|
+
it { should include_honeypot }
|
28
|
+
end
|
29
|
+
|
30
|
+
shared_examples_for "an obfuscated select tag" do
|
31
|
+
it_should_behave_like "an obfuscated tag"
|
32
|
+
|
33
|
+
it "should fill honeypots with html-safe options" do
|
34
|
+
subject.to_s.should match(/<select[^>]*><option[^>]*><\/option>/)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# select(method, options)
|
39
|
+
context '#select' do
|
40
|
+
context "with options" do
|
41
|
+
subject { builder.select(method_name, {1 => :a, 2 => :b}) }
|
42
|
+
it_should_behave_like "an obfuscated select tag"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
|
47
|
+
context '#collection_select' do
|
48
|
+
subject { builder.collection_select method_name, [mock_object], :method_name, :method_name }
|
49
|
+
it_should_behave_like "an obfuscated select tag"
|
50
|
+
end
|
51
|
+
|
52
|
+
# grouped_collection_select(method, collection, group_method, group_label_method, option_key_method,
|
53
|
+
# option_value_method, options = {}, html_options = {})
|
54
|
+
context '#grouped_collection_select' do
|
55
|
+
subject { builder.grouped_collection_select method_name, [mock_object], object_name, method_name, method_name, :to_s }
|
56
|
+
it_should_behave_like "an obfuscated select tag"
|
57
|
+
end
|
58
|
+
|
59
|
+
# time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
|
60
|
+
context '#time_zone_select' do
|
61
|
+
subject { builder.time_zone_select method_name }
|
62
|
+
it_should_behave_like "an obfuscated tag"
|
63
|
+
end
|
64
|
+
|
65
|
+
context '#hidden_field' do
|
66
|
+
subject { builder.hidden_field method_name }
|
67
|
+
it_should_behave_like "an obfuscated tag"
|
68
|
+
end
|
69
|
+
|
70
|
+
context '#text_field' do
|
71
|
+
subject { builder.text_field method_name }
|
72
|
+
it_should_behave_like "an obfuscated tag"
|
73
|
+
end
|
74
|
+
|
75
|
+
context '#text_area' do
|
76
|
+
subject { builder.text_area method_name }
|
77
|
+
it_should_behave_like "an obfuscated tag"
|
78
|
+
end
|
79
|
+
|
80
|
+
context '#file_filed' do
|
81
|
+
subject { builder.file_field method_name }
|
82
|
+
it_should_behave_like "an obfuscated tag"
|
83
|
+
end
|
84
|
+
|
85
|
+
context '#password_field' do
|
86
|
+
subject { builder.password_field method_name }
|
87
|
+
it_should_behave_like "an obfuscated tag"
|
88
|
+
end
|
89
|
+
|
90
|
+
context '#check_box' do
|
91
|
+
subject { builder.check_box method_name }
|
92
|
+
it_should_behave_like "an obfuscated tag"
|
93
|
+
end
|
94
|
+
|
95
|
+
context '#radio_button' do
|
96
|
+
subject { builder.radio_button method_name, :value }
|
97
|
+
it { should be_obfuscated_as obfuscated_name, '767c870add970ab6d64803043c4ccfbb' }
|
98
|
+
it { should include_honeypot_called tag_name, tag_id+'_value' }
|
99
|
+
end
|
100
|
+
|
101
|
+
context '#label' do
|
102
|
+
subject { builder.label method_name, :for => tag_id }
|
103
|
+
|
104
|
+
it "links labels to their obfuscated elements" do
|
105
|
+
subject.should match(/for=["']#{obfuscated_id}['"]/)
|
106
|
+
end
|
107
|
+
|
108
|
+
# TODO ideas for future implementation, but they may break nested tags
|
109
|
+
# it "obfuscates label text using bdo dir" do
|
110
|
+
# subject.should match(/<bdo dir=['"]rtl["']/)
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
# it "uses reversed unicode entities for label text" do
|
114
|
+
# # method_name => reversed(eman_dohtem)
|
115
|
+
# subject.should match("eman_dohtem")
|
116
|
+
# end
|
117
|
+
end
|
118
|
+
end
|