secure_headers 2.5.3 → 3.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of secure_headers might be problematic. Click here for more details.

Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.travis.yml +2 -1
  4. data/Gemfile +9 -16
  5. data/README.md +154 -331
  6. data/Rakefile +2 -36
  7. data/lib/secure_headers/configuration.rb +189 -0
  8. data/lib/secure_headers/headers/content_security_policy.rb +341 -254
  9. data/lib/secure_headers/headers/public_key_pins.rb +43 -58
  10. data/lib/secure_headers/headers/strict_transport_security.rb +21 -49
  11. data/lib/secure_headers/headers/x_content_type_options.rb +18 -33
  12. data/lib/secure_headers/headers/x_download_options.rb +18 -33
  13. data/lib/secure_headers/headers/x_frame_options.rb +24 -34
  14. data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +19 -34
  15. data/lib/secure_headers/headers/x_xss_protection.rb +17 -48
  16. data/lib/secure_headers/middleware.rb +15 -0
  17. data/lib/secure_headers/padrino.rb +1 -2
  18. data/lib/secure_headers/railtie.rb +9 -6
  19. data/lib/secure_headers/view_helper.rb +27 -43
  20. data/lib/secure_headers.rb +254 -61
  21. data/secure_headers.gemspec +7 -12
  22. data/spec/lib/secure_headers/configuration_spec.rb +80 -0
  23. data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +111 -276
  24. data/spec/lib/secure_headers/headers/public_key_pins_spec.rb +17 -17
  25. data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +11 -43
  26. data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +11 -18
  27. data/spec/lib/secure_headers/headers/x_download_options_spec.rb +13 -17
  28. data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +15 -17
  29. data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +22 -39
  30. data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +20 -30
  31. data/spec/lib/secure_headers/middleware_spec.rb +40 -0
  32. data/spec/lib/secure_headers_spec.rb +201 -339
  33. data/spec/spec_helper.rb +30 -30
  34. data/upgrading-to-3-0.md +35 -0
  35. metadata +14 -100
  36. data/fixtures/rails_3_2_22/.rspec +0 -1
  37. data/fixtures/rails_3_2_22/Gemfile +0 -6
  38. data/fixtures/rails_3_2_22/README.rdoc +0 -261
  39. data/fixtures/rails_3_2_22/Rakefile +0 -7
  40. data/fixtures/rails_3_2_22/app/controllers/application_controller.rb +0 -4
  41. data/fixtures/rails_3_2_22/app/controllers/other_things_controller.rb +0 -5
  42. data/fixtures/rails_3_2_22/app/controllers/things_controller.rb +0 -5
  43. data/fixtures/rails_3_2_22/app/models/.gitkeep +0 -0
  44. data/fixtures/rails_3_2_22/app/views/layouts/application.html.erb +0 -11
  45. data/fixtures/rails_3_2_22/app/views/other_things/index.html.erb +0 -2
  46. data/fixtures/rails_3_2_22/app/views/things/index.html.erb +0 -1
  47. data/fixtures/rails_3_2_22/config/application.rb +0 -14
  48. data/fixtures/rails_3_2_22/config/boot.rb +0 -6
  49. data/fixtures/rails_3_2_22/config/environment.rb +0 -5
  50. data/fixtures/rails_3_2_22/config/environments/test.rb +0 -37
  51. data/fixtures/rails_3_2_22/config/initializers/secure_headers.rb +0 -16
  52. data/fixtures/rails_3_2_22/config/routes.rb +0 -4
  53. data/fixtures/rails_3_2_22/config/script_hashes.yml +0 -5
  54. data/fixtures/rails_3_2_22/config.ru +0 -7
  55. data/fixtures/rails_3_2_22/lib/assets/.gitkeep +0 -0
  56. data/fixtures/rails_3_2_22/lib/tasks/.gitkeep +0 -0
  57. data/fixtures/rails_3_2_22/log/.gitkeep +0 -0
  58. data/fixtures/rails_3_2_22/spec/controllers/other_things_controller_spec.rb +0 -83
  59. data/fixtures/rails_3_2_22/spec/controllers/things_controller_spec.rb +0 -54
  60. data/fixtures/rails_3_2_22/spec/spec_helper.rb +0 -15
  61. data/fixtures/rails_3_2_22/vendor/assets/javascripts/.gitkeep +0 -0
  62. data/fixtures/rails_3_2_22/vendor/assets/stylesheets/.gitkeep +0 -0
  63. data/fixtures/rails_3_2_22/vendor/plugins/.gitkeep +0 -0
  64. data/fixtures/rails_3_2_22_no_init/.rspec +0 -1
  65. data/fixtures/rails_3_2_22_no_init/Gemfile +0 -6
  66. data/fixtures/rails_3_2_22_no_init/README.rdoc +0 -261
  67. data/fixtures/rails_3_2_22_no_init/Rakefile +0 -7
  68. data/fixtures/rails_3_2_22_no_init/app/controllers/application_controller.rb +0 -4
  69. data/fixtures/rails_3_2_22_no_init/app/controllers/other_things_controller.rb +0 -20
  70. data/fixtures/rails_3_2_22_no_init/app/controllers/things_controller.rb +0 -5
  71. data/fixtures/rails_3_2_22_no_init/app/models/.gitkeep +0 -0
  72. data/fixtures/rails_3_2_22_no_init/app/views/layouts/application.html.erb +0 -12
  73. data/fixtures/rails_3_2_22_no_init/app/views/other_things/index.html.erb +0 -1
  74. data/fixtures/rails_3_2_22_no_init/app/views/things/index.html.erb +0 -0
  75. data/fixtures/rails_3_2_22_no_init/config/application.rb +0 -17
  76. data/fixtures/rails_3_2_22_no_init/config/boot.rb +0 -6
  77. data/fixtures/rails_3_2_22_no_init/config/environment.rb +0 -5
  78. data/fixtures/rails_3_2_22_no_init/config/environments/test.rb +0 -37
  79. data/fixtures/rails_3_2_22_no_init/config/routes.rb +0 -4
  80. data/fixtures/rails_3_2_22_no_init/config.ru +0 -4
  81. data/fixtures/rails_3_2_22_no_init/lib/assets/.gitkeep +0 -0
  82. data/fixtures/rails_3_2_22_no_init/lib/tasks/.gitkeep +0 -0
  83. data/fixtures/rails_3_2_22_no_init/log/.gitkeep +0 -0
  84. data/fixtures/rails_3_2_22_no_init/spec/controllers/other_things_controller_spec.rb +0 -56
  85. data/fixtures/rails_3_2_22_no_init/spec/controllers/things_controller_spec.rb +0 -54
  86. data/fixtures/rails_3_2_22_no_init/spec/spec_helper.rb +0 -5
  87. data/fixtures/rails_3_2_22_no_init/vendor/assets/javascripts/.gitkeep +0 -0
  88. data/fixtures/rails_3_2_22_no_init/vendor/assets/stylesheets/.gitkeep +0 -0
  89. data/fixtures/rails_3_2_22_no_init/vendor/plugins/.gitkeep +0 -0
  90. data/fixtures/rails_4_1_8/Gemfile +0 -5
  91. data/fixtures/rails_4_1_8/README.rdoc +0 -28
  92. data/fixtures/rails_4_1_8/Rakefile +0 -6
  93. data/fixtures/rails_4_1_8/app/controllers/application_controller.rb +0 -4
  94. data/fixtures/rails_4_1_8/app/controllers/concerns/.keep +0 -0
  95. data/fixtures/rails_4_1_8/app/controllers/other_things_controller.rb +0 -5
  96. data/fixtures/rails_4_1_8/app/controllers/things_controller.rb +0 -5
  97. data/fixtures/rails_4_1_8/app/models/.keep +0 -0
  98. data/fixtures/rails_4_1_8/app/models/concerns/.keep +0 -0
  99. data/fixtures/rails_4_1_8/app/views/layouts/application.html.erb +0 -11
  100. data/fixtures/rails_4_1_8/app/views/other_things/index.html.erb +0 -2
  101. data/fixtures/rails_4_1_8/app/views/things/index.html.erb +0 -1
  102. data/fixtures/rails_4_1_8/config/application.rb +0 -15
  103. data/fixtures/rails_4_1_8/config/boot.rb +0 -4
  104. data/fixtures/rails_4_1_8/config/environment.rb +0 -5
  105. data/fixtures/rails_4_1_8/config/environments/test.rb +0 -10
  106. data/fixtures/rails_4_1_8/config/initializers/secure_headers.rb +0 -16
  107. data/fixtures/rails_4_1_8/config/routes.rb +0 -4
  108. data/fixtures/rails_4_1_8/config/script_hashes.yml +0 -5
  109. data/fixtures/rails_4_1_8/config/secrets.yml +0 -22
  110. data/fixtures/rails_4_1_8/config.ru +0 -4
  111. data/fixtures/rails_4_1_8/lib/assets/.keep +0 -0
  112. data/fixtures/rails_4_1_8/lib/tasks/.keep +0 -0
  113. data/fixtures/rails_4_1_8/log/.keep +0 -0
  114. data/fixtures/rails_4_1_8/spec/controllers/other_things_controller_spec.rb +0 -83
  115. data/fixtures/rails_4_1_8/spec/controllers/things_controller_spec.rb +0 -59
  116. data/fixtures/rails_4_1_8/spec/spec_helper.rb +0 -15
  117. data/fixtures/rails_4_1_8/vendor/assets/javascripts/.keep +0 -0
  118. data/fixtures/rails_4_1_8/vendor/assets/stylesheets/.keep +0 -0
  119. data/lib/secure_headers/controller_extension.rb +0 -158
  120. data/lib/secure_headers/hash_helper.rb +0 -7
  121. data/lib/secure_headers/header.rb +0 -5
  122. data/lib/secure_headers/headers/content_security_policy/script_hash_middleware.rb +0 -22
  123. data/lib/secure_headers/version.rb +0 -3
  124. data/lib/tasks/tasks.rake +0 -48
  125. data/spec/lib/secure_headers/headers/content_security_policy/script_hash_middleware_spec.rb +0 -46
