actionpack 5.1.7 → 5.2.0.beta1

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

Potentially problematic release.


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

Files changed (144) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +132 -490
  3. data/README.rdoc +1 -1
  4. data/lib/abstract_controller.rb +2 -0
  5. data/lib/abstract_controller/asset_paths.rb +2 -0
  6. data/lib/abstract_controller/base.rb +10 -2
  7. data/lib/abstract_controller/caching.rb +3 -2
  8. data/lib/abstract_controller/caching/fragments.rb +30 -7
  9. data/lib/abstract_controller/callbacks.rb +25 -3
  10. data/lib/abstract_controller/collector.rb +2 -0
  11. data/lib/abstract_controller/error.rb +2 -0
  12. data/lib/abstract_controller/helpers.rb +4 -5
  13. data/lib/abstract_controller/logger.rb +2 -0
  14. data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
  15. data/lib/abstract_controller/rendering.rb +9 -16
  16. data/lib/abstract_controller/translation.rb +2 -0
  17. data/lib/abstract_controller/url_for.rb +2 -0
  18. data/lib/action_controller.rb +3 -0
  19. data/lib/action_controller/api.rb +2 -0
  20. data/lib/action_controller/api/api_rendering.rb +2 -0
  21. data/lib/action_controller/base.rb +3 -0
  22. data/lib/action_controller/caching.rb +2 -0
  23. data/lib/action_controller/form_builder.rb +2 -0
  24. data/lib/action_controller/log_subscriber.rb +5 -3
  25. data/lib/action_controller/metal.rb +3 -2
  26. data/lib/action_controller/metal/basic_implicit_render.rb +2 -0
  27. data/lib/action_controller/metal/conditional_get.rb +4 -3
  28. data/lib/action_controller/metal/content_security_policy.rb +26 -0
  29. data/lib/action_controller/metal/cookies.rb +2 -0
  30. data/lib/action_controller/metal/data_streaming.rb +7 -5
  31. data/lib/action_controller/metal/etag_with_flash.rb +2 -0
  32. data/lib/action_controller/metal/etag_with_template_digest.rb +3 -2
  33. data/lib/action_controller/metal/exceptions.rb +2 -3
  34. data/lib/action_controller/metal/flash.rb +3 -2
  35. data/lib/action_controller/metal/force_ssl.rb +2 -0
  36. data/lib/action_controller/metal/head.rb +2 -0
  37. data/lib/action_controller/metal/helpers.rb +4 -3
  38. data/lib/action_controller/metal/http_authentication.rb +8 -9
  39. data/lib/action_controller/metal/implicit_render.rb +2 -0
  40. data/lib/action_controller/metal/instrumentation.rb +4 -6
  41. data/lib/action_controller/metal/live.rb +3 -1
  42. data/lib/action_controller/metal/mime_responds.rb +3 -1
  43. data/lib/action_controller/metal/parameter_encoding.rb +2 -0
  44. data/lib/action_controller/metal/params_wrapper.rb +13 -9
  45. data/lib/action_controller/metal/redirecting.rb +21 -10
  46. data/lib/action_controller/metal/renderers.rb +4 -3
  47. data/lib/action_controller/metal/rendering.rb +2 -2
  48. data/lib/action_controller/metal/request_forgery_protection.rb +22 -6
  49. data/lib/action_controller/metal/rescue.rb +5 -3
  50. data/lib/action_controller/metal/streaming.rb +2 -0
  51. data/lib/action_controller/metal/strong_parameters.rb +19 -11
  52. data/lib/action_controller/metal/testing.rb +2 -6
  53. data/lib/action_controller/metal/url_for.rb +2 -0
  54. data/lib/action_controller/railtie.rb +16 -4
  55. data/lib/action_controller/railties/helpers.rb +2 -0
  56. data/lib/action_controller/renderer.rb +2 -0
  57. data/lib/action_controller/template_assertions.rb +2 -0
  58. data/lib/action_controller/test_case.rb +4 -1
  59. data/lib/action_dispatch.rb +3 -0
  60. data/lib/action_dispatch/http/cache.rb +15 -9
  61. data/lib/action_dispatch/http/content_security_policy.rb +233 -0
  62. data/lib/action_dispatch/http/filter_parameters.rb +4 -2
  63. data/lib/action_dispatch/http/filter_redirect.rb +2 -0
  64. data/lib/action_dispatch/http/headers.rb +2 -0
  65. data/lib/action_dispatch/http/mime_negotiation.rb +4 -13
  66. data/lib/action_dispatch/http/mime_type.rb +15 -13
  67. data/lib/action_dispatch/http/mime_types.rb +4 -2
  68. data/lib/action_dispatch/http/parameter_filter.rb +2 -0
  69. data/lib/action_dispatch/http/parameters.rb +6 -9
  70. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  71. data/lib/action_dispatch/http/request.rb +36 -16
  72. data/lib/action_dispatch/http/response.rb +11 -9
  73. data/lib/action_dispatch/http/upload.rb +2 -0
  74. data/lib/action_dispatch/http/url.rb +4 -5
  75. data/lib/action_dispatch/journey.rb +2 -0
  76. data/lib/action_dispatch/journey/formatter.rb +4 -2
  77. data/lib/action_dispatch/journey/gtg/builder.rb +2 -0
  78. data/lib/action_dispatch/journey/gtg/simulator.rb +2 -8
  79. data/lib/action_dispatch/journey/gtg/transition_table.rb +3 -2
  80. data/lib/action_dispatch/journey/nfa/builder.rb +2 -0
  81. data/lib/action_dispatch/journey/nfa/dot.rb +2 -0
  82. data/lib/action_dispatch/journey/nfa/simulator.rb +2 -0
  83. data/lib/action_dispatch/journey/nfa/transition_table.rb +2 -0
  84. data/lib/action_dispatch/journey/nodes/node.rb +2 -0
  85. data/lib/action_dispatch/journey/parser_extras.rb +2 -0
  86. data/lib/action_dispatch/journey/path/pattern.rb +2 -0
  87. data/lib/action_dispatch/journey/route.rb +15 -6
  88. data/lib/action_dispatch/journey/router.rb +3 -1
  89. data/lib/action_dispatch/journey/router/utils.rb +14 -7
  90. data/lib/action_dispatch/journey/routes.rb +2 -1
  91. data/lib/action_dispatch/journey/scanner.rb +1 -0
  92. data/lib/action_dispatch/journey/visitors.rb +5 -3
  93. data/lib/action_dispatch/middleware/callbacks.rb +2 -0
  94. data/lib/action_dispatch/middleware/cookies.rb +141 -91
  95. data/lib/action_dispatch/middleware/debug_exceptions.rb +4 -2
  96. data/lib/action_dispatch/middleware/debug_locks.rb +9 -7
  97. data/lib/action_dispatch/middleware/exception_wrapper.rb +4 -6
  98. data/lib/action_dispatch/middleware/executor.rb +2 -0
  99. data/lib/action_dispatch/middleware/flash.rb +3 -1
  100. data/lib/action_dispatch/middleware/public_exceptions.rb +6 -4
  101. data/lib/action_dispatch/middleware/reloader.rb +2 -0
  102. data/lib/action_dispatch/middleware/remote_ip.rb +7 -5
  103. data/lib/action_dispatch/middleware/request_id.rb +2 -0
  104. data/lib/action_dispatch/middleware/session/abstract_store.rb +3 -1
  105. data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
  106. data/lib/action_dispatch/middleware/session/cookie_store.rb +13 -25
  107. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +2 -0
  108. data/lib/action_dispatch/middleware/show_exceptions.rb +3 -1
  109. data/lib/action_dispatch/middleware/ssl.rb +42 -37
  110. data/lib/action_dispatch/middleware/stack.rb +2 -0
  111. data/lib/action_dispatch/middleware/static.rb +10 -8
  112. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +1 -0
  113. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +6 -2
  114. data/lib/action_dispatch/railtie.rb +7 -0
  115. data/lib/action_dispatch/request/session.rb +8 -4
  116. data/lib/action_dispatch/request/utils.rb +4 -4
  117. data/lib/action_dispatch/routing.rb +3 -1
  118. data/lib/action_dispatch/routing/endpoint.rb +8 -4
  119. data/lib/action_dispatch/routing/inspector.rb +5 -3
  120. data/lib/action_dispatch/routing/mapper.rb +62 -51
  121. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
  122. data/lib/action_dispatch/routing/redirection.rb +7 -5
  123. data/lib/action_dispatch/routing/route_set.rb +26 -33
  124. data/lib/action_dispatch/routing/routes_proxy.rb +5 -2
  125. data/lib/action_dispatch/routing/url_for.rb +6 -4
  126. data/lib/action_dispatch/system_test_case.rb +14 -6
  127. data/lib/action_dispatch/system_testing/driver.rb +20 -2
  128. data/lib/action_dispatch/system_testing/server.rb +2 -16
  129. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +6 -4
  130. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +2 -0
  131. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
  132. data/lib/action_dispatch/testing/assertion_response.rb +2 -0
  133. data/lib/action_dispatch/testing/assertions.rb +2 -0
  134. data/lib/action_dispatch/testing/assertions/response.rb +4 -2
  135. data/lib/action_dispatch/testing/assertions/routing.rb +5 -5
  136. data/lib/action_dispatch/testing/integration.rb +24 -21
  137. data/lib/action_dispatch/testing/request_encoder.rb +2 -0
  138. data/lib/action_dispatch/testing/test_process.rb +2 -0
  139. data/lib/action_dispatch/testing/test_request.rb +3 -1
  140. data/lib/action_dispatch/testing/test_response.rb +23 -3
  141. data/lib/action_pack.rb +2 -0
  142. data/lib/action_pack/gem_version.rb +5 -3
  143. data/lib/action_pack/version.rb +2 -0
  144. metadata +17 -13
