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
@@ -15,7 +15,6 @@ module BotAway
15
15
 
16
16
  if authenticity_token
17
17
  if catch(:bastard) { deobfuscate! } == :took_the_bait
18
- #params.clear
19
18
  # don't clear the controller or action keys, as Rails 3 needs them
20
19
  params.keys.each { |key| params.delete(key) unless %w(controller action).include?(key) }
21
20
  params[:suspected_bot] = true
@@ -35,7 +34,8 @@ module BotAway
35
34
  deobfuscate!(value, object_name ? "#{object_name}[#{key}]" : key)
36
35
  else
37
36
  if object_name && !BotAway.excluded?(:object_name => object_name, :method_name => key)
38
- if value.blank? && params.keys.include?(spun_key = spinner.encode("#{object_name}[#{key}]"))
37
+ spun_key = spinner.encode("#{object_name}[#{key}]")
38
+ if value.blank? && params.keys.include?(spun_key)
39
39
  current[key] = params.delete(spun_key)
40
40
  else
41
41
  #puts "throwing on #{object_name}[#{key}] because its not blank" if !value.blank?
@@ -0,0 +1,10 @@
1
+ require 'rails/engine'
2
+ require 'rails/version'
3
+
4
+ class BotAway::Railtie < Rails::Engine
5
+ if Rails::VERSION::MINOR == 0 # Rails 3.0.x
6
+ paths.config.locales = File.expand_path("../locale/honeypots.yml", File.dirname(__FILE__))
7
+ else
8
+ paths["config/locales"] = File.expand_path("../locale/honeypots.yml", File.dirname(__FILE__))
9
+ end
10
+ end
@@ -0,0 +1,58 @@
1
+ module BotAway::TestCase
2
+ autoload :ControllerTestCase, 'bot-away/test_case/controller_test_case'
3
+ autoload :InstanceTagTestCase, 'bot-away/test_case/instance_tag_test_case'
4
+ autoload :MockObject, 'bot-away/test_case/mock_object'
5
+ autoload :Matchers, 'bot-away/test_case/matchers'
6
+
7
+ def builder
8
+ @builder ||= ActionView::Base.default_form_builder.new(object_name, mock_object, view, {}, proc {})
9
+ end
10
+
11
+ def enable_forgery_protection
12
+ # BotAway doesn't work without forgery protection, and RSpec-Rails 2 disables it.
13
+ # Lost way too many hours on this.
14
+ # Note: This has to happen in spec file because RSpec2 sets a before block, which runs
15
+ # after the ones set by config.
16
+ Rails.application.config.allow_forgery_protection = true
17
+ ActionController::Base.allow_forgery_protection = true
18
+ end
19
+
20
+ def disable_forgery_protection
21
+ Rails.application.config.allow_forgery_protection = false
22
+ ActionController::Base.allow_forgery_protection = false
23
+ end
24
+
25
+ def mock_object
26
+ @mock_object ||= MockObject.new
27
+ end
28
+
29
+ def obfuscated_id
30
+ "f51a02a636f507f1bd64722451b71297"
31
+ end
32
+
33
+ def obfuscated_name
34
+ "cd538a9170613d6dedbcc54a0aa24881"
35
+ end
36
+
37
+ def object_name
38
+ "object_name"
39
+ end
40
+
41
+ def method_name
42
+ "method_name"
43
+ end
44
+
45
+ def tag_id
46
+ "#{object_name}_#{method_name}"
47
+ end
48
+
49
+ def tag_name
50
+ "#{object_name}[#{method_name}]"
51
+ end
52
+
53
+ def dump
54
+ result = yield
55
+ puts result if ENV['DUMP']
56
+ result
57
+ end
58
+ end
@@ -0,0 +1,11 @@
1
+ module BotAway::TestCase::ControllerTestCase
2
+ # note that this only matters for TEST requests; real ones use the params parser.
3
+ module ::ActionController::TestCase::Behavior
4
+ def process_with_deobfuscation(action, parameters = nil, session = nil, flash = nil, http_method = 'GET')
5
+ parameters = BotAway::ParamParser.new(request.ip, (parameters || {}).with_indifferent_access).params
6
+ process_without_deobfuscation(action, parameters, session, flash, http_method)
7
+ end
8
+
9
+ alias_method_chain :process, :deobfuscation
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module BotAway::TestCase::InstanceTagTestCase
2
+ def self.included(base)
3
+ base.module_eval do
4
+ include RSpec::Rails::ViewExampleGroup
5
+ end
6
+ end
7
+
8
+ def mock_object
9
+ @mock_object ||= BotAway::TestCase::MockObject.new
10
+ end
11
+
12
+ def default_instance_tag
13
+ @default_instance_tag ||= ActionView::Helpers::InstanceTag.new("object_name", "method_name", view, mock_object)
14
+ end
15
+ end
@@ -0,0 +1,20 @@
1
+ module BotAway::TestCase::Matchers
2
+ autoload :ObfuscationMatcher, 'bot-away/test_case/matchers/obfuscation_matcher'
3
+ autoload :HoneypotMatcher, 'bot-away/test_case/matchers/honeypot_matcher'
4
+
5
+ def be_obfuscated_as(obfuscated_name, obfuscated_id)
6
+ ObfuscationMatcher.new(obfuscated_name, obfuscated_id)
7
+ end
8
+
9
+ def include_honeypot_called(tag_name, tag_id)
10
+ HoneypotMatcher.new(tag_name, tag_id)
11
+ end
12
+
13
+ def be_obfuscated
14
+ be_obfuscated_as obfuscated_name, obfuscated_id
15
+ end
16
+
17
+ def include_honeypot
18
+ include_honeypot_called tag_name, tag_id
19
+ end
20
+ end
@@ -0,0 +1,30 @@
1
+ class BotAway::TestCase::Matchers::HoneypotMatcher
2
+ attr_reader :tag_name, :tag_id
3
+
4
+ def initialize(tag_name, tag_id)
5
+ @tag_name, @tag_id = tag_name, tag_id
6
+ end
7
+
8
+ def matches?(target)
9
+ target = target.call if target.kind_of?(Proc)
10
+ @target = target
11
+ match(:id, tag_id) && match(:name, tag_name)
12
+ end
13
+
14
+ def match(key, value, suffix = nil)
15
+ @rx = /#{key}=['"]#{Regexp::escape value}["']/
16
+ @target[@rx]
17
+ end
18
+
19
+ def description
20
+ "include a honeypot named '#{tag_name}' with id '#{tag_id}'"
21
+ end
22
+
23
+ def failure_message
24
+ "expected #{@target.inspect}\n to match #{@rx.inspect}"
25
+ end
26
+
27
+ def negative_failure_message
28
+ "expected #{@target.inspect}\n to not match #{@rx.inspect}"
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ class BotAway::TestCase::Matchers::ObfuscationMatcher
2
+ attr_reader :obfuscated_name, :obfuscated_id
3
+
4
+ def initialize(obfuscated_name, obfuscated_id)
5
+ @obfuscated_id, @obfuscated_name = obfuscated_id, obfuscated_name
6
+ end
7
+
8
+ def matches?(target)
9
+ target = target.call if target.kind_of?(Proc)
10
+ @target = target
11
+ match(:id, obfuscated_id) && match(:name, obfuscated_name)
12
+ end
13
+
14
+ def match(key, value)
15
+ @rx = /#{key}=['"]#{Regexp::escape value}["']/
16
+ @target[@rx]
17
+ end
18
+
19
+ def description
20
+ "be obfuscated as '#{obfuscated_name}' with id '#{obfuscated_id}'"
21
+ end
22
+
23
+ def failure_message
24
+ "expected #{@target.inspect}\n to match #{@rx.inspect}"
25
+ end
26
+
27
+ def negative_failure_message
28
+ "expected #{@target.inspect}\n to not match #{@rx.inspect}"
29
+ end
30
+ end
@@ -0,0 +1,16 @@
1
+ class BotAway::TestCase::MockObject
2
+ attr_accessor :method_name
3
+
4
+ def id
5
+ 1
6
+ end
7
+
8
+ # for testing grouped_collection_select
9
+ def object_name
10
+ [self]
11
+ end
12
+
13
+ def initialize
14
+ @method_name = 'method_value'
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ module BotAway
2
+ module Version
3
+ MAJOR = 2
4
+ MINOR = 0
5
+ PATCH = 0
6
+ BUILD = nil
7
+
8
+ STRING = BUILD ? [MAJOR, MINOR, PATCH, BUILD].join('.') : [MAJOR, MINOR, PATCH].join('.')
9
+ end
10
+
11
+ VERSION = BotAway::Version::STRING
12
+ end
@@ -0,0 +1,6 @@
1
+ en:
2
+ bot_away:
3
+ number_of_honeypot_warning_messages: 3
4
+ honeypot_warning_1: "Leave this empty: "
5
+ honeypot_warning_2: "Don't fill this in: "
6
+ honeypot_warning_3: "Keep this blank: "
@@ -0,0 +1,112 @@
1
+ require 'spec_helper'
2
+
3
+ # For all intents and purposes this is a view spec, but because rspec mocks up the controller
4
+ # in view specs (and rightly so!), we need to technically run it as a controller spec in order
5
+ # to invoke a real controller first.
6
+ describe TestsController do
7
+ include BotAway::TestCase::ControllerTestCase
8
+ render_views
9
+ before { enable_forgery_protection }
10
+
11
+ let(:params) { { :controller => 'tests', :action => 'basic_form' } }
12
+
13
+ it "should use forgery protection" do # sanity check
14
+ subject.send(:protect_against_forgery?).should be_true
15
+ end
16
+
17
+ shared_examples_for "enabled" do
18
+ it "returns false when queried for disablement" do
19
+ BotAway.should_not be_disabled_for(params)
20
+ BotAway.should_not be_excluded(params)
21
+ end
22
+
23
+ it "obfuscates the elements" do
24
+ get 'basic_form'
25
+ response.body.should =~ /id="c89ca970ba19a4174d332aa8cfbd0c42"/ # user_login
26
+ response.body.should =~ /id="f41749ab8ef9d2358cfd170fd9af2f5e"/ # user_password
27
+ end
28
+ end
29
+
30
+ shared_examples_for "disabled" do
31
+ it "returns true when queried for disablement" do
32
+ # ..."disablement"?
33
+ BotAway.should be_disabled_for(params)
34
+ BotAway.should be_excluded(params)
35
+ end
36
+
37
+ it "does not obfuscate the elements" do
38
+ get 'basic_form'
39
+ response.body.should_not =~ /id="c89ca970ba19a4174d332aa8cfbd0c42"/ # user_login
40
+ response.body.should_not =~ /id="f41749ab8ef9d2358cfd170fd9af2f5e"/ # user_password
41
+ end
42
+ end
43
+
44
+ context "with bot-away disabled for only this view" do
45
+ before { BotAway.disabled_for({:controller => 'tests', :action => 'basic_form'}) }
46
+ it_should_behave_like "disabled"
47
+ end
48
+
49
+ context "with bot-away enabled for all views" do
50
+ it_should_behave_like "enabled"
51
+ end
52
+
53
+
54
+
55
+
56
+
57
+ context "with matching controller name" do
58
+ context "and no action" do
59
+ before { BotAway.disabled_for :controller => 'tests' }
60
+ it_should_behave_like "disabled"
61
+ end
62
+
63
+ context "and matching action" do
64
+ before { BotAway.disabled_for :controller => 'tests', :action => 'basic_form' }
65
+ it_should_behave_like "disabled"
66
+ end
67
+
68
+ context "and not matching action" do
69
+ before { BotAway.disabled_for :controller => 'tests', :action => 'create' }
70
+ it_should_behave_like "enabled"
71
+ end
72
+ end
73
+
74
+ context "with not matching controller name" do
75
+ context "and no action" do
76
+ before { BotAway.disabled_for :controller => 'users' }
77
+ it_should_behave_like "enabled"
78
+ end
79
+
80
+ context "and matching action" do
81
+ before { BotAway.disabled_for :controller => 'users', :action => 'basic_form' }
82
+ it_should_behave_like "enabled"
83
+ end
84
+
85
+ context "and not matching action" do
86
+ before { BotAway.disabled_for :controller => 'users', :action => 'create' }
87
+ it_should_behave_like "enabled"
88
+ end
89
+ end
90
+
91
+ context "with no controller name" do
92
+ context "and matching action" do
93
+ before { BotAway.disabled_for :action => 'basic_form' }
94
+ it_should_behave_like "disabled"
95
+ end
96
+
97
+ context "and not matching action" do
98
+ before { BotAway.disabled_for :action => 'create' }
99
+ it_should_behave_like "enabled"
100
+ end
101
+ end
102
+
103
+ context "with matching mode" do
104
+ before { BotAway.disabled_for :mode => ENV['RAILS_ENV'] }
105
+ it_should_behave_like "disabled"
106
+ end
107
+
108
+ context "with not matching mode" do
109
+ before { BotAway.disabled_for :mode => "this_is_not_#{ENV['RAILS_ENV']}" }
110
+ it_should_behave_like "enabled"
111
+ end
112
+ end
@@ -1,88 +1,42 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe TestController do
4
- if RAILS_VERSION < "3.0"
5
- # rails 2
6
-
7
- def setup_default_controller
8
- @request = ActionController::TestRequest.new
9
- # can the authenticity token so that we can predict the generated element names
10
- @request.session[:_csrf_token] = 'aVjGViz+pIphXt2pxrWfXgRXShOI0KXOILR23yw0WBo='
11
- @request.remote_addr = '208.77.188.166' # example.com
12
- @response = ActionController::TestResponse.new
13
- @controller = TestController.new
14
-
15
- @controller.request = @request
16
- @controller.params = {}
17
- @controller.send(:initialize_current_url)
18
- @controller
19
- end
20
-
21
- include ActionController::TestProcess
22
-
23
- def controller
24
- @controller
25
- end
26
- else
27
- # rails 3
28
- def setup_default_controller
29
- @controller.request.session[:_csrf_token] = 'aVjGViz+pIphXt2pxrWfXgRXShOI0KXOILR23yw0WBo='
30
- @controller.request.remote_addr = '208.77.188.166'
31
-
32
- @controller
33
- end
34
-
35
- #extend ActiveSupport::Concern
36
- #include ActionController::TestCase::Behavior
37
- delegate :session, :to => :controller
38
- end
39
-
40
- def prepare!(action = 'index', method = 'get')
41
- controller
42
- send(method, action)
43
- if RAILS_VERSION < "3.0"
44
- if @response.template.instance_variable_get("@exception")
45
- raise @response.template.instance_variable_get("@exception")
46
- end
47
- end
48
- end
49
-
50
- before(:each) { setup_default_controller }
3
+ describe TestsController do
4
+ include BotAway::TestCase::ControllerTestCase
51
5
 