@@ -1,61 +1,45 @@
1
1
  module SecureHeaders
2
- class UnexpectedHashedScriptException < StandardError
3
-
4
- end
5
-
6
2
  module ViewHelpers
7
- include SecureHeaders::HashHelper
8
- SECURE_HEADERS_RAKE_TASK = "rake secure_headers:generate_hashes"
9
-
10
- def nonced_style_tag(content = nil, &block)
11
- nonced_tag(content, :style, block)
3
+ # Public: create a style tag using the content security policy nonce.
4
+ # Instructs secure_headers to append a nonce to style/script-src directives.
5
+ #
6
+ # Returns an html-safe style tag with the nonce attribute.
7
+ def nonced_style_tag(content_or_options = nil, &block)
8
+ nonced_tag(:style, content_or_options, block)
12
9
  end
13
10
 
14
- def nonced_javascript_tag(content = nil, &block)
15
- nonced_tag(content, :script, block)
11
+ # Public: create a script tag using the content security policy nonce.
12
+ # Instructs secure_headers to append a nonce to style/script-src directives.
13
+ #
14
+ # Returns an html-safe script tag with the nonce attribute.
15
+ def nonced_javascript_tag(content_or_options = nil, &block)
16
+ nonced_tag(:script, content_or_options, block)
16
17
  end
