secure_headers 1.1.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -8
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +8 -3
  6. data/Gemfile +6 -11
  7. data/README.md +219 -104
  8. data/Rakefile +11 -116
  9. data/fixtures/rails_3_2_12/Gemfile +0 -8
  10. data/fixtures/rails_3_2_12/app/controllers/things_controller.rb +0 -1
  11. data/fixtures/rails_3_2_12/app/views/layouts/application.html.erb +1 -4
  12. data/fixtures/rails_3_2_12/app/views/other_things/index.html.erb +2 -1
  13. data/fixtures/rails_3_2_12/app/views/things/index.html.erb +1 -21
  14. data/fixtures/rails_3_2_12/config/application.rb +0 -54
  15. data/fixtures/rails_3_2_12/config/environments/test.rb +2 -2
  16. data/fixtures/rails_3_2_12/config/initializers/secure_headers.rb +3 -1
  17. data/fixtures/rails_3_2_12/config/routes.rb +0 -57
  18. data/fixtures/rails_3_2_12/config/script_hashes.yml +5 -0
  19. data/fixtures/rails_3_2_12/config.ru +3 -0
  20. data/fixtures/rails_3_2_12/spec/controllers/other_things_controller_spec.rb +57 -19
  21. data/fixtures/rails_3_2_12/spec/controllers/things_controller_spec.rb +18 -13
  22. data/fixtures/rails_3_2_12/spec/spec_helper.rb +1 -5
  23. data/fixtures/rails_3_2_12_no_init/Gemfile +0 -9
  24. data/fixtures/rails_3_2_12_no_init/app/controllers/other_things_controller.rb +1 -2
  25. data/fixtures/rails_3_2_12_no_init/app/controllers/things_controller.rb +1 -1
  26. data/fixtures/rails_3_2_12_no_init/app/views/layouts/application.html.erb +0 -2
  27. data/fixtures/rails_3_2_12_no_init/app/views/things/index.html.erb +0 -21
  28. data/fixtures/rails_3_2_12_no_init/config/application.rb +0 -51
  29. data/fixtures/rails_3_2_12_no_init/config/environments/test.rb +2 -2
  30. data/fixtures/rails_3_2_12_no_init/config/routes.rb +0 -57
  31. data/fixtures/rails_3_2_12_no_init/spec/controllers/other_things_controller_spec.rb +17 -12
  32. data/fixtures/rails_3_2_12_no_init/spec/controllers/things_controller_spec.rb +17 -12
  33. data/fixtures/rails_3_2_12_no_init/spec/spec_helper.rb +3 -18
  34. data/fixtures/rails_4_1_8/Gemfile +5 -0
  35. data/fixtures/rails_4_1_8/README.rdoc +28 -0
  36. data/fixtures/rails_4_1_8/Rakefile +6 -0
  37. data/fixtures/rails_4_1_8/app/controllers/application_controller.rb +4 -0
  38. data/fixtures/rails_4_1_8/app/controllers/concerns/.keep +0 -0
  39. data/fixtures/rails_4_1_8/app/controllers/other_things_controller.rb +5 -0
  40. data/fixtures/rails_4_1_8/app/controllers/things_controller.rb +5 -0
  41. data/fixtures/rails_4_1_8/app/models/.keep +0 -0
  42. data/fixtures/rails_4_1_8/app/models/concerns/.keep +0 -0
  43. data/fixtures/rails_4_1_8/app/views/layouts/application.html.erb +11 -0
  44. data/fixtures/rails_4_1_8/app/views/other_things/index.html.erb +2 -0
  45. data/fixtures/rails_4_1_8/app/views/things/index.html.erb +1 -0
  46. data/fixtures/rails_4_1_8/config/application.rb +15 -0
  47. data/fixtures/rails_4_1_8/config/boot.rb +4 -0
  48. data/fixtures/rails_4_1_8/config/environment.rb +5 -0
  49. data/fixtures/rails_4_1_8/config/environments/test.rb +10 -0
  50. data/fixtures/rails_4_1_8/config/initializers/secure_headers.rb +17 -0
  51. data/fixtures/rails_4_1_8/config/routes.rb +4 -0
  52. data/fixtures/rails_4_1_8/config/script_hashes.yml +5 -0
  53. data/fixtures/rails_4_1_8/config/secrets.yml +22 -0
  54. data/fixtures/rails_4_1_8/config.ru +4 -0
  55. data/fixtures/rails_4_1_8/lib/assets/.keep +0 -0
  56. data/fixtures/rails_4_1_8/lib/tasks/.keep +0 -0
  57. data/fixtures/rails_4_1_8/log/.keep +0 -0
  58. data/fixtures/rails_4_1_8/spec/controllers/other_things_controller_spec.rb +83 -0
  59. data/fixtures/rails_4_1_8/spec/controllers/things_controller_spec.rb +59 -0
  60. data/fixtures/rails_4_1_8/spec/spec_helper.rb +15 -0
  61. data/fixtures/rails_4_1_8/vendor/assets/javascripts/.keep +0 -0
  62. data/fixtures/rails_4_1_8/vendor/assets/stylesheets/.keep +0 -0
  63. data/lib/secure_headers/hash_helper.rb +7 -0
  64. data/lib/secure_headers/headers/content_security_policy/script_hash_middleware.rb +22 -0
  65. data/lib/secure_headers/headers/content_security_policy.rb +160 -129
  66. data/lib/secure_headers/headers/public_key_pins.rb +95 -0
  67. data/lib/secure_headers/headers/strict_transport_security.rb +2 -1
  68. data/lib/secure_headers/headers/x_download_options.rb +39 -0
  69. data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +40 -0
  70. data/lib/secure_headers/headers/x_xss_protection.rb +2 -1
  71. data/lib/secure_headers/padrino.rb +14 -0
  72. data/lib/secure_headers/railtie.rb +19 -24
  73. data/lib/secure_headers/version.rb +1 -1
  74. data/lib/secure_headers/view_helper.rb +68 -0
  75. data/lib/secure_headers.rb +78 -16
  76. data/lib/tasks/tasks.rake +48 -0
  77. data/secure_headers.gemspec +4 -4
  78. data/spec/lib/secure_headers/headers/content_security_policy/script_hash_middleware_spec.rb +47 -0
  79. data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +127 -220
  80. data/spec/lib/secure_headers/headers/public_key_pins_spec.rb +37 -0
  81. data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +21 -20
  82. data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +12 -12
  83. data/spec/lib/secure_headers/headers/x_download_options_spec.rb +32 -0
  84. data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +12 -12
  85. data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +64 -0
  86. data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +20 -19
  87. data/spec/lib/secure_headers_spec.rb +115 -73
  88. data/spec/spec_helper.rb +30 -18
  89. metadata +62 -58
  90. data/.rvmrc +0 -1
  91. data/Guardfile +0 -10
  92. data/HISTORY.md +0 -115
  93. data/app/controllers/content_security_policy_controller.rb +0 -75
  94. data/config/curl-ca-bundle.crt +0 -5420
  95. data/config/routes.rb +0 -3
  96. data/fixtures/rails_3_2_12/Guardfile +0 -14
  97. data/fixtures/rails_3_2_12/app/models/thing.rb +0 -3
  98. data/fixtures/rails_3_2_12/config/database.yml +0 -25
  99. data/fixtures/rails_3_2_12/config/environments/development.rb +0 -37
  100. data/fixtures/rails_3_2_12/config/environments/production.rb +0 -67
  101. data/fixtures/rails_3_2_12/config/initializers/backtrace_silencers.rb +0 -7
  102. data/fixtures/rails_3_2_12/config/initializers/inflections.rb +0 -15
  103. data/fixtures/rails_3_2_12/config/initializers/mime_types.rb +0 -5
  104. data/fixtures/rails_3_2_12/config/initializers/secret_token.rb +0 -7
  105. data/fixtures/rails_3_2_12/config/initializers/session_store.rb +0 -8
  106. data/fixtures/rails_3_2_12/config/initializers/wrap_parameters.rb +0 -14
  107. data/fixtures/rails_3_2_12/config/locales/en.yml +0 -5
  108. data/fixtures/rails_3_2_12/db/schema.rb +0 -16
  109. data/fixtures/rails_3_2_12/db/seeds.rb +0 -7
  110. data/fixtures/rails_3_2_12_no_init/Guardfile +0 -14
  111. data/fixtures/rails_3_2_12_no_init/app/models/thing.rb +0 -3
  112. data/fixtures/rails_3_2_12_no_init/app/views/things/_form.html.erb +0 -17
  113. data/fixtures/rails_3_2_12_no_init/app/views/things/edit.html.erb +0 -6
  114. data/fixtures/rails_3_2_12_no_init/app/views/things/new.html.erb +0 -5
  115. data/fixtures/rails_3_2_12_no_init/app/views/things/show.html.erb +0 -5
  116. data/fixtures/rails_3_2_12_no_init/config/database.yml +0 -25
  117. data/fixtures/rails_3_2_12_no_init/config/environments/development.rb +0 -37
  118. data/fixtures/rails_3_2_12_no_init/config/environments/production.rb +0 -67
  119. data/fixtures/rails_3_2_12_no_init/config/initializers/backtrace_silencers.rb +0 -7
  120. data/fixtures/rails_3_2_12_no_init/config/initializers/inflections.rb +0 -15
  121. data/fixtures/rails_3_2_12_no_init/config/initializers/mime_types.rb +0 -5
  122. data/fixtures/rails_3_2_12_no_init/config/initializers/secret_token.rb +0 -7
  123. data/fixtures/rails_3_2_12_no_init/config/initializers/session_store.rb +0 -8
  124. data/fixtures/rails_3_2_12_no_init/config/initializers/wrap_parameters.rb +0 -14
  125. data/fixtures/rails_3_2_12_no_init/config/locales/en.yml +0 -5
  126. data/fixtures/rails_3_2_12_no_init/db/schema.rb +0 -16
  127. data/fixtures/rails_3_2_12_no_init/db/seeds.rb +0 -7
  128. data/spec/controllers/content_security_policy_controller_spec.rb +0 -90
