subdomainbox 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.3@subdomainbox
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'uuidtools'
4
+
5
+ group :development do
6
+ gem 'rspec', '2.10.0'
7
+ gem 'jeweler', '~> 1.8.4'
8
+ end
9
+
10
+ group :development, :test do
11
+ gem 'pry'
12
+ gem 'pry-nav'
13
+ gem 'pry-stack_explorer'
14
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,49 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ binding_of_caller (0.7.1)
5
+ debug_inspector (>= 0.0.1)
6
+ coderay (1.0.9)
7
+ debug_inspector (0.0.2)
8
+ diff-lcs (1.1.3)
9
+ git (1.2.5)
10
+ jeweler (1.8.4)
11
+ bundler (~> 1.0)
12
+ git (>= 1.2.5)
13
+ rake
14
+ rdoc
15
+ json (1.7.7)
16
+ method_source (0.8.1)
17
+ pry (0.9.12)
18
+ coderay (~> 1.0.5)
19
+ method_source (~> 0.8)
20
+ slop (~> 3.4)
21
+ pry-nav (0.2.3)
22
+ pry (~> 0.9.10)
23
+ pry-stack_explorer (0.4.9)
24
+ binding_of_caller (>= 0.7)
25
+ pry (~> 0.9.11)
26
+ rake (10.0.3)
27
+ rdoc (4.0.0)
28
+ json (~> 1.4)
29
+ rspec (2.10.0)
30
+ rspec-core (~> 2.10.0)
31
+ rspec-expectations (~> 2.10.0)
32
+ rspec-mocks (~> 2.10.0)
33
+ rspec-core (2.10.1)
34
+ rspec-expectations (2.10.0)
35
+ diff-lcs (~> 1.1.3)
36
+ rspec-mocks (2.10.1)
37
+ slop (3.4.3)
38
+ uuidtools (2.1.3)
39
+
40
+ PLATFORMS
41
+ ruby
42
+
43
+ DEPENDENCIES
44
+ jeweler (~> 1.8.4)
45
+ pry
46
+ pry-nav
47
+ pry-stack_explorer
48
+ rspec (= 2.10.0)
49
+ uuidtools
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 Daniel Nelson
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,62 @@
1
+ subdomainbox
2
+ ============
3
+
4
+ Subdomain boxing was inspired by Egor Homakov's [post on pageboxing](http://homakov.blogspot.com/2013/02/pagebox-website-gatekeeper.html). Subdomain boxing limits the reach of any XSS attacks. If an attacker manages to insert javascript onto a page of your application, the javascript on that page will be unable to read data from or post data to any pages on different subdomains in your application. Post protection is achieved by creating a separate CSRF token for each subdomain. CSRF protection is also strengthened by changing the CSRF token based on session id.
5
+
6
+ The subdomainbox gem is simple to add even to existing Rails applications:
7
+
8
+ class PostsController < ApplicationController
9
+
10
+ subdomainbox 'posts', :only => :index
11
+ subdomainbox ['posts-%{id}', 'comments-%{pop_id}'], :except => :index
12
+
13
+ ...
14
+
15
+ end
16
+
17
+
18
+ class Admin::PostsController < ApplicationController
19
+
20
+ subdomainbox 'admin', :only => :index
21
+ subdomainbox 'admin-%{post_id}', :except => :index
22
+
23
+ ...
24
+
25
+ end
26
+
27
+
28
+ Testing
29
+ =======
30
+
31
+ In controller specs:
32
+
33
+ controller.stub(:subdomainbox)
34
+
35
+
36
+ To make request/feature/integration specs work:
37
+
38
+ brew install dnsmasq
39
+ mkdir -pv $(brew --prefix)/etc/
40
+ echo 'address=/.dev/127.0.0.1' > $(brew --prefix)/etc/dnsmasq.conf
41
+ sudo cp -v $(brew --prefix dnsmasq)/homebrew.mxcl.dnsmasq.plist /Library/LaunchDaemons
42
+ sudo launchctl load -w /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist
43
+ sudo mkdir -v /etc/resolver
44
+ sudo bash -c 'echo "nameserver 127.0.0.1" > /etc/resolver/dev'
45
+
46
+ -- source [http://www.echoditto.com/blog/never-touch-your-local-etchosts-file-os-x-again](http://www.echoditto.com/blog/never-touch-your-local-etchosts-file-os-x-again)
47
+
48
+ Contributing to subdomainbox
49
+ ============================
50
+
51
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
52
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
53
+ * Fork the project.
54
+ * Start a feature/bugfix branch.
55
+ * Commit and push until you are happy with your contribution.
56
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
57
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
58
+
59
+ Credits
60
+ =======
61
+
62
+ Inspired by Egor Homakov's [post on pageboxing](http://homakov.blogspot.com/2013/02/pagebox-website-gatekeeper.html).
data/Rakefile ADDED
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "subdomainbox"
18
+ gem.homepage = "http://github.com/populr/subdomainbox"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{prevent XSS incursions from accessing entire application}
21
+ gem.description = %Q{use subdomains to prevent XSS from accessing your entire application if it should happen to be injected into some page in your app}
22
+ gem.email = "dnelson@centresource.com"
23
+ gem.authors = ["Daniel Nelson"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ task :default => :spec
40
+
41
+ require 'rdoc/task'
42
+ Rake::RDocTask.new do |rdoc|
43
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
44
+
45
+ rdoc.rdoc_dir = 'rdoc'
46
+ rdoc.title = "subdomainbox #{version}"
47
+ rdoc.rdoc_files.include('README*')
48
+ rdoc.rdoc_files.include('lib/**/*.rb')
49
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
@@ -0,0 +1,7 @@
1
+ class SubdomainboxGenerator < Rails::Generators::Base
2
+
3
+ def create_initializer_file
4
+ create_file "config/initializers/xsrf_token_secret.rb", "XSRF_TOKEN_SECRET = '#{SecureRandom.base64(48)}'"
5
+ end
6
+
7
+ end
@@ -0,0 +1,21 @@
1
+ require 'digest/sha1'
2
+
3
+ module ActionController #:nodoc:
4
+
5
+ module RequestForgeryProtection
6
+
7
+ protected
8
+
9
+ alias_method :original_form_authenticity_token, :form_authenticity_token
10
+ # Sets the token value for the current session.
11
+ def form_authenticity_token
12
+ raise 'XSRF token secret must be defined' if XSRF_TOKEN_SECRET.nil? || XSRF_TOKEN_SECRET.empty?
13
+ if request.session_options[:id]
14
+ Digest::SHA1.hexdigest("#{XSRF_TOKEN_SECRET}#{request.session_options[:id]}#{request.subdomain}")
15
+ else
16
+ original_form_authenticity_token
17
+ end
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,81 @@
1
+ module ActionController
2
+ class Base
3
+
4
+ class SubdomainboxDomainViolation < StandardError
5
+ end
6
+
7
+ def self.subdomainbox(allowed, options={})
8
+ before_filter(lambda { subdomainbox(:allowed => allowed) }, options)
9
+ end
10
+
11
+ def subdomainbox(options)
12
+ allowed = subdomainbox_process_definitions(options)
13
+ subdomain_match = subdomainbox_find_subdomain_match(allowed)
14
+ subdomainbox_no_subdomain_match!(allowed) if subdomain_match.empty?
15
+ end
16
+
17
+ private
18
+
19
+ def subdomainbox_no_subdomain_match!(allowed)
20
+ if request.format == 'text/html'
21
+ if request.get?
22
+ flash[:alert] = flash.now[:alert]
23
+ flash[:notice] = flash.now[:notice]
24
+ flash[:info] = flash.now[:info]
25
+
26
+ default_definition = allowed.first
27
+ if default_definition.first == ''
28
+ redirect_to(request.protocol + request.domain + request.port_string + request.fullpath)
29
+ else
30
+ allowed_id_name = default_definition.pop
31
+ allowed_id_name = allowed_id_name if allowed_id_name
32
+ default_definition << params[allowed_id_name]
33
+ default_definition.compact!
34
+ default_definition.pop if default_definition.length == 2
35
+
36
+ redirect_to(request.protocol + default_definition.join + '.' + request.domain + request.port_string + request.fullpath)
37
+ end
38
+ else
39
+ raise SubdomainboxDomainViolation.new
40
+ end
41
+ else
42
+ raise SubdomainboxDomainViolation.new
43
+ end
44
+ end
45
+
46
+ def subdomainbox_find_subdomain_match(allowed)
47
+ allowed.each do |allowed_subdomain, separator, allowed_id_name|
48
+ if allowed_subdomain == ''
49
+ next unless request.subdomain.nil? || request.subdomain.empty?
50
+ else
51
+ next unless request.subdomain =~ /\A#{allowed_subdomain}\.?/
52
+ end
53
+ if allowed_id_name
54
+ if id = request.subdomain.sub(/\A#{allowed_subdomain}\.?/, '')
55
+ if params.keys.include?(allowed_id_name)
56
+ return [] unless id == params[allowed_id_name]
57
+ else
58
+ params[allowed_id_name] = id
59
+ end
60
+ end
61
+ end
62
+ return [allowed_subdomain, separator, id]
63
+ end
64
+ []
65
+ end
66
+
67
+ def subdomainbox_process_definitions(options)
68
+ allowed = []
69
+ raw_definitions = options[:allowed]
70
+ raw_definitions = [raw_definitions] unless raw_definitions.is_a?(Array)
71
+ raw_definitions.each do |definition|
72
+ discard, allowed_subdomain, separator, allowed_id_name = definition.match(/([^%]*?)(\.?)\%\{([^}]*)\}/).to_a
73
+ allowed_subdomain = definition if allowed_subdomain.nil?
74
+ allowed_id_name = allowed_id_name if allowed_id_name
75
+ allowed << [allowed_subdomain, separator, allowed_id_name]
76
+ end
77
+ allowed
78
+ end
79
+
80
+ end
81
+ end
@@ -0,0 +1,44 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "ActionController::RequestForgeryProtection" do
4
+ include ActionController::RequestForgeryProtection
5
+ let(:request) { double('request') }
6
+ let(:session) { {} }
7
+
8
+ before(:each) do
9
+ request.stub(:subdomain).and_return('pets')
10
+ request.stub_chain(:session_options, :[]).and_return('abc')
11
+ end
12
+
13
+ describe "#form_authenticity_token" do
14
+
15
+ context "when XSRF_TOKEN_SECRET is blank" do
16
+ it "should raise an exception" do
17
+ XSRF_TOKEN_SECRET = ''
18
+ lambda {
19
+ form_authenticity_token
20
+ }.should raise_error
21
+ end
22
+ end
23
+
24
+ context "when the user has a session" do
25
+
26
+ it "should be generated from the XSRF_TOKEN_SECRET salted with the session id and the subdomain" do
27
+ request.stub_chain(:session_options, :[]).and_return('abc')
28
+ XSRF_TOKEN_SECRET = 'xyz'
29
+ form_authenticity_token.should == Digest::SHA1.hexdigest('xyzabcpets')
30
+ end
31
+
32
+ end
33
+
34
+ context "when there is no session id" do
35
+ it "should call the original form_authenticity_token" do
36
+ request.stub_chain(:session_options, :[]).and_return(nil)
37
+ self.should_receive(:original_form_authenticity_token)
38
+ form_authenticity_token
39
+ end
40
+ end
41
+
42
+ end
43
+
44
+ end
@@ -0,0 +1,30 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+
4
+
5
+
6
+ module ActionController
7
+ module RequestForgeryProtection
8
+ def form_authenticity_token
9
+ raise 'wrong form_authenticity_token method'
10
+ end
11
+ end
12
+ end
13
+
14
+
15
+ require 'rspec'
16
+ require 'subdomainbox'
17
+ require 'secure_xsrf_token'
18
+ require 'bundler'
19
+ Bundler.require
20
+ require 'pry'
21
+ require 'pry-nav'
22
+ require 'pry-stack_explorer'
23
+
24
+ # Requires supporting files with custom matchers and macros, etc,
25
+ # in ./support/ and its subdirectories.
26
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
27
+
28
+ RSpec.configure do |config|
29
+
30
+ end
@@ -0,0 +1,326 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe ActionController::Base do
4
+
5
+ # default behavior:
6
+ # subdomainbox 'mysubdomain%{pop_id}', :except => []
7
+ # subdomainbox 'mysubdomain', :only => []
8
+ describe "#subdomainbox" do
9
+ let(:request) { double('request') }
10
+ let(:controller) { ActionController::Base.new }
11
+ let(:flash) { double('flash') }
12
+ let(:flash_now) { double('flash_now') }
13
+
14
+ before(:each) do
15
+ request.stub(:domain).and_return('peanuts.com')
16
+ controller.stub(:request).and_return(request)
17
+ controller.stub(:params).and_return({})
18
+
19
+ flash.stub(:now).and_return(flash_now)
20
+ flash.stub(:[]=)
21
+ flash_now.stub(:[])
22
+ controller.stub(:flash).and_return(flash)
23
+ end
24
+
25
+ context "when the specified subdomain includes an id" do
26
+ before(:each) do
27
+ request.stub(:format).and_return('text/html')
28
+ request.stub(:subdomain).and_return('pets.abc')
29
+ end
30
+
31
+ context "when the params include a matching id" do
32
+ it "should not raise an exception" do
33
+ params = { 'pet_id' => 'abc' }
34
+ controller.stub(:params).and_return(params)
35
+ lambda {
36
+ controller.subdomainbox :allowed => 'pets.%{pet_id}'
37
+ }.should_not raise_error
38
+ end
39
+ end
40
+
41
+
42
+ context "when the params don't include an id of the specified name" do
43
+ it "should not raise an exception" do
44
+ params = { 'id' => 'efg' }
45
+ controller.stub(:params).and_return(params)
46
+ lambda {
47
+ controller.subdomainbox :allowed => 'pets.%{pet_id}'
48
+ }.should_not raise_error
49
+ end
50
+
51
+ it "should set a param of the specified name on params" do
52
+ params = {}
53
+ controller.stub(:params).and_return(params)
54
+ controller.subdomainbox :allowed => 'pets.%{pet_id}'
55
+ params['pet_id'].should == 'abc'
56
+ end
57
+ end
58
+
59
+ end
60
+
61
+ context "when the requested format is html" do
62
+ before(:each) do
63
+ request.stub(:format).and_return('text/html')
64
+ request.stub(:subdomain).and_return('pets')
65
+ end
66
+
67
+ context "when the params include an id that doesn't match the id in the subdomain" do
68
+ it "should redirect to the subdomain + id domain" do
69
+ request.stub(:subdomain).and_return('www')
70
+ request.stub(:protocol).and_return('https://')
71
+ request.stub(:port_string).and_return(':8080')
72
+ request.stub(:fullpath).and_return('/pets?e=123')
73
+ request.stub(:get?).and_return(true)
74
+
75
+
76
+ params = { 'pet_id' => 'efg' }
77
+ controller.stub(:params).and_return(params)
78
+
79
+ controller.should_receive(:redirect_to).with('https://pets.efg.peanuts.com:8080/pets?e=123')
80
+ request.stub(:subdomain).and_return('pets')
81
+ controller.subdomainbox :allowed => 'pets.%{pet_id}'
82
+
83
+ controller.should_receive(:redirect_to).with('https://pets.efg.peanuts.com:8080/pets?e=123')
84
+ request.stub(:subdomain).and_return('pets.abc')
85
+ controller.subdomainbox :allowed => 'pets.%{pet_id}'
86
+ end
87
+ end
88
+
89
+ context "when the origin subdomain is the specified subdomain" do
90
+ it "should not raise an exception or redirect" do
91
+ controller.should_not_receive(:redirect_to)
92
+ lambda {
93
+ controller.subdomainbox :allowed => 'pets'
94
+ }.should_not raise_error
95
+ end
96
+
97
+ context "when the origin subdomain includes an id" do
98
+ it "should not raise an exception or redirect" do
99
+ request.stub(:subdomain).and_return('pets.abc')
100
+ params = { 'pet_id' => 'abc' }
101
+ controller.stub(:params).and_return(params)
102
+ controller.should_not_receive(:redirect_to)
103
+ lambda {
104
+ controller.subdomainbox :allowed => 'pets.%{pet_id}'
105
+ }.should_not raise_error
106
+ end
107
+ end
108
+ end
109
+
110
+ context "when the origin subdomain is included in the list" do
111
+ it "should not raise an exception or redirect" do
112
+ controller.should_not_receive(:redirect_to)
113
+ lambda {
114
+ controller.subdomainbox :allowed => ['activities', 'pets']
115
+ }.should_not raise_error
116
+ end
117
+
118
+ context "when the origin subdomain includes an id" do
119
+ it "should not raise an exception or redirect" do
120
+ request.stub(:subdomain).and_return('petsabc')
121
+ params = { 'pet_id' => 'abc' }
122
+ controller.stub(:params).and_return(params)
123
+ controller.should_not_receive(:redirect_to)
124
+ lambda {
125
+ controller.subdomainbox :allowed => ['activities%{pet_id}', 'pets%{pet_id}']
126
+ }.should_not raise_error
127
+ end
128
+ end
129
+ end
130
+
131
+ context "when the origin subdomain is not the specified subdomain" do
132
+ before(:each) do
133
+ request.stub(:subdomain).and_return('www')
134
+ request.stub(:protocol).and_return('https://')
135
+ request.stub(:port_string).and_return(':8080')
136
+ request.stub(:fullpath).and_return('/pets?e=123')
137
+ end
138
+
139
+ context "when this is a GET request" do
140
+ before(:each) do
141
+ request.stub(:get?).and_return(true)
142
+ controller.stub(:redirect_to)
143
+ end
144
+
145
+ it "should 'forward' all flash notices so that they are not lost in the redirect" do
146
+
147
+ flash_now.should_receive(:[]).with(:alert).and_return('The alert flash')
148
+ flash_now.should_receive(:[]).with(:notice).and_return('The notice flash')
149
+ flash_now.should_receive(:[]).with(:info).and_return('The info flash')
150
+
151
+ flash.should_receive(:[]=).with(:alert, 'The alert flash')
152
+ flash.should_receive(:[]=).with(:notice, 'The notice flash')
153
+ flash.should_receive(:[]=).with(:info, 'The info flash')
154
+
155
+ controller.subdomainbox :allowed => 'pets'
156
+ end
157
+
158
+ it "should redirect to the same path (including http variables) at the specified subdomain prefixing the root of the origin domain" do
159
+ controller.should_receive(:redirect_to).with('https://pets.peanuts.com:8080/pets?e=123')
160
+ controller.subdomainbox :allowed => 'pets'
161
+ end
162
+
163
+ context "when the specified subdomain is an empty string" do
164
+ it "should redirect to the root domain" do
165
+ controller.should_receive(:redirect_to).with('https://peanuts.com:8080/pets?e=123')
166
+ controller.subdomainbox :allowed => ''
167
+ end
168
+ end
169
+
170
+ context "when the specified subdomain includes an id" do
171
+ it "the redirection subdomain should include the id" do
172
+ controller.should_receive(:redirect_to).with('https://pets.abc.peanuts.com:8080/pets?e=123')
173
+ params = { 'pet_id' => 'abc' }
174
+ controller.stub(:params).and_return(params)
175
+ controller.subdomainbox :allowed => 'pets.%{pet_id}'
176
+ end
177
+
178
+ context "when no id param matching the specified id name exists" do
179
+ it "the redirection subdomain should not include the id" do
180
+ controller.should_receive(:redirect_to).with('https://pets.peanuts.com:8080/pets?e=123')
181
+ params = { 'id' => 'abc' }
182
+ controller.stub(:params).and_return(params)
183
+ controller.subdomainbox :allowed => 'pets.%{pet_id}'
184
+ end
185
+ end
186
+ end
187
+
188
+ context "when no id is specified in the subdomainbox" do
189
+ it "the redirection subdomain should not include the id" do
190
+ controller.should_receive(:redirect_to).with('https://pets.peanuts.com:8080/pets?e=123')
191
+ params = { 'pet_id' => 'abc' }
192
+ controller.stub(:params).and_return(params)
193
+ controller.subdomainbox :allowed => 'pets'
194
+ end
195
+ end
196
+ end
197
+
198
+ context "when this is not a GET request" do
199
+ it "should raise SubdomainboxDomainViolation" do
200
+ request.stub(:get?).and_return(false)
201
+ lambda {
202
+ controller.subdomainbox :allowed => 'pets'
203
+ }.should raise_error(ActionController::Base::SubdomainboxDomainViolation)
204
+ end
205
+ end
206
+ end
207
+
208
+ context "when the origin subdomain is not in the list of approved subdomains" do
209
+ before(:each) do
210
+ request.stub(:subdomain).and_return('www')
211
+ request.stub(:protocol).and_return('https://')
212
+ request.stub(:port_string).and_return(':8080')
213
+ request.stub(:fullpath).and_return('/pets?e=123')
214
+ end
215
+
216
+
217
+
218
+ context "when this is a GET request" do
219
+ it "should redirect to the same path (http variables) at the first subdomain in the list prefixing the root of the origin domain" do
220
+ request.stub(:get?).and_return(true)
221
+ controller.should_receive(:redirect_to).with('https://activities.peanuts.com:8080/pets?e=123')
222
+ controller.subdomainbox :allowed => ['activities', 'pets']
223
+ end
224
+ end
225
+
226
+ context "when this is not a GET request" do
227
+ it "should raise SubdomainboxDomainViolation" do
228
+ request.stub(:get?).and_return(false)
229
+ lambda {
230
+ controller.subdomainbox :allowed => ['activities', 'pets']
231
+ }.should raise_error(ActionController::Base::SubdomainboxDomainViolation)
232
+ end
233
+ end
234
+ end
235
+
236
+
237
+ end
238
+
239
+
240
+ context "when the requested format is not html" do
241
+ before(:each) do
242
+ request.stub(:format).and_return('application/json')
243
+ end
244
+
245
+
246
+ context "when the origin subdomain is the specified subdomain" do
247
+ it "should not raise an exception" do
248
+ request.stub(:subdomain).and_return('pets')
249
+ lambda {
250
+ controller.subdomainbox :allowed => 'pets'
251
+ }.should_not raise_error
252
+ end
253
+
254
+ context "when the origin subdomain includes an id" do
255
+ it "should not raise an exception" do
256
+ request.stub(:subdomain).and_return('pets.abc')
257
+ lambda {
258
+ controller.subdomainbox :allowed => 'pets'
259
+ }.should_not raise_error
260
+ end
261
+ end
262
+ end
263
+
264
+
265
+ context "when the origin subdomain is in the specified subdomain list" do
266
+ it "should not raise an exception" do
267
+ request.stub(:subdomain).and_return('pets')
268
+ lambda {
269
+ controller.subdomainbox :allowed => ['activities', 'pets']
270
+ }.should_not raise_error
271
+ end
272
+
273
+ context "when the origin subdomain includes an id" do
274
+ it "should not raise an exception" do
275
+ request.stub(:subdomain).and_return('pets.abc')
276
+ lambda {
277
+ controller.subdomainbox :allowed => ['activities', 'pets']
278
+ }.should_not raise_error
279
+ end
280
+ end
281
+ end
282
+
283
+
284
+ context "when the origin subdomain is not the specified subdomain" do
285
+ it "should raise SubdomainboxDomainViolation" do
286
+ # recommend using around filter to rescue these exceptions and respond accordingly from that one place
287
+ request.stub(:subdomain).and_return('houses')
288
+ lambda {
289
+ controller.subdomainbox :allowed => 'pets'
290
+ }.should raise_error(ActionController::Base::SubdomainboxDomainViolation)
291
+ end
292
+
293
+ context "when the origin subdomain includes an id" do
294
+ it "should not raise an exception" do
295
+ request.stub(:subdomain).and_return('houses.abc')
296
+ lambda {
297
+ controller.subdomainbox :allowed => 'pets'
298
+ }.should raise_error
299
+ end
300
+ end
301
+ end
302
+
303
+ context "when the origin subdomain is not in the specified subdomain list" do
304
+ it "should raise SubdomainboxDomainViolation" do
305
+ request.stub(:subdomain).and_return('houses')
306
+ lambda {
307
+ controller.subdomainbox :allowed => ['activities', 'pets']
308
+ }.should raise_error(ActionController::Base::SubdomainboxDomainViolation)
309
+ end
310
+
311
+ context "when the origin subdomain includes an id" do
312
+ it "should raise an exception" do
313
+ request.stub(:subdomain).and_return('houses.abc')
314
+ lambda {
315
+ controller.subdomainbox :allowed => ['activities', 'pets']
316
+ }.should raise_error(ActionController::Base::SubdomainboxDomainViolation)
317
+ end
318
+ end
319
+ end
320
+
321
+ end
322
+
323
+ end
324
+
325
+
326
+ end
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: subdomainbox
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Daniel Nelson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-03-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: uuidtools
16
+ requirement: &2152124540 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *2152124540
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ requirement: &2152122180 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - =
31
+ - !ruby/object:Gem::Version
32
+ version: 2.10.0
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *2152122180
36
+ - !ruby/object:Gem::Dependency
37
+ name: jeweler
38
+ requirement: &2152121100 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 1.8.4
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *2152121100
47
+ - !ruby/object:Gem::Dependency
48
+ name: pry
49
+ requirement: &2152119700 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *2152119700
58
+ - !ruby/object:Gem::Dependency
59
+ name: pry-nav
60
+ requirement: &2152133520 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *2152133520
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry-stack_explorer
71
+ requirement: &2152131660 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *2152131660
80
+ description: use subdomains to prevent XSS from accessing your entire application
81
+ if it should happen to be injected into some page in your app
82
+ email: dnelson@centresource.com
83
+ executables: []
84
+ extensions: []
85
+ extra_rdoc_files:
86
+ - LICENSE.txt
87
+ - README.md
88
+ files:
89
+ - .document
90
+ - .rspec
91
+ - .rvmrc
92
+ - Gemfile
93
+ - Gemfile.lock
94
+ - LICENSE.txt
95
+ - README.md
96
+ - Rakefile
97
+ - VERSION
98
+ - lib/generators/subdomainbox_generator.rb
99
+ - lib/secure_xsrf_token.rb
100
+ - lib/subdomainbox.rb
101
+ - spec/secure_xsrf_token_spec.rb
102
+ - spec/spec_helper.rb
103
+ - spec/subdomainbox_spec.rb
104
+ homepage: http://github.com/populr/subdomainbox
105
+ licenses:
106
+ - MIT
107
+ post_install_message:
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ none: false
113
+ requirements:
114
+ - - ! '>='
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ segments:
118
+ - 0
119
+ hash: 1510684682428060724
120
+ required_rubygems_version: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ requirements: []
127
+ rubyforge_project:
128
+ rubygems_version: 1.8.10
129
+ signing_key:
130
+ specification_version: 3
131
+ summary: prevent XSS incursions from accessing entire application
132
+ test_files: []