17
18
 
18
- def hashed_javascript_tag(raise_error_on_unrecognized_hash = false, &block)
19
- content = capture(&block)
20
-
21
- if ['development', 'test'].include?(ENV["RAILS_ENV"])
22
- hash_value = hash_source(content)
23
- file_path = File.join('app', 'views', self.instance_variable_get(:@virtual_path) + '.html.erb')
24
- script_hashes = controller.instance_variable_get(:@script_hashes)[file_path]
25
- unless script_hashes && script_hashes.include?(hash_value)
26
- message = unexpected_hash_error_message(file_path, hash_value, content)
27
- if raise_error_on_unrecognized_hash
28
- raise UnexpectedHashedScriptException.new(message)
29
- else
30
- request.env[HASHES_ENV_KEY] = (request.env[HASHES_ENV_KEY] || []) << hash_value
31
- end
32
- end
19
+ # Public: use the content security policy nonce for this request directly.
20
+ # Instructs secure_headers to append a nonce to style/script-src directives.
21
+ #
22
+ # Returns a non-html-safe nonce value.
23
+ def content_security_policy_nonce(type)
24
+ case type
25
+ when :script
26
+ SecureHeaders.content_security_policy_script_nonce(@_request)
27
+ when :style
28
+ SecureHeaders.content_security_policy_style_nonce(@_request)
33
29
  end