52
- after :each do
53
- # effectively disables forgery protection.
54
- TestController.request_forgery_protection_token = nil
6
+ before do
7
+ disable_forgery_protection
8
+ request.session[:_csrf_token] = 'aVjGViz+pIphXt2pxrWfXgRXShOI0KXOILR23yw0WBo='
9
+ request.remote_addr = '208.77.188.166'
55
10
  end
56
-
57
11
 
58
12
  context "with a model" do
13
+ render_views
14
+
59
15
  context "with forgery protection" do
60
- before :each do
61
- (class << controller; self; end).send(:protect_from_forgery)
62
- prepare!('model_form')
16
+ before do
17
+ enable_forgery_protection
18
+ get 'model_form'
63
19
  end
64
20
 
65
21
  it "should work?" do
66
- #puts @response.body
22
+ response.body.should =~ /<select/
67
23
  end
68
24
  end
69
25
 
70
26
  context "without forgery protection" do
71
- before :each do
72
- prepare!('model_form')
27
+ before do
28
+ get 'model_form'
73
29
  end
74
30
 
75
31
  it "should work?" do
76
- #puts @response.body
32
+ response.body.should =~ /<select/
77
33
  end
78
34
  end
79
35
  end
80
36
 
81
37
  context "with forgery protection" do
