subdomainbox 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/.rvmrc +1 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +49 -0
- data/LICENSE.txt +20 -0
- data/README.md +62 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/lib/generators/subdomainbox_generator.rb +7 -0
- data/lib/secure_xsrf_token.rb +21 -0
- data/lib/subdomainbox.rb +81 -0
- data/spec/secure_xsrf_token_spec.rb +44 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/subdomainbox_spec.rb +326 -0
- metadata +132 -0
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use 1.9.3@subdomainbox
|
data/Gemfile
ADDED
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,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
|
data/lib/subdomainbox.rb
ADDED
@@ -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
|
data/spec/spec_helper.rb
ADDED
@@ -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: []
|