@@ -1,4 +1,4 @@
1
- require "active_support/core_ext/string/filters"
1
+ # frozen_string_literal: true
2
2
 
3
3
  module ActionController
4
4
  module Rendering
@@ -40,7 +40,7 @@ module ActionController
40
40
  def render_to_string(*)
41
41
  result = super
42
42
  if result.respond_to?(:each)
43
- string = ""
43
+ string = "".dup
44
44
  result.each { |r| string << r }
45
45
  string
46
46
  else
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "rack/session/abstract/id"
2
4
  require "action_controller/metal/exceptions"
3
5
  require "active_support/security_utils"
@@ -20,7 +22,7 @@ module ActionController #:nodoc:
20
22
  # Since HTML and JavaScript requests are typically made from the browser, we
21
23
  # need to ensure to verify request authenticity for the web browser. We can
22
24
  # use session-oriented authentication for these types of requests, by using
23
- # the `protect_from_forgery` method in our controllers.
25
+ # the <tt>protect_from_forgery</tt> method in our controllers.
24
26
  #
25
27
  # GET requests are not protected since they don't have side effects like writing
26
28
  # to the database and don't leak sensitive information. JavaScript requests are
@@ -85,6 +87,10 @@ module ActionController #:nodoc:
85
87
  config_accessor :per_form_csrf_tokens
