bot-away 1.1.0 → 1.2.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/.gitignore +7 -0
- data/History.txt +12 -2
- data/LICENSE +20 -0
- data/README.rdoc +124 -67
- data/Rakefile +57 -0
- data/VERSION +1 -0
- data/bot-away.gemspec +96 -0
- data/lib/bot-away.rb +67 -7
- data/lib/bot-away/action_dispatch/request.rb +20 -0
- data/lib/bot-away/action_view/helpers/instance_tag.rb +13 -4
- data/lib/bot-away/param_parser.rb +25 -12
- data/lib/bot-away/spinner.rb +0 -2
- data/spec/controllers/test_controller_spec.rb +82 -35
- data/spec/rspec_version.rb +19 -0
- data/spec/spec_helper.rb +103 -2
- data/spec/support/obfuscation_helper.rb +102 -47
- data/spec/support/rails/mock_logger.rb +21 -0
- data/spec/support/test_controller.rb +28 -0
- data/spec/{lib → views/lib}/action_view/helpers/instance_tag_spec.rb +28 -22
- data/spec/views/lib/disabled_for_spec.rb +101 -0
- data/spec/{lib/builder_spec.rb → views/lib/form_builder_spec.rb} +5 -12
- data/spec/{lib → views/lib}/param_parser_spec.rb +10 -4
- metadata +66 -32
- data/lib/bot-away/action_controller/request.rb +0 -19
- data/spec/support/controllers/test_controller.rb +0 -18
data/spec/spec_helper.rb
CHANGED
@@ -1,11 +1,112 @@
|
|
1
|
+
ENV['RAILS_ENV'] ||= 'test'
|
2
|
+
|
1
3
|
require 'rubygems'
|
2
4
|
require 'action_controller'
|
3
5
|
require 'action_view'
|
4
6
|
|
5
|
-
|
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'
|
45
|
+
end
|
46
|
+
|
47
|
+
RAILS_VERSION = "3.0"
|
48
|
+
end
|
7
49
|
|
8
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
|
69
|
+
|
70
|
+
|
71
|
+
if RSPEC_VERSION < "2.0"
|
72
|
+
Spec::Runner.configure do |config|
|
73
|
+
config.before(:each) do
|
74
|
+
BotAway.reset!
|
75
|
+
end
|
76
|
+
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
|
9
110
|
|
10
111
|
Dir[File.join(File.dirname(__FILE__), 'support/**/*.rb')].each do |fi|
|
11
112
|
require fi
|
@@ -1,68 +1,123 @@
|
|
1
1
|
module ObfuscationHelper
|
2
|
-
def
|
3
|
-
|
4
|
-
subject.should include_honeypot(object_name, method_name)
|
5
|
-
end
|
6
|
-
end
|
7
|
-
|
8
|
-
def is_obfuscated_as(id, name)
|
9
|
-
it "is obfuscated as #{id}, #{name}" do
|
10
|
-
subject.should be_obfuscated_as(id, name)
|
11
|
-
end
|
2
|
+
def controller_class
|
3
|
+
TestController
|
12
4
|
end
|
13
5
|
|
14
6
|
def dump
|
15
|
-
|
7
|
+
result = yield
|
8
|
+
puts result if ENV['DUMP']
|
9
|
+
result
|
16
10
|
end
|
17
|
-
|
11
|
+
|
12
|
+
if RAILS_VERSION >= "3.0"
|
13
|
+
def self.included(base)
|
14
|
+
base.before(:each) do
|
15
|
+
session[:_csrf_token] = '1234'
|
16
|
+
controller.stub!(:protect_from_forgery?).and_return(true)
|
17
|
+
if respond_to?(:view)
|
18
|
+
controller.request.path_parameters["action"] ||= "index"
|
19
|
+
controller.action_name ||= "index"
|
20
|
+
view.stub!(:request_forgery_protection_token).and_return(:authenticity_token)
|
21
|
+
view.stub!(:form_authenticity_token).and_return('1234')
|
22
|
+
else
|
23
|
+
# response.stub!(:request_forgery_protection_token).and_return(:authenticity_token)
|
24
|
+
# response.stub!(:form_authenticity_token).and_return('1234')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
18
30
|
def builder
|
19
31
|
return @builder if @builder
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
if !options.kind_of?(Hash)
|
29
|
-
options = { :obfuscated_id => options, :obfuscated_name => unused }
|
30
|
-
end
|
31
|
-
|
32
|
-
obfuscated_id = options[:obfuscated_id] || self.obfuscated_id
|
33
|
-
obfuscated_name = options[:obfuscatd_name] || self.obfuscated_name
|
34
|
-
|
35
|
-
value = yield
|
36
|
-
context "##{method}" do
|
37
|
-
subject { proc { dump { value } } }
|
38
|
-
|
39
|
-
if options[:name]
|
40
|
-
includes_honeypot(options[:name], nil)
|
41
|
-
else
|
42
|
-
includes_honeypot(options[:object_name] || object_name, options[:method_name] || method_name)
|
43
|
-
end
|
44
|
-
is_obfuscated_as(obfuscated_id, obfuscated_name)
|
32
|
+
if RAILS_VERSION < "3.0"
|
33
|
+
response = TestController.call(Rack::MockRequest.env_for('/').merge({'REQUEST_URI' => '/',
|
34
|
+
'REMOTE_ADDR' => '127.0.0.1'}))
|
35
|
+
response.template.controller.request_forgery_protection_token = :authenticity_token
|
36
|
+
response.template.controller.session[:_csrf_token] = '1234'
|
37
|
+
@builder = ActionView::Helpers::FormBuilder.new(:object_name, MockObject.new, response.template, {}, proc {})
|
38
|
+
else
|
39
|
+
@builder = ActionView::Base.default_form_builder.new(:object_name, MockObject.new, view, {}, proc {})
|
45
40
|
end
|
46
41
|
end
|
47
|
-
|
42
|
+
|
43
|
+
# this is the obfuscated version of the string "object_name_method_name"
|
48
44
|
def obfuscated_id
|
49
|
-
|
45
|
+
self.class.obfuscated_id
|
50
46
|
end
|
51
|
-
|
47
|
+
|
48
|
+
# this is the obfuscated version of the string "object_name[method_name]"
|
52
49
|
def obfuscated_name
|
53
|
-
|
50
|
+
self.class.obfuscated_name
|
54
51
|
end
|
55
52
|
|
56
53
|
def object_name
|
57
|
-
|
54
|
+
self.class.object_name
|
58
55
|
end
|
59
|
-
|
56
|
+
|
60
57
|
def method_name
|
61
|
-
|
58
|
+
self.class.method_name
|
59
|
+
end
|
60
|
+
|
61
|
+
module ClassMethods
|
62
|
+
def includes_honeypot(object_name, method_name)
|
63
|
+
it "includes a honeypot called #{object_name}[#{method_name}]" do
|
64
|
+
subject.should include_honeypot(object_name, method_name)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def is_obfuscated_as(id, name)
|
69
|
+
it "is obfuscated as #{id}, #{name}" do
|
70
|
+
subject.should be_obfuscated_as(id, name)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def obfuscates(method, options = {}, unused = nil, &block)
|
75
|
+
if !options.kind_of?(Hash)
|
76
|
+
options = { :obfuscated_id => options, :obfuscated_name => unused }
|
77
|
+
end
|
78
|
+
|
79
|
+
obfuscated_id = options[:obfuscated_id] || self.obfuscated_id
|
80
|
+
obfuscated_name = options[:obfuscatd_name] || self.obfuscated_name
|
81
|
+
|
82
|
+
context "##{method}" do
|
83
|
+
before(:each) { @obfuscates_value = instance_eval(&block) }
|
84
|
+
subject { proc { dump { @obfuscates_value } } }
|
85
|
+
|
86
|
+
if options[:name]
|
87
|
+
includes_honeypot(options[:name], nil)
|
88
|
+
else
|
89
|
+
includes_honeypot(options[:object_name] || object_name, options[:method_name] || method_name)
|
90
|
+
end
|
91
|
+
is_obfuscated_as(obfuscated_id, obfuscated_name)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def obfuscated_id
|
96
|
+
RAILS_VERSION >= "3.0" ? "f51a02a636f507f1bd64722451b71297" : "e21372563297c728093bf74c3cb6b96c"
|
97
|
+
end
|
98
|
+
|
99
|
+
def obfuscated_name
|
100
|
+
RAILS_VERSION >= "3.0" ? "cd538a9170613d6dedbcc54a0aa24881" : "a0844d45bf150668ff1d86a6eb491969"
|
101
|
+
end
|
102
|
+
|
103
|
+
def object_name
|
104
|
+
"object_name"
|
105
|
+
end
|
106
|
+
|
107
|
+
def method_name
|
108
|
+
"method_name"
|
109
|
+
end
|
62
110
|
end
|
63
111
|
end
|
64
112
|
|
65
|
-
|
66
|
-
config
|
67
|
-
|
113
|
+
if RSPEC_VERSION < "2.0"
|
114
|
+
Spec::Runner.configure do |config|
|
115
|
+
config.extend ObfuscationHelper::ClassMethods
|
116
|
+
config.include ObfuscationHelper
|
117
|
+
end
|
118
|
+
else
|
119
|
+
RSpec.configure do |config|
|
120
|
+
config.extend ObfuscationHelper::ClassMethods
|
121
|
+
config.include ObfuscationHelper
|
122
|
+
end
|
68
123
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec/mocks'
|
2
|
+
|
3
|
+
module Rails
|
4
|
+
class << self
|
5
|
+
def logger
|
6
|
+
return @logger if @logger
|
7
|
+
@logger = Object.new
|
8
|
+
klass = class << @logger; self; end
|
9
|
+
if RSPEC_VERSION < "2.0"
|
10
|
+
klass.send(:include, Spec::Mocks::Methods)
|
11
|
+
else
|
12
|
+
end
|
13
|
+
|
14
|
+
def @logger.debug(message); end
|
15
|
+
def @logger.info(message); end
|
16
|
+
def @logger.error(message); end
|
17
|
+
def @logger.warn(message); end
|
18
|
+
@logger
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Post
|
2
|
+
attr_reader :subject, :body, :subscribers
|
3
|
+
|
4
|
+
if defined?(ActiveModel)
|
5
|
+
extend ActiveModel::Naming
|
6
|
+
|
7
|
+
def to_key
|
8
|
+
[1]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class ApplicationController < ActionController::Base
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
class TestController < ApplicationController
|
18
|
+
def index
|
19
|
+
end
|
20
|
+
|
21
|
+
def model_form
|
22
|
+
@post = Post.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def proc_form
|
26
|
+
render :text => params.to_yaml
|
27
|
+
end
|
28
|
+
end
|
@@ -1,23 +1,29 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
3
|
+
describe ActionView::Helpers::InstanceTag do
|
4
|
+
if method_defined?(:controller)
|
5
|
+
def template
|
6
|
+
view
|
7
|
+
end
|
8
|
+
else
|
9
|
+
def template
|
10
|
+
return @response if @response
|
11
|
+
@response = TestController.call(Rack::MockRequest.env_for('/').merge({'REQUEST_URI' => '/',
|
12
|
+
'REMOTE_ADDR' => '127.0.0.1'}))
|
13
|
+
@response.template.controller.request_forgery_protection_token = :authenticity_token
|
14
|
+
@response.template.controller.session[:_csrf_token] = '1234'
|
15
|
+
@response.template
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def mock_object
|
20
|
+
@mock_object ||= MockObject.new
|
21
|
+
end
|
15
22
|
|
16
|
-
def default_instance_tag
|
17
|
-
|
18
|
-
end
|
23
|
+
def default_instance_tag
|
24
|
+
ActionView::Helpers::InstanceTag.new("object_name", "method_name", template, mock_object)
|
25
|
+
end
|
19
26
|
|
20
|
-
describe ActionView::Helpers::InstanceTag do
|
21
27
|
subject { default_instance_tag }
|
22
28
|
|
23
29
|
context "with a valid text area tag" do
|
@@ -39,17 +45,17 @@ describe ActionView::Helpers::InstanceTag do
|
|
39
45
|
end
|
40
46
|
|
41
47
|
it "should obfuscate tag name" do
|
42
|
-
subject.obfuscated_tag(*@tag_options).should =~ /name="
|
48
|
+
subject.obfuscated_tag(*@tag_options).should =~ /name="#{obfuscated_name}"/
|
43
49
|
end
|
44
50
|
|
45
51
|
it "should obfuscate tag id" do
|
46
|
-
subject.obfuscated_tag(*@tag_options).should =~ /id="
|
47
|
-
end
|
48
|
-
|
49
|
-
it "should not obfuscate tag value" do
|
50
|
-
subject.obfuscated_tag(*@tag_options).should_not =~ /value="5a6a50d5fd0b5c8b1190d87eb0057e47"/
|
52
|
+
subject.obfuscated_tag(*@tag_options).should =~ /id="#{obfuscated_id}"/
|
51
53
|
end
|
52
54
|
|
55
|
+
# it "should not obfuscate tag value" do
|
56
|
+
# subject.obfuscated_tag(*@tag_options).should =~ /value="@tag_options"/
|
57
|
+
# end
|
58
|
+
#
|
53
59
|
it "should include unobfuscated tag value" do
|
54
60
|
subject.obfuscated_tag(*@tag_options).should =~ /value="method_value"/
|
55
61
|
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Bot-Away" do
|
4
|
+
# Basically we're just switching BA on and off, so we only need 2 sets of tests: what to expect when it's on, and
|
5
|
+
# what to expect when it's off. The rest of this file just flips the switches.
|
6
|
+
|
7
|
+
def test_params
|
8
|
+
{"model"=>"User", "commit"=>"Sign in",
|
9
|
+
"authenticity_token"=>"XBQEDkXrm4E8U9slBX45TWNx7TPcx8ww2FSJRy/XXg4=",
|
10
|
+
"action"=>"index", "controller"=>"test", "user"=>{"username"=>"admin", "password"=>"Admin01"}
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.it_should_be_disabled
|
15
|
+
it "should not obfuscate the field, because it should be disabled" do
|
16
|
+
builder.text_field('method_name').should_not match(/name="#{obfuscated_name}/)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should not drop invalid params, because it should be disabled" do
|
20
|
+
parms = BotAway::ParamParser.new('127.0.0.1', test_params).params
|
21
|
+
parms.should == test_params
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should be disabled" do
|
25
|
+
BotAway.disabled_for?(:controller => 'test', :action => "index").should == true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.it_should_be_enabled
|
30
|
+
it "should obfuscate the field, because it should be enabled" do
|
31
|
+
builder.text_field('method_name').should be_obfuscated_as(obfuscated_id, obfuscated_name)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should drop invalid params, because it should be enabled" do
|
35
|
+
parms = BotAway::ParamParser.new('127.0.0.1', test_params).params
|
36
|
+
parms.should_not == test_params
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should be disabled" do
|
40
|
+
BotAway.disabled_for?(:controller => 'test', :action => "index").should == false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# flip-switching begins
|
45
|
+
|
46
|
+
context "with matching controller name" do
|
47
|
+
context "and no action" do
|
48
|
+
before(:each) { BotAway.disabled_for :controller => 'test' }
|
49
|
+
it_should_be_disabled
|
50
|
+
end
|
51
|
+
|
52
|
+
context "and matching action" do
|
53
|
+
before(:each) { BotAway.disabled_for :controller => 'test', :action => 'index' }
|
54
|
+
it_should_be_disabled
|
55
|
+
end
|
56
|
+
|
57
|
+
context "and not matching action" do
|
58
|
+
before(:each) { BotAway.disabled_for :controller => 'test', :action => 'create' }
|
59
|
+
it_should_be_enabled
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context "with not matching controller name" do
|
64
|
+
context "and no action" do
|
65
|
+
before(:each) { BotAway.disabled_for :controller => 'users' }
|
66
|
+
it_should_be_enabled
|
67
|
+
end
|
68
|
+
|
69
|
+
context "and matching action" do
|
70
|
+
before(:each) { BotAway.disabled_for :controller => 'users', :action => 'index' }
|
71
|
+
it_should_be_enabled
|
72
|
+
end
|
73
|
+
|
74
|
+
context "and not matching action" do
|
75
|
+
before(:each) { BotAway.disabled_for :controller => 'users', :action => 'create' }
|
76
|
+
it_should_be_enabled
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context "with no controller name" do
|
81
|
+
context "and matching action" do
|
82
|
+
before(:each) { BotAway.disabled_for :action => 'index' }
|
83
|
+
it_should_be_disabled
|
84
|
+
end
|
85
|
+
|
86
|
+
context "and not matching action" do
|
87
|
+
before(:each) { BotAway.disabled_for :action => 'create' }
|
88
|
+
it_should_be_enabled
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context "with matching mode" do
|
93
|
+
before(:each) { BotAway.disabled_for :mode => ENV['RAILS_ENV'] }
|
94
|
+
it_should_be_disabled
|
95
|
+
end
|
96
|
+
|
97
|
+
context "with not matching mode" do
|
98
|
+
before(:each) { BotAway.disabled_for :mode => "this_is_not_#{ENV['RAILS_ENV']}" }
|
99
|
+
it_should_be_enabled
|
100
|
+
end
|
101
|
+
end
|