34
-
35
- content_tag :script, content
36
30
  end
37
31
 
38
32
  private
39
33
 
40
- def nonced_tag(content, type, block)
34
+ def nonced_tag(type, content_or_options, block)
35
+ options = {}
41
36
  content = if block
37
+ options = content_or_options
42
38
  capture(&block)
43
39
  else
44
- content.html_safe # :'(
40
+ content_or_options.html_safe # :'(
45
41
  end
46
-
47
- content_tag type, content, :nonce => @content_security_policy_nonce
48
- end
49
-
50
- def unexpected_hash_error_message(file_path, hash_value, content)
51
- <<-EOF
52
- \n\n*** WARNING: Unrecognized hash in #{file_path}!!! Value: #{hash_value} ***
53
- <script>#{content}</script>
54
- *** This is fine in dev/test, but will raise exceptions in production. ***
55
- *** Run #{SECURE_HEADERS_RAKE_TASK} or add the following to config/script_hashes.yml:***
56
- #{file_path}:
57
- - #{hash_value}\n\n
58
- EOF
42
+ content_tag type, content, options.merge(nonce: content_security_policy_nonce(type))
59
43
  end
60
44
  end
61
45
  end
@@ -1,5 +1,4 @@
1
- require "secure_headers/version"
2
- require "secure_headers/header"
1
+ require "secure_headers/configuration"
3
2
  require "secure_headers/headers/public_key_pins"
4
3
  require "secure_headers/headers/content_security_policy"
5
4
  require "secure_headers/headers/x_frame_options"
@@ -8,86 +7,280 @@ require "secure_headers/headers/x_xss_protection"
8
7
  require "secure_headers/headers/x_content_type_options"
9
8
  require "secure_headers/headers/x_download_options"
10
9
  require "secure_headers/headers/x_permitted_cross_domain_policies"
11
- require "secure_headers/controller_extension"
10
+ require "secure_headers/middleware"
12
11
  require "secure_headers/railtie"
13
- require "secure_headers/hash_helper"
14
12
  require "secure_headers/view_helper"
13
+ require "useragent"
15
14
 
15
+ # All headers (except for hpkp) have a default value. Provide SecureHeaders::OPT_OUT
16
+ # or ":optout_of_protection" as a config value to disable a given header
16
17
  module SecureHeaders
17
- SCRIPT_HASH_CONFIG_FILE = 'config/script_hashes.yml'
18
- HASHES_ENV_KEY = 'secure_headers.script_hashes'
18
+ OPT_OUT = :opt_out_of_protection
19
+ SECURE_HEADERS_CONFIG = "secure_headers_request_config".freeze
20
+ NONCE_KEY = "secure_headers_content_security_policy_nonce".freeze
21
+ HTTPS = "https".freeze
22
+ CSP = ContentSecurityPolicy
19
23
 