86
88
  self.per_form_csrf_tokens = false
87
89
 
90
+ # Controls whether forgery protection is enabled by default.
91
+ config_accessor :default_protect_from_forgery
92
+ self.default_protect_from_forgery = false
93
+
88
94
  helper_method :form_authenticity_token
89
95
  helper_method :protect_against_forgery?
90
96
  end
@@ -128,6 +134,15 @@ module ActionController #:nodoc:
128
134
  append_after_action :verify_same_origin_request
129
135
  end
130
136
 
137
+ # Turn off request forgery protection. This is a wrapper for:
138
+ #
139
+ # skip_before_action :verify_authenticity_token
140
+ #
141
+ # See +skip_before_action+ for allowed options.
142
+ def skip_forgery_protection(options = {})
143
+ skip_before_action :verify_authenticity_token, options
144
+ end
145
+
131
146
  private
132
147
 
133
148
  def protection_method_class(name)
@@ -201,7 +216,7 @@ module ActionController #:nodoc:
201
216
  # The actual before_action that is used to verify the CSRF token.
202
217
  # Don't override this directly. Provide your own forgery protection
203
218
  # strategy instead. If you override, you'll disable same-origin
204
- # `<script>` verification.
219
+ # <tt><script></tt> verification.
205
220
  #
206
221
  # Lean on the protect_from_forgery declaration to mark which actions are