@@ -1,33 +1,33 @@
1
1
  module SecureHeaders
2
2
  describe XContentTypeOptions do
3
- specify{ XContentTypeOptions.new.name.should == "X-Content-Type-Options" }
3
+ specify{ expect(XContentTypeOptions.new.name).to eq("X-Content-Type-Options") }
4
4
 
5
5
  describe "#value" do
6
- specify { XContentTypeOptions.new.value.should == XContentTypeOptions::Constants::DEFAULT_VALUE}
7
- specify { XContentTypeOptions.new("nosniff").value.should == "nosniff"}
8
- specify { XContentTypeOptions.new(:value => 'nosniff').value.should == "nosniff"}
6
+ specify { expect(XContentTypeOptions.new.value).to eq(XContentTypeOptions::Constants::DEFAULT_VALUE)}
7
+ specify { expect(XContentTypeOptions.new("nosniff").value).to eq("nosniff")}
8
+ specify { expect(XContentTypeOptions.new(:value => 'nosniff').value).to eq("nosniff")}
9
9
 
10
10
  context "invalid configuration values" do
11
11
  it "accepts nosniff" do
12
- lambda {
12
+ expect {
13
13
  XContentTypeOptions.new("nosniff")
14
- }.should_not raise_error
14
+ }.not_to raise_error
15
15
 