20
24
  ALL_HEADER_CLASSES = [
21
- SecureHeaders::ContentSecurityPolicy,
22
- SecureHeaders::StrictTransportSecurity,
23
- SecureHeaders::PublicKeyPins,
24
- SecureHeaders::XContentTypeOptions,
25
- SecureHeaders::XDownloadOptions,
26
- SecureHeaders::XFrameOptions,
27
- SecureHeaders::XPermittedCrossDomainPolicies,
28
- SecureHeaders::XXssProtection
29
- ]
30
-
31
- ALL_FILTER_METHODS = [
32
- :prep_script_hash,
33
- :set_hsts_header,
34
- :set_hpkp_header,
35
- :set_x_frame_options_header,
36
- :set_csp_header,
37
- :set_x_xss_protection_header,
38
- :set_x_content_type_options_header,
39
- :set_x_download_options_header,
40
- :set_x_permitted_cross_domain_policies_header
41
- ]
42
-
43
- module Configuration
44
- class << self
45
- attr_accessor :hsts, :x_frame_options, :x_content_type_options,
46
- :x_xss_protection, :csp, :x_download_options, :script_hashes,
47
- :x_permitted_cross_domain_policies, :hpkp
48
-
49
- # For preparation for the secure_headers 3.x change.
50
- def default &block
51
- instance_eval &block
52
- if File.exists?(SCRIPT_HASH_CONFIG_FILE)
53
- ::SecureHeaders::Configuration.script_hashes = YAML.load(File.open(SCRIPT_HASH_CONFIG_FILE))
54
- end
25
+ ContentSecurityPolicy,
26
+ StrictTransportSecurity,
27
+ PublicKeyPins,
28
+ XContentTypeOptions,
29
+ XDownloadOptions,
30
+ XFrameOptions,
31
+ XPermittedCrossDomainPolicies,
32
+ XXssProtection
33
+ ].freeze
34
+
35
+ ALL_HEADERS_BESIDES_CSP = (ALL_HEADER_CLASSES - [CSP]).freeze
36
+
37
+ # Headers set on http requests (excludes STS and HPKP)
38
+ HTTP_HEADER_CLASSES =
39
+ (ALL_HEADER_CLASSES - [StrictTransportSecurity, PublicKeyPins]).freeze
40
+
41
+ class << self
42
+ # Public: override a given set of directives for the current request. If a
43
+ # value already exists for a given directive, it will be overridden.
44
+ #
45
+ # If CSP was previously OPT_OUT, a new blank policy is used.
46
+ #
47
+ # additions - a hash containing directives. e.g.
48
+ # script_src: %w(another-host.com)
49
+ def override_content_security_policy_directives(request, additions)
50
+ config = config_for(request).dup
51
+ if config.csp == OPT_OUT
52
+ config.csp = {}
53
+ end
54
+ config.csp.merge!(additions)
55
+ override_secure_headers_request_config(request, config)
56
+ end
57
+
58
+ # Public: appends source values to the current configuration. If no value
59
+ # is set for a given directive, the value will be merged with the default-src
60
+ # value. If a value exists for the given directive, the values will be combined.
61
+ #
62
+ # additions - a hash containing directives. e.g.
63
+ # script_src: %w(another-host.com)
64
+ def append_content_security_policy_directives(request, additions)
65
+ config = config_for(request).dup
66
+ config.csp = CSP.combine_policies(config.csp, additions)
67
+ override_secure_headers_request_config(request, config)
68
+ end
69
+
70
+ # Public: override X-Frame-Options settings for this request.
71
+ #
72
+ # value - deny, sameorigin, or allowall
73
+ #
74
+ # Returns the current config
75
+ def override_x_frame_options(request, value)
76
+ default_config = config_for(request).dup
77
+ default_config.x_frame_options = value
78
+ override_secure_headers_request_config(request, default_config)
79
+ end
80
+
81
+ # Public: opts out of setting a given header by creating a temporary config
82
+ # and setting the given headers config to OPT_OUT.
83
+ def opt_out_of_header(request, header_key)
84
+ config = config_for(request).dup
85
+ config.send("#{header_key}=", OPT_OUT)
86
+ override_secure_headers_request_config(request, config)
87
+ end
88
+
89
+ # Public: opts out of setting all headers by telling secure_headers to use
90
+ # the NOOP configuration.
91
+ def opt_out_of_all_protection(request)
92
+ use_secure_headers_override(request, Configuration::NOOP_CONFIGURATION)
93
+ end
94
+
95
+ # Public: Builds the hash of headers that should be applied base on the
96
+ # request.
97
+ #
98
+ # StrictTransportSecurity and PublicKeyPins are not applied to http requests.
99
+ # See #config_for to determine which config is used for a given request.
100
+ #
101
+ # Returns a hash of header names => header values. The value
102
+ # returned is meant to be merged into the header value from `@app.call(env)`
103
+ # in Rack middleware.
104
+ def header_hash_for(request)
105
+ config = config_for(request)
106
+
107
+ headers = if cached_headers = config.cached_headers
108
+ use_cached_headers(cached_headers, request)
109
+ else
110
+ build_headers(config, request)
55
111
  end