207
222
  # due for same-origin request verification. If protect_from_forgery is
@@ -233,8 +248,9 @@ module ActionController #:nodoc:
233
248
  "If you know what you're doing, go ahead and disable forgery " \
234
249
  "protection on this action to permit cross-origin JavaScript embedding."
235
250
  private_constant :CROSS_ORIGIN_JAVASCRIPT_WARNING
251
+ # :startdoc:
236
252
 
237
- # If `verify_authenticity_token` was run (indicating that we have
253
+ # If +verify_authenticity_token+ was run (indicating that we have
238
254
  # forgery protection enabled for this request) then also verify that
239
255
  # we aren't serving an unauthorized cross-origin response.
240
256
  def verify_same_origin_request # :doc:
@@ -251,7 +267,7 @@ module ActionController #:nodoc:
251
267
  @marked_for_same_origin_verification = request.get?
252
268
  end
253
269
 
254
- # If the `verify_authenticity_token` before_action ran, verify that
270
+ # If the +verify_authenticity_token+ before_action ran, verify that
255
271
  # JavaScript responses are only served to same-origin GET requests.
256
272
  def marked_for_same_origin_verification? # :doc:
257
273
  @marked_for_same_origin_verification ||= false
@@ -353,7 +369,7 @@ module ActionController #:nodoc:
353
369
  end
354
370
 
355
371
  def compare_with_real_token(token, session) # :doc:
356
- ActiveSupport::SecurityUtils.secure_compare(token, real_csrf_token(session))
372
+ ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, real_csrf_token(session))
357
373
  end
358
374
 
359
375
  def valid_per_form_csrf_token?(token, session) # :doc:
@@ -364,7 +380,7 @@ module ActionController #:nodoc:
364
380
  request.request_method
365
381
  )
366
382
 
367
- ActiveSupport::SecurityUtils.secure_compare(token, correct_token)
383
+ ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, correct_token)
368
384
  else
369
385
  false
370
386
  end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionController #:nodoc:
2
- # This module is responsible for providing `rescue_from` helpers
4
+ # This module is responsible for providing +rescue_from+ helpers
3
5
  # to controllers and configuring when detailed exceptions must be
4
6
  # shown.
5
7
  module Rescue
@@ -8,8 +10,8 @@ module ActionController #:nodoc:
8
10
 
9
11
  # Override this method if you want to customize when detailed
10
12
  # exceptions must be shown. This method is only called when
11
- # consider_all_requests_local is false. By default, it returns
12
- # false, but someone may set it to `request.local?` so local
13
+ # +consider_all_requests_local+ is +false+. By default, it returns
14
+ # +false+, but someone may set it to <tt>request.local?</tt> so local
13
15
  # requests in production still show the detailed exception pages.
14
16
  def show_detailed_exceptions?
15
17
  false
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "rack/chunked"
2
4
 
3
5
  module ActionController #:nodoc:
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/core_ext/hash/indifferent_access"
2
4
  require "active_support/core_ext/hash/transform_values"
3
5
  require "active_support/core_ext/array/wrap"
@@ -119,8 +121,7 @@ module ActionController
119
121
  # params[:key] # => "value"
120
122
  # params["key"] # => "value"
121
123
  class Parameters
122
- cattr_accessor :permit_all_parameters, instance_accessor: false
123
- self.permit_all_parameters = false
124
+ cattr_accessor :permit_all_parameters, instance_accessor: false, default: false
124
125
 
125
126
  cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false
126
127
 
@@ -185,6 +186,7 @@ module ActionController
185
186
  #
186
187
  # :call-seq:
187
188
  # to_s()
189
+ #
188
190
  # Returns the content of the parameters as a string.
189
191
 
190
192
  ##
@@ -212,8 +214,7 @@ module ActionController
212
214
  # config. For instance:
213
215
  #