16
- lambda {
16
+ expect {
17
17
  XContentTypeOptions.new(:value => "nosniff")
18
- }.should_not raise_error
18
+ }.not_to raise_error
19
19
  end
20
20
 
21
21
  it "accepts nil" do
22
- lambda {
22
+ expect {
23
23
  XContentTypeOptions.new
24
- }.should_not raise_error
24
+ }.not_to raise_error
25
25
  end
26
26
 
27
27
  it "doesn't accept anything besides no-sniff" do
28
- lambda {
28
+ expect {
29
29
  XContentTypeOptions.new("donkey")
30
- }.should raise_error
30
+ }.to raise_error
31
31
  end
32
32
  end
33
33
  end
@@ -0,0 +1,32 @@
1
+ module SecureHeaders
2
+ describe XDownloadOptions do
3
+ specify { expect(XDownloadOptions.new.name).to eq(XDO_HEADER_NAME)}
4
+ specify { expect(XDownloadOptions.new.value).to eq("noopen")}
5
+ specify { expect(XDownloadOptions.new('noopen').value).to eq('noopen')}
6
+ specify { expect(XDownloadOptions.new(:value => 'noopen').value).to eq('noopen') }
7
+
8
+ context "invalid configuration values" do
9
+ it "accepts noopen" do
10
+ expect {
11
+ XDownloadOptions.new("noopen")
12
+ }.not_to raise_error
13
+
14
+ expect {
15
+ XDownloadOptions.new(:value => "noopen")
16
+ }.not_to raise_error
17
+ end
18
+
19
+ it "accepts nil" do
20
+ expect {
21
+ XDownloadOptions.new
22
+ }.not_to raise_error
23
+ end
24
+
25
+ it "doesn't accept anything besides noopen" do
26
+ expect {
27
+ XDownloadOptions.new("open")
28
+ }.to raise_error
29
+ end
30
+ end
31
+ end
32
+ end
@@ -2,34 +2,34 @@ require 'spec_helper'
2
2
 
3
3
  module SecureHeaders
4
4
  describe XFrameOptions do
5
- specify{ XFrameOptions.new.name.should == "X-Frame-Options" }
5
+ specify{ expect(XFrameOptions.new.name).to eq("X-Frame-Options") }
6
6
 
7
7
  describe "#value" do
8
- specify { XFrameOptions.new.value.should == XFrameOptions::Constants::DEFAULT_VALUE}
9
- specify { XFrameOptions.new("SAMEORIGIN").value.should == "SAMEORIGIN"}
10
- specify { XFrameOptions.new(:value => 'DENY').value.should == "DENY"}
8
+ specify { expect(XFrameOptions.new.value).to eq(XFrameOptions::Constants::DEFAULT_VALUE)}
9
+ specify { expect(XFrameOptions.new("SAMEORIGIN").value).to eq("SAMEORIGIN")}
10
+ specify { expect(XFrameOptions.new(:value => 'DENY').value).to eq("DENY")}
11
11
 
12
12
  context "with invalid configuration" do
13
13
  it "allows SAMEORIGIN" do
14
- lambda {
14
+ expect {
15
15
  XFrameOptions.new("SAMEORIGIN").value
16
- }.should_not raise_error
16
+ }.not_to raise_error
17
17
  end