56
112
 
57
- def configure &block
58
- warn "[DEPRECATION] `configure` is removed in secure_headers 3.x. Instead use `default`."
59
- default &block
113
+ headers
114
+ end
115
+
116
+ # Public: specify which named override will be used for this request.
117
+ # Raises an argument error if no named override exists.
118
+ #
119
+ # name - the name of the previously configured override.
120
+ def use_secure_headers_override(request, name)
121
+ if config = Configuration.get(name)
122
+ override_secure_headers_request_config(request, config)
123
+ else
124
+ raise ArgumentError.new("no override by the name of #{name} has been configured")
60
125
  end
61
126
  end
62
- end
63
127
 
64
- class << self
65
- def append_features(base)
66
- base.send(:include, ControllerExtension)
128
+ # Public: gets or creates a nonce for CSP.
129
+ #
130
+ # The nonce will be added to script_src
131
+ #
132
+ # Returns the nonce
133
+ def content_security_policy_script_nonce(request)
134
+ content_security_policy_nonce(request, CSP::SCRIPT_SRC)
135
+ end
136
+
137
+ # Public: gets or creates a nonce for CSP.
138
+ #
139
+ # The nonce will be added to style_src
140
+ #
141
+ # Returns the nonce
142
+ def content_security_policy_style_nonce(request)
143
+ content_security_policy_nonce(request, CSP::STYLE_SRC)
144
+ end
145
+
146
+ private
147
+
148
+ # Private: gets or creates a nonce for CSP.
149
+ #
150
+ # Returns the nonce
151
+ def content_security_policy_nonce(request, script_or_style)
152
+ request.env[NONCE_KEY] ||= SecureRandom.base64(32).chomp
153
+ nonce_key = script_or_style == CSP::SCRIPT_SRC ? :script_nonce : :style_nonce
154
+ append_content_security_policy_directives(request, nonce_key => request.env[NONCE_KEY])
155
+ request.env[NONCE_KEY]
156
+ end
157
+
158
+ # Private: convenience method for specifying which configuration object should
159
+ # be used for this request.
160
+ #
161
+ # Returns the config.
162
+ def override_secure_headers_request_config(request, config)
163
+ request.env[SECURE_HEADERS_CONFIG] = config
67
164
  end
68
165
 
69
- def header_hash(options = nil)
70
- ALL_HEADER_CLASSES.inject({}) do |memo, klass|
71
- # must use !options[key].nil? because 'false' represents opting out, nil
72
- # represents use global default.
73
- config = if options.is_a?(Hash) && !options[klass::Constants::CONFIG_KEY].nil?
74
- options[klass::Constants::CONFIG_KEY]
166
+ # Private: determines which headers are applicable to a given request.
167
+ #
168
+ # Returns a list of classes whose corresponding header values are valid for
169
+ # this request.
170
+ def header_classes_for(request)
171
+ if request.scheme == HTTPS
172
+ ALL_HEADER_CLASSES
173
+ else
174
+ HTTP_HEADER_CLASSES
175
+ end
176
+ end
177
+
178
+ # Private: do the heavy lifting of converting a configuration object
179
+ # to a hash of headers valid for this request.
180
+ #
181
+ # Returns a hash of header names / values.
182
+ def build_headers(config, request)
183
+ header_classes_for(request).each_with_object({}) do |klass, hash|
184
+ header_config = if config
185
+ config.fetch(klass::CONFIG_KEY)
186
+ end
187
+
188
+ header_name, value = if klass == CSP
189
+ make_header(klass, header_config, request.user_agent)
75
190
  else
76
- ::SecureHeaders::Configuration.send(klass::Constants::CONFIG_KEY)
191
+ make_header(klass, header_config)
77
192
  end
193
+ hash[header_name] = value if value
194
+ end
195
+ end
78
196
 
