bot-away 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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