18
18
 
19
19
  it "allows DENY" do
20
- lambda {
20
+ expect {
21
21
  XFrameOptions.new("DENY").value
22
- }.should_not raise_error end
22
+ }.not_to raise_error end
23
23
 
24
24
  it "allows ALLOW-FROM*" do
25
- lambda {
25
+ expect {
26
26
  XFrameOptions.new("ALLOW-FROM: example.com").value
27
- }.should_not raise_error
27
+ }.not_to raise_error
28
28
  end
29
29
  it "does not allow garbage" do
30
- lambda {
30
+ expect {
31
31
  XFrameOptions.new("I like turtles").value
32
- }.should raise_error(XFOBuildError)
32
+ }.to raise_error(XFOBuildError)
33
33
  end
34
34
  end
35
35
  end
@@ -0,0 +1,64 @@
1
+ module SecureHeaders
2
+ describe XPermittedCrossDomainPolicies do
3
+ specify { expect(XPermittedCrossDomainPolicies.new.name).to eq(XPermittedCrossDomainPolicies::Constants::XPCDP_HEADER_NAME)}
4
+ specify { expect(XPermittedCrossDomainPolicies.new.value).to eq("none")}
5
+ specify { expect(XPermittedCrossDomainPolicies.new('master-only').value).to eq('master-only')}
6
+ specify { expect(XPermittedCrossDomainPolicies.new(:value => 'master-only').value).to eq('master-only') }
7
+
8
+ context "valid configuration values" do
9
+ it "accepts 'all'" do
10
+ expect {
11
+ XPermittedCrossDomainPolicies.new("all")
12
+ }.not_to raise_error
13
+
14
+ expect {
15
+ XPermittedCrossDomainPolicies.new(:value => "all")
16
+ }.not_to raise_error
17
+ end
18
+
19
+ it "accepts 'by-ftp-filename'" do
20
+ expect {
21
+ XPermittedCrossDomainPolicies.new("by-ftp-filename")
22
+ }.not_to raise_error
23
+
24
+ expect {
25
+ XPermittedCrossDomainPolicies.new(:value => "by-ftp-filename")
26
+ }.not_to raise_error
27
+ end
28
+
29
+ it "accepts 'by-content-type'" do
30
+ expect {
31
+ XPermittedCrossDomainPolicies.new("by-content-type")
32
+ }.not_to raise_error
33
+
34
+ expect {
35
+ XPermittedCrossDomainPolicies.new(:value => "by-content-type")
36
+ }.not_to raise_error
37
+ end
38
+ it "accepts 'master-only'" do
39
+ expect {
40
+ XPermittedCrossDomainPolicies.new("master-only")
41
+ }.not_to raise_error
42
+
43
+ expect {
44
+ XPermittedCrossDomainPolicies.new(:value => "master-only")
45
+ }.not_to raise_error
46
+ end
47
+
48
+ it "accepts nil" do
49
+ expect {
50
+ XPermittedCrossDomainPolicies.new
51
+ }.not_to raise_error
52
+ end
53
+ end
54
+
55
+ context 'invlaid configuration values' do
56
+
57
+ it "doesn't accept invalid values" do
58
+ expect {
59
+ XPermittedCrossDomainPolicies.new("open")
60
+ }.to raise_error
61
+ end
62
+ end
63
+ end
64
+ end
@@ -1,53 +1,54 @@
1
1
  module SecureHeaders
2
2
  describe XXssProtection do
3
- specify { XXssProtection.new.name.should == X_XSS_PROTECTION_HEADER_NAME}
4
- specify { XXssProtection.new.value.should == "1"}
5
- specify { XXssProtection.new("0").value.should == "0"}
6
- specify { XXssProtection.new(:value => 1, :mode => 'block').value.should == '1; mode=block' }
3
+ specify { expect(XXssProtection.new.name).to eq(X_XSS_PROTECTION_HEADER_NAME)}
4
+ specify { expect(XXssProtection.new.value).to eq("1")}
5
+ specify { expect(XXssProtection.new("0").value).to eq("0")}
6
+ specify { expect(XXssProtection.new(:value => 1, :mode => 'block').value).to eq('1; mode=block') }
7
+ specify { expect(XXssProtection.new(:value => 1, :mode => 'block', :report_uri => 'https://www.secure.com/reports').value).to eq('1; mode=block; report=https://www.secure.com/reports') }
7
8
 
8
9
  context "with invalid configuration" do
9
10
  it "should raise an error when providing a string that is not valid" do
10
- lambda {
11
+ expect {
11
12
  XXssProtection.new("asdf")
12
- }.should raise_error(XXssProtectionBuildError)
13
+ }.to raise_error(XXssProtectionBuildError)
13
14
 
14
- lambda {
15
+ expect {
15
16
  XXssProtection.new("asdf; mode=donkey")
16
- }.should raise_error(XXssProtectionBuildError)
17
+ }.to raise_error(XXssProtectionBuildError)
17
18
  end