214
216
  # config.always_permitted_parameters = %w( controller action format )
215
- cattr_accessor :always_permitted_parameters
216
- self.always_permitted_parameters = %w( controller action )
217
+ cattr_accessor :always_permitted_parameters, default: %w( controller action )
217
218
 
218
219
  # Returns a new instance of <tt>ActionController::Parameters</tt>.
219
220
  # Also, sets the +permitted+ attribute to the default value of
@@ -254,7 +255,7 @@ module ActionController
254
255
  # oddity: "Heavy stone crab"
255
256
  # })
256
257
  # params.to_h
257
- # # => ActionController::UnfilteredParameters: unable to convert unfiltered parameters to hash
258
+ # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash
258
259
  #
259
260
  # safe_params = params.permit(:name)
260
261
  # safe_params.to_h # => {"name"=>"Senjougahara Hitagi"}
@@ -274,7 +275,7 @@ module ActionController
274
275
  # oddity: "Heavy stone crab"
275
276
  # })
276
277
  # params.to_hash
277
- # # => ActionController::UnfilteredParameters: unable to convert unfiltered parameters to hash
278
+ # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash
278
279
  #
279
280
  # safe_params = params.permit(:name)
280
281
  # safe_params.to_hash # => {"name"=>"Senjougahara Hitagi"}
@@ -290,6 +291,10 @@ module ActionController
290
291
  # nationality: "Danish"
291
292
  # })
292
293
  # params.to_query
294
+ # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash
295
+ #
296
+ # safe_params = params.permit(:name, :nationality)
297
+ # safe_params.to_query
293
298
  # # => "name=David&nationality=Danish"
294
299
  #
295
300
  # An optional namespace can be passed to enclose key names:
@@ -298,7 +303,8 @@ module ActionController
298
303
  # name: "David",
299
304
  # nationality: "Danish"
300
305
  # })
301
- # params.to_query("user")
306
+ # safe_params = params.permit(:name, :nationality)
307
+ # safe_params.to_query("user")
302
308
  # # => "user%5Bname%5D=David&user%5Bnationality%5D=Danish"
303
309
  #
304
310
  # The string pairs "key=value" that conform the query string
@@ -669,10 +675,10 @@ module ActionController
669
675
  self
670
676
  end
671
677
 
672
- # Deletes and returns a key-value pair from +Parameters+ whose key is equal
673
- # to key. If the key is not found, returns the default value. If the
674
- # optional code block is given and the key is not found, pass in the key
675
- # and return the result of block.
678
+ # Deletes a key-value pair from +Parameters+ and returns the value. If
679
+ # +key+ is not found, returns +nil+ (or, with optional code block, yields
680
+ # +key+ and returns the result). Cf. +#extract!+, which returns the
681
+ # corresponding +ActionController::Parameters+ object.
676
682
  def delete(key, &block)
677
683
  convert_value_to_parameters(@parameters.delete(key, &block))
678
684
  end
@@ -731,6 +737,7 @@ module ActionController
731
737
  other_hash.to_h.merge(@parameters)
732
738
  )
733
739
  end
740
+ alias_method :with_defaults, :reverse_merge
734
741
 
735
742
  # Returns current <tt>ActionController::Parameters</tt> instance with
736
743
  # current hash merged into +other_hash+.
@@ -738,6 +745,7 @@ module ActionController
738
745
  @parameters.merge!(other_hash.to_h) { |key, left, right| left }
739
746
  self
740
747
  end
748
+ alias_method :with_defaults!, :reverse_merge!
741
749
 
742
750
  # This is required by ActiveModel attribute assignment, so that user can
743
751
  # pass +Parameters+ to a mass assignment methods in a model. It should not
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionController
2
4
  module Testing
3
5
  extend ActiveSupport::Concern
@@ -10,11 +12,5 @@ module ActionController
10
12
  self.params = nil
11
13
  end
12
14
  end
13
-
14
- module ClassMethods
15
- def before_filters
16
- _process_action_callbacks.find_all { |x| x.kind == :before }.map(&:name)
17
- end
18
- end
19
15
  end
20
16
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionController
2
4
  # Includes +url_for+ into the host class. The class has to provide a +RouteSet+ by implementing
