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
@@ -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