18
19
 
19
20
  context "when using a hash value" do
20
21
  it "should allow string values ('1' or '0' are the only valid strings)" do
21
- lambda {
22
+ expect {
22
23
  XXssProtection.new(:value => '1')
23
- }.should_not raise_error
24
+ }.not_to raise_error
24
25
  end
25
26
 
26
27
  it "should allow integer values (1 or 0 are the only valid integers)" do
27
- lambda {
28
+ expect {
28
29
  XXssProtection.new(:value => 1)
29
- }.should_not raise_error
30
+ }.not_to raise_error
30
31
  end
31
32
 
32
33
  it "should raise an error if no value key is supplied" do
33
- lambda {
34
+ expect {
34
35
  XXssProtection.new(:mode => 'block')
35
- }.should raise_error(XXssProtectionBuildError)
36
+ }.to raise_error(XXssProtectionBuildError)
36
37
  end
37
38
 
38
39
  it "should raise an error if an invalid key is supplied" do
39
- lambda {
40
+ expect {
40
41
  XXssProtection.new(:value => 123)
41
- }.should raise_error(XXssProtectionBuildError)
42
+ }.to raise_error(XXssProtectionBuildError)
42
43
  end
43
44
 
44
45
  it "should raise an error if mode != block" do
45
- lambda {
46
+ expect {
46
47
  XXssProtection.new(:value => 1, :mode => "donkey")
47
- }.should raise_error(XXssProtectionBuildError)
48
+ }.to raise_error(XXssProtectionBuildError)
48
49
  end
49
50
  end
50
51
 
51
52
  end
52
53
  end
53
- end
54
+ end
@@ -13,64 +13,37 @@ describe SecureHeaders do
13
13
 
14
14
  before(:each) do
15
15
  stub_user_agent(nil)
16
- headers.stub(:[])
17
- subject.stub(:response).and_return(response)
18
- subject.stub(:request).and_return(request)
19
- end
20
-
21
- ALL_HEADERS = Hash[[:hsts, :csp, :x_frame_options, :x_content_type_options, :x_xss_protection].map{|header| [header, false]}]
22
- USER_AGENTS = {
23
- :firefox => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1',
24
- :chrome => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.56 Safari/536.5',
25
- :ie => 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/5.0)',
26
- :opera => 'Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00',
27
- :ios5 => "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3",
28
- :ios6 => "Mozilla/5.0 (iPhone; CPU iPhone OS 614 like Mac OS X) AppleWebKit/536.26 (KHTML like Gecko) Version/6.0 Mobile/10B350 Safari/8536.25",
29
- :safari5 => "Mozilla/5.0 (iPad; CPU OS 5_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko ) Version/5.1 Mobile/9B176 Safari/7534.48.3",
30
- :safari5_1 => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/534.55.3 (KHTML, like Gecko) Version/5.1.3 Safari/534.53.10",
31
- :safari6 => "Mozilla/5.0 (Macintosh; Intel Mac OS X 1084) AppleWebKit/536.30.1 (KHTML like Gecko) Version/6.0.5 Safari/536.30.1"
32
- }
33
-
34
- def should_assign_header name, value
35
- response.headers.should_receive(:[]=).with(name, value)
36
- end
37
-
38
- def should_not_assign_header name
39
- response.headers.should_not_receive(:[]=).with(name, anything)
16
+ allow(headers).to receive(:[])
17
+ allow(subject).to receive(:response).and_return(response)
18
+ allow(subject).to receive(:request).and_return(request)
40
19
  end
41
20
 
42
21
  def stub_user_agent val
43
- request.stub_chain(:env, :[]).and_return(val)
44
- end
45
-
46
- def options_for header
47
- ALL_HEADERS.reject{|k,v| k == header}
22
+ allow(request).to receive_message_chain(:env, :[]).and_return(val)
48
23
  end
49
24
 
50
25
  def reset_config
51
26
  ::SecureHeaders::Configuration.configure do |config|
27
+ config.hpkp = nil
52
28
  config.hsts = nil
53
29
  config.x_frame_options = nil
54
30
  config.x_content_type_options = nil
55
31
  config.x_xss_protection = nil
56
32
  config.csp = nil
33
+ config.x_download_options = nil
34
+ config.x_permitted_cross_domain_policies = nil
57
35
  end
58
36
  end
59
37
 
60
38
  def set_security_headers(subject)
61
39
  subject.set_csp_header
40
+ subject.set_hpkp_header
62
41
  subject.set_hsts_header
63
42
  subject.set_x_frame_options_header
64
43
  subject.set_x_content_type_options_header
65
44
  subject.set_x_xss_protection_header
66
- end
67
-
68
- describe "#ensure_security_headers" do
69
- it "sets a before filter" do
70
- options = {}
71
- DummyClass.should_receive(:before_filter).exactly(5).times
72
- DummyClass.ensure_security_headers(options)
73
- end
45
+ subject.set_x_download_options_header
46
+ subject.set_x_permitted_cross_domain_policies_header
74
47
  end