3
5
  # the <tt>_routes</tt> method. Otherwise, an exception will be raised.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "rails"
2
4
  require "action_controller"
3
5
  require "action_dispatch/railtie"
@@ -23,10 +25,6 @@ module ActionController
23
25
  options = app.config.action_controller
24
26
 
25
27
  ActiveSupport.on_load(:action_controller, run_once: true) do
26
- if options.delete(:raise_on_unfiltered_parameters)
27
- ActiveSupport::Deprecation.warn("raise_on_unfiltered_parameters is deprecated and has no effect in Rails 5.1.")
28
- end
29
-
30
28
  ActionController::Parameters.permit_all_parameters = options.delete(:permit_all_parameters) { false }
31
29
  if app.config.action_controller[:always_permitted_parameters]
32
30
  ActionController::Parameters.always_permitted_parameters =
@@ -73,5 +71,19 @@ module ActionController
73
71
  config.compile_methods! if config.respond_to?(:compile_methods!)
74
72
  end
75
73
  end
74
+
75
+ initializer "action_controller.request_forgery_protection" do |app|
76
+ ActiveSupport.on_load(:action_controller_base) do
77
+ if app.config.action_controller.default_protect_from_forgery
78
+ protect_from_forgery with: :exception
79
+ end
80
+ end
81
+ end
82
+
83
+ initializer "action_controller.eager_load_actions" do
84
+ ActiveSupport.on_load(:after_initialize) do
85
+ ActionController::Metal.descendants.each(&:action_methods) if config.eager_load
86
+ end
87
+ end
76
88
  end
77
89
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionController
2
4
  module Railties
3
5
  module Helpers
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/core_ext/hash/keys"
2
4
 
3
5
  module ActionController
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionController
2
4
  module TemplateAssertions
3
5
  def assert_template(options = {}, message = nil)
@@ -1,7 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "rack/session/abstract/id"
2
4
  require "active_support/core_ext/hash/conversions"
3
5
  require "active_support/core_ext/object/to_query"
4
6
  require "active_support/core_ext/module/anonymous"
7
+ require "active_support/core_ext/module/redefine_method"
5
8
  require "active_support/core_ext/hash/keys"
6
9
  require "active_support/testing/constant_lookup"
7
10
  require "action_controller/template_assertions"
@@ -17,7 +20,7 @@ module ActionController
17
20
  # the database on the main thread, so they could open a txn, then the
18
21
  # controller thread will open a new connection and try to access data
19
22
  # that's only visible to the main thread's txn. This is the problem in #23483.
20
- remove_method :new_controller_thread
23
+ silence_redefinition_of_method :new_controller_thread
21
24
  def new_controller_thread # :nodoc:
22
25
  yield
23
26
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #--
2
4
  # Copyright (c) 2004-2017 David Heinemeier Hansson
3
5
  #
@@ -40,6 +42,7 @@ module ActionDispatch
40
42
 
41
43
  eager_autoload do
42
44
  autoload_under "http" do
45
+ autoload :ContentSecurityPolicy
43
46
  autoload :Request
44
47
  autoload :Response
45
48
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionDispatch
2
4
  module Http
3
5
  module Cache
@@ -95,7 +97,7 @@ module ActionDispatch
95
97
  # support strong ETags and will ignore weak ETags entirely.
96
98
  #
97
99
  # Weak ETags are what we almost always need, so they're the default.
98
- # Check out `#strong_etag=` to provide a strong ETag validator.
100
+ # Check out #strong_etag= to provide a strong ETag validator.
99
101
  def etag=(weak_validators)
100
102
  self.weak_etag = weak_validators
101
103
  end
@@ -164,19 +166,23 @@ module ActionDispatch
164
166
  @cache_control = cache_control_headers
165
167
  end
166
168
 
167
- def handle_conditional_get!
168
- if etag? || last_modified? || !@cache_control.empty?
169
- set_conditional_cache_control!(@cache_control)
170
- end
171
- end
172
-
173
169
  DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate".freeze
174
170
  NO_CACHE = "no-cache".freeze
175
171
  PUBLIC = "public".freeze