79
- unless klass == SecureHeaders::PublicKeyPins && !config.is_a?(Hash)
80
- header = get_a_header(klass, config)
81
- memo[header.name] = header.value if header
197
+ # Private: takes a precomputed hash of headers and returns the Headers
198
+ # customized for the request.
199
+ #
200
+ # Returns a hash of header names / values valid for a given request.
201
+ def use_cached_headers(default_headers, request)
202
+ header_classes_for(request).each_with_object({}) do |klass, hash|
203
+ if default_header = default_headers[klass::CONFIG_KEY]
204
+ header_name, value = if klass == CSP
205
+ default_csp_header_for_ua(default_header, request)
206
+ else
207
+ default_header
208
+ end
209
+ hash[header_name] = value
82
210
  end
83
- memo
84
211
  end
85
212
  end
86
213
 
87
- def get_a_header(klass, options)
88
- return if options == false
89
- klass.new(options)
214
+ # Private: Retreives the config for a given header type:
215
+ #
216
+ # Checks to see if there is an override for this request, then
217
+ # Checks to see if a named override is used for this request, then
218
+ # Falls back to the global config
219
+ def config_for(request)
220
+ request.env[SECURE_HEADERS_CONFIG] ||
221
+ Configuration.get(Configuration::DEFAULT_CONFIG)
222
+ end
223
+
224
+ # Private: chooses the applicable CSP header for the provided user agent.
225
+ #
226
+ # headers - a hash of header_config_key => [header_name, header_value]
227
+ #
228
+ # Returns a CSP [header, value] array
229
+ def default_csp_header_for_ua(headers, request)
230
+ family = UserAgent.parse(request.user_agent).browser
231
+ if CSP::VARIATIONS.key?(family)
232
+ headers[family]
233
+ else
234
+ headers[CSP::OTHER]
235
+ end
236
+ end
237
+
238
+ # Private: optionally build a header with a given configure
239
+ #
240
+ # klass - corresponding Class for a given header
241
+ # config - A string, symbol, or hash config for the header
242
+ # user_agent - A string representing the UA (only used for CSP feature sniffing)
243
+ #
244
+ # Returns a 2 element array [header_name, header_value] or nil if config
245
+ # is OPT_OUT
246
+ def make_header(klass, header_config, user_agent = nil)
247
+ unless header_config == OPT_OUT
248
+ if klass == CSP
249
+ klass.make_header(header_config, user_agent)
250
+ else
251
+ klass.make_header(header_config)
252
+ end
253
+ end
90
254
  end
91
255
  end
92
256
 
257
+ # These methods are mixed into controllers and delegate to the class method
258
+ # with the same name.
259
+ def use_secure_headers_override(name)
260
+ SecureHeaders.use_secure_headers_override(request, name)
261
+ end
262
+
263
+ def content_security_policy_script_nonce
264
+ SecureHeaders.content_security_policy_script_nonce(request)
265
+ end
266
+
267
+ def content_security_policy_style_nonce
268
+ SecureHeaders.content_security_policy_style_nonce(request)
269
+ end
270
+
271
+ def opt_out_of_header(header_key)
272
+ SecureHeaders.opt_out_of_header(request, header_key)
273
+ end
274
+
275
+ def append_content_security_policy_directives(additions)
276
+ SecureHeaders.append_content_security_policy_directives(request, additions)
277
+ end
278
+
279
+ def override_content_security_policy_directives(additions)
280
+ SecureHeaders.override_content_security_policy_directives(request, additions)
281
+ end
282
+
283
+ def override_x_frame_options(value)
284
+ SecureHeaders.override_x_frame_options(request, value)
285
+ end
93
286
  end
@@ -1,24 +1,19 @@
1
1
  # -*- encoding: utf-8 -*-
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'secure_headers/version'
5
-
6
2
  Gem::Specification.new do |gem|
7
3
  gem.name = "secure_headers"
8
- gem.version = SecureHeaders::VERSION
4
+ gem.version = "3.0.0.pre"
9
5
  gem.authors = ["Neil Matatall"]
10
6
  gem.email = ["neil.matatall@gmail.com"]