75
48
 
76
49
  describe "#set_header" do
@@ -86,19 +59,19 @@ describe SecureHeaders do
86
59
  end
87
60
 
88
61
  describe "#set_security_headers" do
89
- before(:each) do
90
- SecureHeaders::ContentSecurityPolicy.stub(:new).and_return(double.as_null_object)
91
- end
92
62
  USER_AGENTS.each do |name, useragent|
93
63
  it "sets all default headers for #{name} (smoke test)" do
94
64
  stub_user_agent(useragent)
95
- number_of_headers = 5
96
- subject.should_receive(:set_header).exactly(number_of_headers).times # a request for a given header
65
+ number_of_headers = 7
66
+ expect(subject).to receive(:set_header).exactly(number_of_headers).times # a request for a given header
97
67
  subject.set_csp_header
98
68
  subject.set_x_frame_options_header
99
69
  subject.set_hsts_header
70
+ subject.set_hpkp_header
100
71
  subject.set_x_xss_protection_header
101
72
  subject.set_x_content_type_options_header
73
+ subject.set_x_download_options_header
74
+ subject.set_x_permitted_cross_domain_policies_header
102
75
  end
103
76
  end
104
77
 
@@ -113,38 +86,73 @@ describe SecureHeaders do
113
86
  subject.set_x_xss_protection_header(false)
114
87
  end
115
88
 
89
+ it "does not set the X-Download-Options header if disabled" do
90
+ should_not_assign_header(XDO_HEADER_NAME)
91
+ subject.set_x_download_options_header(false)
92
+ end
93
+
116
94
  it "does not set the X-Frame-Options header if disabled" do
117
95
  should_not_assign_header(XFO_HEADER_NAME)
118
96
  subject.set_x_frame_options_header(false)
119
97
  end
120
98
 
99
+ it "does not set the X-Permitted-Cross-Domain-Policies header if disabled" do
100
+ should_not_assign_header(XPCDP_HEADER_NAME)
101
+ subject.set_x_permitted_cross_domain_policies_header(false)
102
+ end
103
+
121
104
  it "does not set the HSTS header if disabled" do
122
105
  should_not_assign_header(HSTS_HEADER_NAME)
123
106
  subject.set_hsts_header(false)
124
107
  end
125
108
 
126
109
  it "does not set the HSTS header if request is over HTTP" do
127
- subject.stub_chain(:request, :ssl?).and_return(false)
110
+ allow(subject).to receive_message_chain(:request, :ssl?).and_return(false)
128
111
  should_not_assign_header(HSTS_HEADER_NAME)
129
112
  subject.set_hsts_header({:include_subdomains => true})
130
113
  end
131
114
 
115
+ it "does not set the HPKP header if disabled" do
116
+ should_not_assign_header(HPKP_HEADER_NAME)
117
+ subject.set_hpkp_header
118
+ end
119
+
120
+ it "does not set the HPKP header if request is over HTTP" do
121
+ allow(subject).to receive_message_chain(:request, :ssl?).and_return(false)
122
+ should_not_assign_header(HPKP_HEADER_NAME)
123
+ subject.set_hpkp_header(:max_age => 1234)
124
+ end
125
+
132
126
  it "does not set the CSP header if disabled" do
133
127
  stub_user_agent(USER_AGENTS[:chrome])
134
- should_not_assign_header(STANDARD_HEADER_NAME)
135
- subject.set_csp_header(options_for(:csp).merge(:csp => false))
128
+ should_not_assign_header(HEADER_NAME)
129
+ subject.set_csp_header(false)
130
+ end
131
+
132
+ it "saves the options to the env when using script hashes" do
133
+ opts = {
134
+ :default_src => 'self',
135
+ :script_hash_middleware => true
136
+ }
137
+ stub_user_agent(USER_AGENTS[:chrome])
138
+
139
+ expect(SecureHeaders::ContentSecurityPolicy).to receive(:add_to_env)
140
+ subject.set_csp_header(opts)
136
141
  end
137
142
 
138
143
  context "when disabled by configuration settings" do
139
144
  it "does not set any headers when disabled" do
140
145
  ::SecureHeaders::Configuration.configure do |config|
141
146
  config.hsts = false
147
+ config.hpkp = false
142
148
  config.x_frame_options = false
143
149
  config.x_content_type_options = false
144
150
  config.x_xss_protection = false
145
151
  config.csp = false
152
+ config.x_download_options = false
153
+ config.x_permitted_cross_domain_policies = false
146
154
  end
147
- subject.should_not_receive(:set_header)
155
+ expect(subject).not_to receive(:set_header)
148
156
  set_security_headers(subject)
149
157
  reset_config
150
158
  end
@@ -163,6 +171,18 @@ describe SecureHeaders do
163
171
  end