176
172
  PRIVATE = "private".freeze
177
173
  MUST_REVALIDATE = "must-revalidate".freeze
178
174
 
179
- def set_conditional_cache_control!(cache_control)
175
+ def handle_conditional_get!
176
+ # Normally default cache control setting is handled by ETag
177
+ # middleware. But, if an etag is already set, the middleware
178
+ # defaults to `no-cache` unless a default `Cache-Control` value is
179
+ # previously set. So, set a default one here.
180
+ if (etag? || last_modified?) && !self._cache_control
181
+ self._cache_control = DEFAULT_CACHE_CONTROL
182
+ end
183
+ end
184
+
185
+ def merge_and_normalize_cache_control!(cache_control)
180
186
  control = {}
181
187
  cc_headers = cache_control_headers
182
188
  if extras = cc_headers.delete(:extras)
@@ -189,7 +195,7 @@ module ActionDispatch
189
195
  control.merge! cache_control
190
196
 
191
197
  if control.empty?
192
- self._cache_control = DEFAULT_CACHE_CONTROL
198
+ # Let middleware handle default behavior
193
199
  elsif control[:no_cache]
194
200
  self._cache_control = NO_CACHE
195
201
  if control[:extras]
@@ -0,0 +1,233 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionDispatch #:nodoc:
4
+ class ContentSecurityPolicy
5
+ class Middleware
6
+ CONTENT_TYPE = "Content-Type".freeze
7
+ POLICY = "Content-Security-Policy".freeze
8
+ POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only".freeze
9
+
10
+ def initialize(app)
11
+ @app = app
12
+ end
13
+
14
+ def call(env)
15
+ request = ActionDispatch::Request.new env
16
+ _, headers, _ = response = @app.call(env)
17
+
18
+ return response unless html_response?(headers)
19
+ return response if policy_present?(headers)
20
+
21
+ if policy = request.content_security_policy
22
+ headers[header_name(request)] = policy.build(request.controller_instance)
23
+ end
24
+
25
+ response
26
+ end
27
+
28
+ private
29
+
30
+ def html_response?(headers)
31
+ if content_type = headers[CONTENT_TYPE]
32
+ content_type =~ /html/
33
+ end
34
+ end
35
+
36
+ def header_name(request)
37
+ if request.content_security_policy_report_only
38
+ POLICY_REPORT_ONLY
39
+ else
40
+ POLICY
41
+ end
42
+ end
43
+
44
+ def policy_present?(headers)
45
+ headers[POLICY] || headers[POLICY_REPORT_ONLY]
46
+ end
47
+ end
48
+
49
+ module Request
50
+ POLICY = "action_dispatch.content_security_policy".freeze
51
+ POLICY_REPORT_ONLY = "action_dispatch.content_security_policy_report_only".freeze
52
+
53
+ def content_security_policy
54
+ get_header(POLICY)
55
+ end
56
+
57
+ def content_security_policy=(policy)
58
+ set_header(POLICY, policy)
59
+ end
60
+
61
+ def content_security_policy_report_only
62
+ get_header(POLICY_REPORT_ONLY)
63
+ end
64
+
65
+ def content_security_policy_report_only=(value)
66
+ set_header(POLICY_REPORT_ONLY, value)
67
+ end
68
+ end
69
+
70
+ MAPPINGS = {
71
+ self: "'self'",
72
+ unsafe_eval: "'unsafe-eval'",
73
+ unsafe_inline: "'unsafe-inline'",
74
+ none: "'none'",
75
+ http: "http:",
76
+ https: "https:",
77
+ data: "data:",
78
+ mediastream: "mediastream:",
79
+ blob: "blob:",
80
+ filesystem: "filesystem:",
81
+ report_sample: "'report-sample'",
82
+ strict_dynamic: "'strict-dynamic'"
83
+ }.freeze
84
+
85
+ DIRECTIVES = {
86
+ base_uri: "base-uri",
87
+ child_src: "child-src",
88
+ connect_src: "connect-src",
89
+ default_src: "default-src",
90
+ font_src: "font-src",
91
+ form_action: "form-action",
92
+ frame_ancestors: "frame-ancestors",
93
+ frame_src: "frame-src",
94
+ img_src: "img-src",
95
+ manifest_src: "manifest-src",
96
+ media_src: "media-src",
97
+ object_src: "object-src",
98
+ script_src: "script-src",
99
+ style_src: "style-src",
100
+ worker_src: "worker-src"
101
+ }.freeze
102
+
103
+ private_constant :MAPPINGS, :DIRECTIVES
104
+
105
+ attr_reader :directives
106
+
107
+ def initialize
108
+ @directives = {}
109
+ yield self if block_given?
110
+ end
111
+
112
+ def initialize_copy(other)
113
+ @directives = copy_directives(other.directives)
114
+ end
115
+
116
+ DIRECTIVES.each do |name, directive|
117
+ define_method(name) do |*sources|
118
+ if sources.first
119
+ @directives[directive] = apply_mappings(sources)
120
+ else
121
+ @directives.delete(directive)
122
+ end
123
+ end
124
+ end
125
+
126
+ def block_all_mixed_content(enabled = true)
127
+ if enabled
128
+ @directives["block-all-mixed-content"] = true
129
+ else
130
+ @directives.delete("block-all-mixed-content")
131
+ end
132
+ end
133
+
134
+ def plugin_types(*types)
135
+ if types.first
136
+ @directives["plugin-types"] = types
137
+ else
138
+ @directives.delete("plugin-types")
139
+ end
140
+ end
141
+
142
+ def report_uri(uri)
143
+ @directives["report-uri"] = [uri]
144
+ end
145
+
146
+ def require_sri_for(*types)
147
+ if types.first
148
+ @directives["require-sri-for"] = types
149
+ else
150
+ @directives.delete("require-sri-for")
151
+ end
152
+ end
153
+
154
+ def sandbox(*values)
155
+ if values.empty?
156
+ @directives["sandbox"] = true
157
+ elsif values.first
158
+ @directives["sandbox"] = values
159
+ else
160
+ @directives.delete("sandbox")
161
+ end
162
+ end
163
+
164
+ def upgrade_insecure_requests(enabled = true)
165
+ if enabled
166
+ @directives["upgrade-insecure-requests"] = true
167
+ else
168
+ @directives.delete("upgrade-insecure-requests")
169
+ end
170
+ end
171
+
172
+ def build(context = nil)
173
+ build_directives(context).compact.join("; ") + ";"
174
+ end
175
+
176
+ private
177
+ def copy_directives(directives)
178
+ directives.transform_values { |sources| sources.map(&:dup) }
179
+ end
180
+
181
+ def apply_mappings(sources)
182
+ sources.map do |source|
183
+ case source
184
+ when Symbol
185
+ apply_mapping(source)
186
+ when String, Proc
187
+ source
188
+ else
189
+ raise ArgumentError, "Invalid content security policy source: #{source.inspect}"
190
+ end
191
+ end
192
+ end
193
+
194
+ def apply_mapping(source)
195
+ MAPPINGS.fetch(source) do
196
+ raise ArgumentError, "Unknown content security policy source mapping: #{source.inspect}"
197
+ end
198
+ end
199
+
200
+ def build_directives(context)
201
+ @directives.map do |directive, sources|
202
+ if sources.is_a?(Array)
203
+ "#{directive} #{build_directive(sources, context).join(' ')}"
204
+ elsif sources
205
+ directive
206
+ else
207
+ nil
208
+ end
209
+ end
210
+ end
211
+
212
+ def build_directive(sources, context)
213
+ sources.map { |source| resolve_source(source, context) }
214
+ end
215
+
216
+ def resolve_source(source, context)
217
+ case source
218
+ when String
219
+ source
220
+ when Symbol
221
+ source.to_s
222
+ when Proc
223
+ if context.nil?
224
+ raise RuntimeError, "Missing context for the dynamic content security policy source: #{source.inspect}"
225
+ else
226
+ context.instance_exec(&source)
227
+ end
228
+ else
229
+ raise RuntimeError, "Unexpected content security policy source: #{source.inspect}"
230
+ end
231
+ end
232
+ end
233
+ end