Skip to content

Commit cdc74c5

Browse files
committed
Merge branch 'master' into tests_for_csp_override_action
2 parents 1a9d2ea + 53b6db4 commit cdc74c5

File tree

7 files changed

+63
-13
lines changed

7 files changed

+63
-13
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ This gem makes a few assumptions about how you will use some features. For exam
4949
config.x_permitted_cross_domain_policies = 'none'
5050
config.csp = {
5151
:default_src => "https: self",
52+
:enforce => proc {|controller| contoller.current_user.enforce_csp? }
5253
:frame_src => "https: http:.twimg.com http://itunes.apple.com",
5354
:img_src => "https:",
5455
:report_uri => '//example.com/uri-directive'

fixtures/rails_3_2_12/spec/controllers/other_things_controller_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def request(opts = {})
1313
options = opts.merge(
1414
{
1515
'HTTPS' => 'on',
16-
'HTTP_USER_AGENT' => "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"
16+
'HTTP_USER_AGENT' => "Mozilla/5.0 (Macintosh; Intel Mac OS X 1084) AppleWebKit/537.22 (KHTML like Gecko) Chrome/25.0.1364.99 Safari/537.22"
1717
}
1818
)
1919

fixtures/rails_4_1_8/spec/controllers/other_things_controller_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def request(opts = {})
1313
options = opts.merge(
1414
{
1515
'HTTPS' => 'on',
16-
'HTTP_USER_AGENT' => "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"
16+
'HTTP_USER_AGENT' => "Mozilla/5.0 (Macintosh; Intel Mac OS X 1084) AppleWebKit/537.22 (KHTML like Gecko) Chrome/25.0.1364.99 Safari/537.22"
1717
}
1818
)
1919

lib/secure_headers/headers/content_security_policy.rb

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
require 'uri'
22
require 'base64'
33
require 'securerandom'
4+
require 'user_agent_parser'
45

56
module SecureHeaders
67
class ContentSecurityPolicyBuildError < StandardError; end
@@ -106,7 +107,7 @@ def initialize(config=nil, options={})
106107

107108
# Config values can be string, array, or lamdba values
108109
@config = config.inject({}) do |hash, (key, value)|
109-
config_val = value.respond_to?(:call) ? value.call : value
110+
config_val = value.respond_to?(:call) ? value.call(@controller) : value
110111

111112
if SOURCE_DIRECTIVES.include?(key) # directives need to be normalized to arrays of strings
112113
config_val = config_val.split if config_val.is_a? String
@@ -205,8 +206,12 @@ def translate_dir_value val
205206
elsif %{self none}.include?(val)
206207
"'#{val}'"
207208
elsif val == 'nonce'
208-
self.class.set_nonce(@controller, nonce)
209-
["'nonce-#{nonce}'", "'unsafe-inline'"]
209+
if supports_nonces?(@ua)
210+
self.class.set_nonce(@controller, nonce)
211+
["'nonce-#{nonce}'", "'unsafe-inline'"]
212+
else
213+
"'unsafe-inline'"
214+
end
210215
else
211216
val
212217
end
@@ -258,5 +263,10 @@ def non_default_directives
258263
def build_directive(key)
259264
"#{self.class.symbol_to_hyphen_case(key)} #{@config[key].join(" ")}; "
260265
end
266+
267+
def supports_nonces?(user_agent)
268+
parsed_ua = UserAgentParser.parse(user_agent)
269+
["Chrome", "Opera", "Firefox"].include?(parsed_ua.family)
270+
end
261271
end
262272
end

lib/secure_headers/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module SecureHeaders
2-
VERSION = "2.1.0"
2+
VERSION = "2.2.1"
33
end

secure_headers.gemspec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,6 @@ Gem::Specification.new do |gem|
1919
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
2020
gem.require_paths = ["lib"]
2121
gem.add_development_dependency "rake"
22+
gem.add_dependency "user_agent_parser"
23+
gem.post_install_message = "Warning: lambda config values will be broken until you add |controller|. e.g. :enforce => lambda { |controller| some_expression }"
2224
end

spec/lib/secure_headers/headers/content_security_policy_spec.rb

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ module SecureHeaders
1717
FIREFOX_23 = "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:23.0) Gecko/20131011 Firefox/23.0"
1818
CHROME = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4"
1919
CHROME_25 = "Mozilla/5.0 (Macintosh; Intel Mac OS X 1084) AppleWebKit/537.22 (KHTML like Gecko) Chrome/25.0.1364.99 Safari/537.22"
20-
20+
SAFARI = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A"
21+
OPERA = "Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16"
2122

2223
def request_for user_agent, request_uri=nil, options={:ssl => false}
2324
double(:ssl? => options[:ssl], :env => {'HTTP_USER_AGENT' => user_agent}, :url => (request_uri || 'http://areallylongdomainexample.com') )
@@ -76,7 +77,7 @@ def request_for user_agent, request_uri=nil, options={:ssl => false}
7677
end
7778

7879
it "adds a @enforce and @app_name variables to the report uri" do
79-
opts = @opts.merge(:tag_report_uri => true, :enforce => true, :app_name => lambda { 'twitter' })
80+
opts = @opts.merge(:tag_report_uri => true, :enforce => true, :app_name => proc { 'twitter' })
8081
csp = ContentSecurityPolicy.new(opts, :request => request_for(CHROME))
8182
expect(csp.value).to include("/csp_report?enforce=true&app_name=twitter")
8283
end
@@ -90,7 +91,7 @@ def request_for user_agent, request_uri=nil, options={:ssl => false}
9091
it "accepts procs for report-uris" do
9192
opts = {
9293
:default_src => 'self',
93-
:report_uri => lambda { "http://lambda/result" }
94+
:report_uri => proc { "http://lambda/result" }
9495
}
9596

9697
csp = ContentSecurityPolicy.new(opts)
@@ -99,15 +100,29 @@ def request_for user_agent, request_uri=nil, options={:ssl => false}
99100

100101
it "accepts procs for other fields" do
101102
opts = {
102-
:default_src => lambda { "http://lambda/result" },
103-
:enforce => lambda { true },
104-
:disable_fill_missing => lambda { true }
103+
:default_src => proc { "http://lambda/result" },
104+
:enforce => proc { true },
105+
:disable_fill_missing => proc { true }
105106
}
106107

107108
csp = ContentSecurityPolicy.new(opts)
108109
expect(csp.value).to eq("default-src http://lambda/result; img-src http://lambda/result data:;")
109110
expect(csp.name).to match("Content-Security-Policy")
110111
end
112+
113+
it "passes a reference to the controller to the proc" do
114+
controller = double
115+
user = double(:beta_testing? => true)
116+
117+
allow(controller).to receive(:current_user).and_return(user)
118+
opts = {
119+
:disable_fill_missing => true,
120+
:default_src => "self",
121+
:enforce => lambda { |c| c.current_user.beta_testing? }
122+
}
123+
csp = ContentSecurityPolicy.new(opts, :controller => controller)
124+
expect(csp.name).to match("Content-Security-Policy")
125+
end
111126
end
112127
end
113128

@@ -170,11 +185,33 @@ def request_for user_agent, request_uri=nil, options={:ssl => false}
170185
end
171186

172187
context "when using a nonce" do
173-
it "adds a nonce and unsafe-inline to the script-src value" do
188+
it "adds a nonce and unsafe-inline to the script-src value when using chrome" do
174189
header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "self nonce"), :request => request_for(CHROME))
175190
expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")
176191
end
177192

193+
it "adds a nonce and unsafe-inline to the script-src value when using firefox" do
194+
header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "self nonce"), :request => request_for(FIREFOX))
195+
expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")
196+
end
197+
198+
it "adds a nonce and unsafe-inline to the script-src value when using opera" do
199+
header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "self nonce"), :request => request_for(OPERA))
200+
expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")
201+
end
202+
203+
it "does not add a nonce and unsafe-inline to the script-src value when using Safari" do
204+
header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "self nonce"), :request => request_for(SAFARI))
205+
expect(header.value).to include("script-src 'self' 'unsafe-inline'")
206+
expect(header.value).not_to include("nonce")
207+
end
208+
209+
it "does not add a nonce and unsafe-inline to the script-src value when using IE" do
210+
header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "self nonce"), :request => request_for(IE))
211+
expect(header.value).to include("script-src 'self' 'unsafe-inline'")
212+
expect(header.value).not_to include("nonce")
213+
end
214+
178215
it "adds a nonce and unsafe-inline to the style-src value" do
179216
header = ContentSecurityPolicy.new(default_opts.merge(:style_src => "self nonce"), :request => request_for(CHROME))
180217
expect(header.value).to include("style-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")

0 commit comments

Comments
 (0)