164
172
  end
165
173
 
174
+ describe "#set_x_download_options_header" do
175
+ it "sets the X-Download-Options header" do
176
+ should_assign_header(XDO_HEADER_NAME, SecureHeaders::XDownloadOptions::Constants::DEFAULT_VALUE)
177
+ subject.set_x_download_options_header
178
+ end
179
+
180
+ it "allows a custom X-Download-Options header" do
181
+ should_assign_header(XDO_HEADER_NAME, "noopen")
182
+ subject.set_x_download_options_header(:value => 'noopen')
183
+ end
184
+ end
185
+
166
186
  describe "#set_strict_transport_security" do
167
187
  it "sets the Strict-Transport-Security header" do
168
188
  should_assign_header(HSTS_HEADER_NAME, SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE)
@@ -178,6 +198,43 @@ describe SecureHeaders do
178
198
  should_assign_header(HSTS_HEADER_NAME, "max-age=#{HSTS_MAX_AGE}; includeSubdomains")
179
199
  subject.set_hsts_header(:max_age => HSTS_MAX_AGE, :include_subdomains => true)
180
200
  end
201
+
202
+ it "allows you to specify preload" do
203
+ should_assign_header(HSTS_HEADER_NAME, "max-age=#{HSTS_MAX_AGE}; includeSubdomains; preload")
204
+ subject.set_hsts_header(:max_age => HSTS_MAX_AGE, :include_subdomains => true, :preload => true)
205
+ end
206
+ end
207
+
208
+ describe "#set_public_key_pins" do
209
+ it "sets the Public-Key-Pins header" do
210
+ should_assign_header(HPKP_HEADER_NAME + "-Report-Only", "max-age=1234")
211
+ subject.set_hpkp_header(:max_age => 1234)
212
+ end
213
+
214
+ it "allows you to enforce public key pinning" do
215
+ should_assign_header(HPKP_HEADER_NAME, "max-age=1234")
216
+ subject.set_hpkp_header(:max_age => 1234, :enforce => true)
217
+ end
218
+
219
+ it "allows you to specific a custom max-age value" do
220
+ should_assign_header(HPKP_HEADER_NAME + "-Report-Only", 'max-age=1234')
221
+ subject.set_hpkp_header(:max_age => 1234)
222
+ end
223
+
224
+ it "allows you to specify includeSubdomains" do
225
+ should_assign_header(HPKP_HEADER_NAME, "max-age=1234; includeSubDomains")
226
+ subject.set_hpkp_header(:max_age => 1234, :include_subdomains => true, :enforce => true)
227
+ end
228
+
229
+ it "allows you to specify a report-uri" do
230
+ should_assign_header(HPKP_HEADER_NAME, "max-age=1234; report-uri=\"https://foobar.com\"")
231
+ subject.set_hpkp_header(:max_age => 1234, :report_uri => "https://foobar.com", :enforce => true)
232
+ end
233
+
234
+ it "allows you to specify a report-uri with app_name" do
235
+ should_assign_header(HPKP_HEADER_NAME, "max-age=1234; report-uri=\"https://foobar.com?enforce=true&app_name=my_app\"")
236
+ subject.set_hpkp_header(:max_age => 1234, :report_uri => "https://foobar.com", :app_name => "my_app", :tag_report_uri => true, :enforce => true)
237
+ end
181
238
  end
182
239
 
183
240
  describe "#set_x_xss_protection" do
@@ -221,7 +278,7 @@ describe SecureHeaders do
221
278
  context "when using Firefox" do
222
279
  it "sets CSP headers" do
223
280
  stub_user_agent(USER_AGENTS[:firefox])
224
- should_assign_header(STANDARD_HEADER_NAME + "-Report-Only", DEFAULT_CSP_HEADER)
281
+ should_assign_header(HEADER_NAME + "-Report-Only", DEFAULT_CSP_HEADER)
225
282
  subject.set_csp_header
226
283
  end
227
284
  end
@@ -229,7 +286,7 @@ describe SecureHeaders do
229
286
  context "when using Chrome" do
230
287
  it "sets default CSP header" do
231
288
  stub_user_agent(USER_AGENTS[:chrome])
232
- should_assign_header(STANDARD_HEADER_NAME + "-Report-Only", DEFAULT_CSP_HEADER)
289
+ should_assign_header(HEADER_NAME + "-Report-Only", DEFAULT_CSP_HEADER)
233
290
  subject.set_csp_header
234
291
  end
235
292
  end
@@ -237,36 +294,21 @@ describe SecureHeaders do
237
294
  context "when using a browser besides chrome/firefox" do
238
295
  it "sets the CSP header" do
239
296
  stub_user_agent(USER_AGENTS[:opera])
240
- should_assign_header(STANDARD_HEADER_NAME + "-Report-Only", DEFAULT_CSP_HEADER)
297
+ should_assign_header(HEADER_NAME + "-Report-Only", DEFAULT_CSP_HEADER)
241
298
  subject.set_csp_header
