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.
Files changed (51) hide show
  1. data/.travis.yml +14 -0
  2. data/History.txt +20 -0
  3. data/README.md +198 -0
  4. data/Rakefile +14 -94
  5. data/bot-away.gemspec +20 -87
  6. data/gemfiles/Gemfile.rails-3.0.x +8 -0
  7. data/gemfiles/Gemfile.rails-3.0.x.lock +121 -0
  8. data/gemfiles/Gemfile.rails-3.1.x +8 -0
  9. data/gemfiles/Gemfile.rails-3.1.x.lock +133 -0
  10. data/lib/bot-away.rb +15 -13
  11. data/lib/bot-away/action_dispatch/params_parser.rb +22 -0
  12. data/lib/bot-away/action_view/helpers/instance_tag.rb +36 -12
  13. data/lib/bot-away/param_parser.rb +2 -2
  14. data/lib/bot-away/railtie.rb +10 -0
  15. data/lib/bot-away/test_case.rb +58 -0
  16. data/lib/bot-away/test_case/controller_test_case.rb +11 -0
  17. data/lib/bot-away/test_case/instance_tag_test_case.rb +15 -0
  18. data/lib/bot-away/test_case/matchers.rb +20 -0
  19. data/lib/bot-away/test_case/matchers/honeypot_matcher.rb +30 -0
  20. data/lib/bot-away/test_case/matchers/obfuscation_matcher.rb +30 -0
  21. data/lib/bot-away/test_case/mock_object.rb +16 -0
  22. data/lib/bot-away/version.rb +12 -0
  23. data/lib/locale/honeypots.yml +6 -0
  24. data/spec/controllers/basic_form_view_spec.rb +112 -0
  25. data/spec/controllers/{test_controller_spec.rb → tests_controller_spec.rb} +29 -80
  26. data/spec/integration/params_post_spec.rb +42 -0
  27. data/spec/lib/action_view/helpers/instance_tag_spec.rb +94 -0
  28. data/spec/{views/lib → lib/action_view}/param_parser_spec.rb +10 -10
  29. data/spec/spec_helper.rb +37 -105
  30. data/spec/test_rails_app/app/controllers/tests_controller.rb +11 -0
  31. data/spec/test_rails_app/app/models/post.rb +13 -0
  32. data/spec/test_rails_app/app/views/tests/basic_form.html.erb +5 -0
  33. data/spec/test_rails_app/app/views/tests/model_form.html.erb +12 -0
  34. data/spec/test_rails_app/config/locales/bot-away-overrides.yml +6 -0
  35. data/spec/views/form_builder_spec.rb +118 -0
  36. metadata +94 -137
  37. data/Manifest.txt +0 -23
  38. data/README.rdoc +0 -179
  39. data/VERSION +0 -1
  40. data/lib/bot-away/action_dispatch/request.rb +0 -20
  41. data/spec/rspec_version.rb +0 -19
  42. data/spec/support/honeypot_matcher.rb +0 -30
  43. data/spec/support/obfuscation_helper.rb +0 -123
  44. data/spec/support/obfuscation_matcher.rb +0 -28
  45. data/spec/support/rails/mock_logger.rb +0 -21
  46. data/spec/support/test_controller.rb +0 -28
  47. data/spec/support/views/test/index.html.erb +0 -4
  48. data/spec/support/views/test/model_form.html.erb +0 -6
  49. data/spec/views/lib/action_view/helpers/instance_tag_spec.rb +0 -75
  50. data/spec/views/lib/disabled_for_spec.rb +0 -101
  51. 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 '&#x20;&#x3a;&#x79;&#x74;&#x70;&#x6d;&#x65;&#x20;&#x73;&#x69;&#x68;&#x74;&#x20;&#x65;&#x76;&#x61;&#x65;&#x4c;'}/
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(:each) do
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
- @params = params('test' => { 'name' => '', 'posts' => [] })
15
+ params('test' => { 'name' => '', 'posts' => [] })
16
16
  end
17
17
 
18
- subject { dump { BotAway::ParamParser.new(@ip, @params) } }
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(:each) { BotAway.dump_params = true }
26
- after(:each) { BotAway.dump_params = false }
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(:each) { @params = params({'test' => {'name' => 'colin', 'posts' => []}}) }
57
- subject { dump { BotAway::ParamParser.new(@ip, @params) } }
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(:each) { @params = params({'test' => {'name' => '', 'posts' => [1, 2]}}) }
66
- subject { dump { BotAway::ParamParser.new(@ip, @params) } }
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 }
@@ -1,113 +1,45 @@
1
- ENV['RAILS_ENV'] ||= 'test'
2
-
3
- require 'rubygems'
4
- require 'action_controller'
5
- require 'action_view'
6
-
7
- begin
8
- # rails 2.x
9
- ActionController::Routing::Routes.load!
10
- ActionController::Base.session = { :key => "_myapp_session", :secret => "12345"*6 }
11
- ActionController::Base.view_paths << File.expand_path(File.join(File.dirname(__FILE__), "support/views"))
12
- RAILS_VERSION = "2.3"
13
- rescue
14
- # rails 3
15
- #require 'active_record/railtie'
16
- require 'action_controller/railtie'
17
- require 'action_mailer/railtie'
18
- require 'active_resource/railtie'
19
- require 'rails/test_unit/railtie'
20
-
21
- # stub out the csrf token so that it always produces a predictable (testable) result
22
- module ActionView
23
- # = Action View CSRF Helper
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
- require File.join(File.dirname(__FILE__), '../lib/bot-away')
51
- require File.join(File.dirname(__FILE__), "rspec_version")
52
-
53
- class MockObject
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
- if RSPEC_VERSION < "2.0"
72
- Spec::Runner.configure do |config|
73
- config.before(:each) do
74
- BotAway.reset!
75
- end
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
- Dir[File.join(File.dirname(__FILE__), 'support/**/*.rb')].each do |fi|
112
- require fi
44
+ config.include BotAway::TestCase
113
45
  end
@@ -0,0 +1,11 @@
1
+ class TestsController < ActionController::Base
2
+ protect_from_forgery
3
+
4
+ def model_form
5
+ @post = Post.new
6
+ end
7
+
8
+ def proc_form
9
+ render :text => params.to_yaml
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ class Post
2
+ attr_reader :subject, :body, :subscribers
3
+
4
+ def subscribers
5
+ []
6
+ end
7
+
8
+ extend ActiveModel::Naming
9
+
10
+ def to_key
11
+ [1]
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ <%= form_for :user do |f| %>
2
+ <%= f.text_field :login %>
3
+ <%= f.password_field :password %>
4
+ <%= f.submit %>
5
+ <% end %>
@@ -0,0 +1,12 @@
1
+ <%= form_for @post, :url => url_for('proc_form') do |f| %>
2
+ <p>
3
+ <%= f.label :subject %><br/>
4
+ <%= f.text_field :subject %>
5
+ </p>
6
+
7
+ <p>
8
+ <%= f.select :subscribers, [["one", '1'], ['two', '2']] %>
9
+ </p>
10
+
11
+ <%= f.submit 'submit' %>
12
+ <%end%>
@@ -0,0 +1,6 @@
1
+ en:
2
+ bot_away:
3
+ number_of_honeypot_warning_messages: 4
4
+ honeypot_warning_2: "Overridden honeypot warning"
5
+ honeypot_warning_4: "Additional honeypot warning"
6
+
@@ -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("&#x65;&#x6d;&#x61;&#x6e;&#x5f;&#x64;&#x6f;&#x68;&#x74;&#x65;&#x6d;")
116
+ # end
117
+ end
118
+ end