11
- gem.description = %q{Security related headers all in one gem.}
12
- gem.summary = %q{Add easily configured security headers to responses
7
+ gem.description = 'Security related headers all in one gem.'
8
+ gem.summary = 'Add easily configured security headers to responses
13
9
  including content-security-policy, x-frame-options,
14
- strict-transport-security, etc.}
10
+ strict-transport-security, etc.'
15
11
  gem.homepage = "https://github.com/twitter/secureheaders"
16
12
  gem.license = "Apache Public License 2.0"
17
- gem.files = `git ls-files`.split($/)
18
- gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
14
+ gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
19
15
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
20
16
  gem.require_paths = ["lib"]
21
17
  gem.add_development_dependency "rake"
22
- gem.add_dependency "user_agent_parser"
23
- gem.post_install_message = "[DEPRECATION] Development has stopped on the 2.x line of secure_headers. It will be maintained, but new features will be added to the 3.x branch. A lot has changed in secure_headers 3.x. A migration guide can be found in the documentation: https://github.com/twitter/secureheaders/blob/master/upgrading-to-3-0.md."
18
+ gem.add_dependency "useragent"
24
19
  end
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+
3
+ module SecureHeaders
4
+ describe Configuration do
5
+ before(:each) do
6
+ reset_config
7
+ Configuration.default
8
+ end
9
+
10
+ it "has a default config" do
11
+ expect(Configuration.get(Configuration::DEFAULT_CONFIG)).to_not be_nil
12
+ end
13
+
14
+ it "has an 'noop' config" do
15
+ expect(Configuration.get(Configuration::NOOP_CONFIGURATION)).to_not be_nil
16
+ end
17
+
18
+ it "precomputes headers upon creation" do
19
+ default_config = Configuration.get(Configuration::DEFAULT_CONFIG)
20
+ header_hash = default_config.cached_headers.each_with_object({}) do |(key, value), hash|
21
+ header_name, header_value = if key == :csp
22
+ value["Chrome"]
23
+ else
24
+ value
25
+ end
26
+
27
+ hash[header_name] = header_value
28
+ end
29
+ expect_default_values(header_hash)
30
+ end
31
+
32
+ it "copies all config values except for the cached headers when dup" do
33
+ Configuration.override(:test_override, Configuration::NOOP_CONFIGURATION) do
34
+ # do nothing, just copy it
35
+ end
36
+
37
+ config = Configuration.get(:test_override)
38
+ noop = Configuration.get(Configuration::NOOP_CONFIGURATION)
39
+ [:hsts, :x_frame_options, :x_content_type_options, :x_xss_protection,
40
+ :x_download_options, :x_permitted_cross_domain_policies, :hpkp, :csp].each do |key|
41
+
42
+ expect(config.send(key)).to eq(noop.send(key)), "Value not copied: #{key}."
43
+ end
44
+ end
45
+
46
+ it "stores an override of the global config" do
47
+ Configuration.override(:test_override) do |config|
48
+ config.x_frame_options = "DENY"
49
+ end
50
+
51
+ expect(Configuration.get(:test_override)).to_not be_nil
52
+ end
53
+
54
+ it "deep dup's config values when overriding so the original cannot be modified" do
55
+ Configuration.override(:override) do |config|
56
+ config.csp[:default_src] << "'self'"
57
+ end
58
+
59
+ default = Configuration.get
60
+ override = Configuration.get(:override)
61
+
62
+ expect(override.csp).not_to eq(default.csp)
63
+ end
64
+
65
+ it "allows you to override an override" do
66
+ Configuration.override(:override) do |config|
67
+ config.csp = { default_src: %w('self')}
68
+ end
69
+
70
+ Configuration.override(:second_override, :override) do |config|
71
+ config.csp = config.csp.merge(script_src: %w(example.org))
72
+ end
73
+
74
+ original_override = Configuration.get(:override)
75
+ expect(original_override.csp).to eq(default_src: %w('self'))
76
+ override_config = Configuration.get(:second_override)
77
+ expect(override_config.csp).to eq(default_src: %w('self'), script_src: %w(example.org))
78
+ end
79
+ end
80
+ end