242
299
  end
243
300
  end
301
+ end
244
302
 
245
- context "when using the experimental key" do
246
- before(:each) do
247
- stub_user_agent(USER_AGENTS[:chrome])
248
- @opts = {
249
- :enforce => true,
250
- :default_src => 'self',
251
- :script_src => 'https://mycdn.example.com',
252
- :experimental => {
253
- :script_src => 'self',
254
- }
255
- }
256
- end
257
-
258
- it "does not set the header in enforce mode if experimental is supplied, but enforce is disabled" do
259
- opts = @opts.merge(:enforce => false)
260
- should_assign_header(STANDARD_HEADER_NAME + "-Report-Only", anything)
261
- should_not_assign_header(STANDARD_HEADER_NAME)
262
- subject.set_csp_header(opts)
263
- end
303
+ describe "#set_x_permitted_cross_domain_policies_header" do
304
+ it "sets the X-Permitted-Cross-Domain-Policies header" do
305
+ should_assign_header(XPCDP_HEADER_NAME, SecureHeaders::XPermittedCrossDomainPolicies::Constants::DEFAULT_VALUE)
306
+ subject.set_x_permitted_cross_domain_policies_header
307
+ end
264
308
 
265
- it "sets a header in enforce mode as well as report-only mode" do
266
- should_assign_header(STANDARD_HEADER_NAME, anything)
267
- should_assign_header(STANDARD_HEADER_NAME + "-Report-Only", anything)
268
- subject.set_csp_header(@opts)
269
- end
309
+ it "allows a custom X-Permitted-Cross-Domain-Policies header" do
310
+ should_assign_header(XPCDP_HEADER_NAME, "master-only")
311
+ subject.set_x_permitted_cross_domain_policies_header(:value => 'master-only')
270
312
  end
271
313
  end
272
314
  end
data/spec/spec_helper.rb CHANGED
@@ -1,25 +1,37 @@
1
1
  require 'rubygems'
2
- require 'spork'
2
+ require 'rspec'
3
3
 
4
- unless Spork.using_spork?
5
- require 'simplecov'
6
- SimpleCov.start do
7
- add_filter "spec"
8
- end
9
- end
4
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'secure_headers')
10
5
 
11
- Spork.prefork do
12
- require 'pry'
13
- require 'rspec'
6
+ if defined?(Coveralls)
7
+ Coveralls.wear!
14
8
  end
15
9
 
16
- Spork.each_run do
17
- require File.join(File.dirname(__FILE__), '..', 'lib', 'secure_headers')
18
- require File.join(File.dirname(__FILE__), '..', 'app', 'controllers', 'content_security_policy_controller')
19
- include ::SecureHeaders::StrictTransportSecurity::Constants
20
- include ::SecureHeaders::ContentSecurityPolicy::Constants
21
- include ::SecureHeaders::XFrameOptions::Constants
22
- include ::SecureHeaders::XXssProtection::Constants
23
- include ::SecureHeaders::XContentTypeOptions::Constants
10
+ include ::SecureHeaders::PublicKeyPins::Constants
11
+ include ::SecureHeaders::StrictTransportSecurity::Constants
12
+ include ::SecureHeaders::ContentSecurityPolicy::Constants
13
+ include ::SecureHeaders::XFrameOptions::Constants
14
+ include ::SecureHeaders::XXssProtection::Constants
15
+ include ::SecureHeaders::XContentTypeOptions::Constants
16
+ include ::SecureHeaders::XDownloadOptions::Constants
17
+ include ::SecureHeaders::XPermittedCrossDomainPolicies::Constants
18
+
19
+ USER_AGENTS = {
20
+ :firefox => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1',
21
+ :chrome => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.56 Safari/536.5',
22
+ :ie => 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/5.0)',
23
+ :opera => 'Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00',
24
+ :ios5 => "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3",
25
+ :ios6 => "Mozilla/5.0 (iPhone; CPU iPhone OS 614 like Mac OS X) AppleWebKit/536.26 (KHTML like Gecko) Version/6.0 Mobile/10B350 Safari/8536.25",
26
+ :safari5 => "Mozilla/5.0 (iPad; CPU OS 5_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko ) Version/5.1 Mobile/9B176 Safari/7534.48.3",
27
+ :safari5_1 => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/534.55.3 (KHTML, like Gecko) Version/5.1.3 Safari/534.53.10",
28
+ :safari6 => "Mozilla/5.0 (Macintosh; Intel Mac OS X 1084) AppleWebKit/536.30.1 (KHTML like Gecko) Version/6.0.5 Safari/536.30.1"
29
+ }
30
+
31
+ def should_assign_header name, value
32
+ expect(response.headers).to receive(:[]=).with(name, value)
24
33
  end
25
34
 
35
+ def should_not_assign_header name
36
+ expect(response.headers).not_to receive(:[]=).with(name, anything)
37
+ end