82
- before :each do
83
- (class << controller; self; end).send(:protect_from_forgery)
84
- prepare! if RAILS_VERSION < "3.0"
85
- end
38
+ before { enable_forgery_protection }
39
+
86
40
  #"object_name_method_name" name="object_name[method_name]" size="30" type="text" value="" /></div>
87
41
  #<input id="e21372563297c728093bf74c3cb6b96c" name="a0844d45bf150668ff1d86a6eb491969" size="30" type="text" value="method_value" />
88
42
 
@@ -92,12 +46,12 @@ describe TestController do
92
46
  '842d8d1c80014ce9f3d974614338605c' => 'some_value'
93
47
  }
94
48
  post 'proc_form', form
95
- #puts @response.body
49
+ #puts response.body
96
50
  controller.params[:object_name].should == { 'method_name' => 'some_value' }
97
51
  end
98
52
 
99
53
  context "after processing valid obfuscated post" do
100
- before(:each) do
54
+ before do
101
55
  post 'proc_form', { 'authenticity_token' => '1234',
102
56
  'object_name' => { 'method_name' => '' },
103
57
  '842d8d1c80014ce9f3d974614338605c' => 'some_value'
@@ -152,36 +106,31 @@ describe TestController do
152
106
  controller.request.remote_addr = '127.0.0.1'
153
107
  post 'proc_form', form
154
108
  # puts controller.params.inspect
155
- controller.params.should == { 'action' => 'proc_form', 'controller' => 'test',
156
- 'authenticity_token' => 'yPgTAsngzpBO8k1v83RGH26sTrQYD50Ou2oiMT4r/iw=',
157
- 'user_session' => {
158
- 'login' => 'admin',
159
- 'password' => 'pwpwpw',
160
- 'remember_me' => '1'
161
- },
162
- 'commit' => 'Log In'
109
+ controller.params.should == { 'action' => 'proc_form', 'controller' => 'tests',
110
+ 'authenticity_token' => 'yPgTAsngzpBO8k1v83RGH26sTrQYD50Ou2oiMT4r/iw=',
111
+ 'user_session' => {
112
+ 'login' => 'admin',
113
+ 'password' => 'pwpwpw',
114
+ 'remember_me' => '1'
115
+ },
116
+ 'commit' => 'Log In'
163
117
  }
164
118
  end
165
119
  end
166
120
 
167
121
  context "without forgery protection" do
168
- before :each do
169
- prepare! if RAILS_VERSION < "3.0"
170
- end
171
-
172
122
  it "processes non-obfuscated form post" do
173
123
  form = { #'authenticity_token' => '1234',
174
124
  'object_name' => { 'method_name' => 'test' }
175
125
  }
176
126
  post 'proc_form', form
177
- # puts @response.body
127
+ # puts response.body
178
128
  controller.params.should_not == { 'suspected_bot' => true }
179
129
  controller.params[:object_name].should == { 'method_name' => 'test' }
180
130
  end
181
131
 
182
132
  it "produces non-obfuscated form elements" do
183
- prepare! if RAILS_VERSION >= "3.0"
184
- @response.body.should_not match(/<\/div><input/)
133
+ response.body.should_not match(/<\/div><input/)
185
134
  end
186
135
  end
187
136
  end