actionpack 6.1.7.5 → 7.0.8

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 (128) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +319 -401
  3. data/MIT-LICENSE +1 -0
  4. data/README.rdoc +4 -5
  5. data/lib/abstract_controller/asset_paths.rb +1 -1
  6. data/lib/abstract_controller/base.rb +13 -26
  7. data/lib/abstract_controller/caching/fragments.rb +2 -2
  8. data/lib/abstract_controller/caching.rb +1 -1
  9. data/lib/abstract_controller/callbacks.rb +21 -7
  10. data/lib/abstract_controller/collector.rb +2 -2
  11. data/lib/abstract_controller/error.rb +1 -1
  12. data/lib/abstract_controller/helpers.rb +17 -12
  13. data/lib/abstract_controller/logger.rb +1 -1
  14. data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
  15. data/lib/abstract_controller/rendering.rb +9 -11
  16. data/lib/abstract_controller/translation.rb +5 -4
  17. data/lib/abstract_controller/url_for.rb +4 -6
  18. data/lib/action_controller/api.rb +7 -7
  19. data/lib/action_controller/base.rb +5 -4
  20. data/lib/action_controller/form_builder.rb +2 -2
  21. data/lib/action_controller/log_subscriber.rb +4 -3
  22. data/lib/action_controller/metal/basic_implicit_render.rb +3 -1
  23. data/lib/action_controller/metal/conditional_get.rb +137 -102
  24. data/lib/action_controller/metal/content_security_policy.rb +36 -2
  25. data/lib/action_controller/metal/cookies.rb +1 -1
  26. data/lib/action_controller/metal/data_streaming.rb +23 -31
  27. data/lib/action_controller/metal/etag_with_flash.rb +1 -1
  28. data/lib/action_controller/metal/exceptions.rb +19 -30
  29. data/lib/action_controller/metal/flash.rb +6 -2
  30. data/lib/action_controller/metal/head.rb +1 -1
  31. data/lib/action_controller/metal/helpers.rb +2 -2
  32. data/lib/action_controller/metal/http_authentication.rb +66 -39
  33. data/lib/action_controller/metal/instrumentation.rb +57 -52
  34. data/lib/action_controller/metal/live.rb +43 -2
  35. data/lib/action_controller/metal/mime_responds.rb +3 -3
  36. data/lib/action_controller/metal/params_wrapper.rb +20 -11
  37. data/lib/action_controller/metal/permissions_policy.rb +19 -28
  38. data/lib/action_controller/metal/redirecting.rb +95 -22
  39. data/lib/action_controller/metal/renderers.rb +12 -13
  40. data/lib/action_controller/metal/rendering.rb +121 -9
  41. data/lib/action_controller/metal/request_forgery_protection.rb +83 -32
  42. data/lib/action_controller/metal/rescue.rb +5 -4
  43. data/lib/action_controller/metal/streaming.rb +7 -9
  44. data/lib/action_controller/metal/strong_parameters.rb +138 -115
  45. data/lib/action_controller/metal/testing.rb +9 -2
  46. data/lib/action_controller/metal/url_for.rb +3 -5
  47. data/lib/action_controller/metal.rb +10 -13
  48. data/lib/action_controller/railtie.rb +50 -6
  49. data/lib/action_controller/renderer.rb +1 -20
  50. data/lib/action_controller/test_case.rb +28 -7
  51. data/lib/action_controller.rb +2 -5
  52. data/lib/action_dispatch/http/cache.rb +20 -13
  53. data/lib/action_dispatch/http/content_security_policy.rb +113 -36
  54. data/lib/action_dispatch/http/filter_parameters.rb +4 -19
  55. data/lib/action_dispatch/http/headers.rb +1 -1
  56. data/lib/action_dispatch/http/mime_negotiation.rb +15 -5
  57. data/lib/action_dispatch/http/mime_type.rb +9 -11
  58. data/lib/action_dispatch/http/parameters.rb +5 -5
  59. data/lib/action_dispatch/http/permissions_policy.rb +17 -1
  60. data/lib/action_dispatch/http/request.rb +27 -37
  61. data/lib/action_dispatch/http/response.rb +3 -20
  62. data/lib/action_dispatch/http/upload.rb +13 -2
  63. data/lib/action_dispatch/http/url.rb +11 -19
  64. data/lib/action_dispatch/journey/gtg/builder.rb +11 -12
  65. data/lib/action_dispatch/journey/gtg/simulator.rb +10 -4
  66. data/lib/action_dispatch/journey/gtg/transition_table.rb +77 -21
  67. data/lib/action_dispatch/journey/nodes/node.rb +70 -5
  68. data/lib/action_dispatch/journey/path/pattern.rb +22 -13
  69. data/lib/action_dispatch/journey/route.rb +6 -13
  70. data/lib/action_dispatch/journey/router/utils.rb +2 -2
  71. data/lib/action_dispatch/journey/router.rb +1 -1
  72. data/lib/action_dispatch/journey/routes.rb +3 -3
  73. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  74. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  75. data/lib/action_dispatch/middleware/actionable_exceptions.rb +0 -1
  76. data/lib/action_dispatch/middleware/cookies.rb +20 -13
  77. data/lib/action_dispatch/middleware/debug_exceptions.rb +6 -4
  78. data/lib/action_dispatch/middleware/debug_locks.rb +3 -3
  79. data/lib/action_dispatch/middleware/exception_wrapper.rb +4 -0
  80. data/lib/action_dispatch/middleware/executor.rb +3 -0
  81. data/lib/action_dispatch/middleware/flash.rb +17 -18
  82. data/lib/action_dispatch/middleware/host_authorization.rb +13 -17
  83. data/lib/action_dispatch/middleware/remote_ip.rb +20 -8
  84. data/lib/action_dispatch/middleware/request_id.rb +3 -3
  85. data/lib/action_dispatch/middleware/server_timing.rb +76 -0
  86. data/lib/action_dispatch/middleware/session/abstract_store.rb +1 -1
  87. data/lib/action_dispatch/middleware/session/cookie_store.rb +9 -9
  88. data/lib/action_dispatch/middleware/show_exceptions.rb +17 -16
  89. data/lib/action_dispatch/middleware/stack.rb +27 -9
  90. data/lib/action_dispatch/middleware/static.rb +5 -9
  91. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +1 -1
  92. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -11
  93. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +2 -2
  94. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +10 -5
  95. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +7 -3
  96. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +4 -4
  97. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +3 -3
  98. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +28 -18
  99. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +3 -3
  100. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +3 -3
  101. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
  102. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +3 -3
  103. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +3 -3
  104. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +22 -22
  105. data/lib/action_dispatch/railtie.rb +8 -2
  106. data/lib/action_dispatch/request/session.rb +43 -13
  107. data/lib/action_dispatch/routing/inspector.rb +1 -1
  108. data/lib/action_dispatch/routing/mapper.rb +82 -83
  109. data/lib/action_dispatch/routing/redirection.rb +5 -2
  110. data/lib/action_dispatch/routing/route_set.rb +17 -7
  111. data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
  112. data/lib/action_dispatch/routing/url_for.rb +24 -25
  113. data/lib/action_dispatch/routing.rb +5 -6
  114. data/lib/action_dispatch/system_test_case.rb +5 -5
  115. data/lib/action_dispatch/system_testing/browser.rb +3 -13
  116. data/lib/action_dispatch/system_testing/driver.rb +34 -10
  117. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +11 -7
  118. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +0 -8
  119. data/lib/action_dispatch/testing/assertions/response.rb +1 -1
  120. data/lib/action_dispatch/testing/assertions/routing.rb +3 -2
  121. data/lib/action_dispatch/testing/assertions.rb +2 -5
  122. data/lib/action_dispatch/testing/integration.rb +6 -8
  123. data/lib/action_dispatch/testing/test_process.rb +3 -29
  124. data/lib/action_dispatch/testing/test_response.rb +20 -2
  125. data/lib/action_dispatch.rb +1 -0
  126. data/lib/action_pack/gem_version.rb +5 -5
  127. data/lib/action_pack/version.rb +1 -1
  128. metadata +16 -15
@@ -2,8 +2,6 @@
2
2
 
3
3
  require "active_support/core_ext/array/extract_options"
4
4
  require "action_dispatch/middleware/stack"
5
- require "action_dispatch/http/request"
6
- require "action_dispatch/http/response"
7
5
 
8
6
  module ActionController
9
7
  # Extend ActionDispatch middleware stack to make it aware of options
@@ -13,8 +11,8 @@ module ActionController
13
11
  # use AuthenticationMiddleware, except: [:index, :show]
14
12
  # end
15
13
  #
16
- class MiddlewareStack < ActionDispatch::MiddlewareStack #:nodoc:
17
- class Middleware < ActionDispatch::MiddlewareStack::Middleware #:nodoc:
14
+ class MiddlewareStack < ActionDispatch::MiddlewareStack # :nodoc:
15
+ class Middleware < ActionDispatch::MiddlewareStack::Middleware # :nodoc:
18
16
  def initialize(klass, args, actions, strategy, block)
19
17
  @actions = actions
20
18
  @strategy = strategy
@@ -62,7 +60,7 @@ module ActionController
62
60
 
63
61
  # <tt>ActionController::Metal</tt> is the simplest possible controller, providing a
64
62
  # valid Rack interface without the additional niceties provided by
65
- # <tt>ActionController::Base</tt>.
63
+ # ActionController::Base.
66
64
  #
67
65
  # A sample metal controller might look like this:
68
66
  #
@@ -113,7 +111,7 @@ module ActionController
113
111
  #
114
112
  # == Other Helpers
115
113
  #
116
- # You can refer to the modules included in <tt>ActionController::Base</tt> to see
114
+ # You can refer to the modules included in ActionController::Base to see
117
115
  # other features you can bring into your metal controller.
118
116
  #
119
117
  class Metal < AbstractController::Base
@@ -139,7 +137,7 @@ module ActionController
139
137
  false
140
138
  end
141
139
 
142
- # Delegates to the class' <tt>controller_name</tt>.
140
+ # Delegates to the class's ::controller_name.
143
141
  def controller_name
144
142
  self.class.controller_name
145
143
  end
@@ -184,7 +182,7 @@ module ActionController
184
182
  response_body || response.committed?
185
183
  end
186
184
 
187
- def dispatch(name, request, response) #:nodoc:
185
+ def dispatch(name, request, response) # :nodoc:
188
186
  set_request!(request)
189
187
  set_response!(response)
190
188
  process(name)
@@ -196,12 +194,12 @@ module ActionController
196
194
  @_response = response
197
195
  end
198
196
 
199
- def set_request!(request) #:nodoc:
197
+ def set_request!(request) # :nodoc:
200
198
  @_request = request
201
199
  @_request.controller_instance = self
202
200
  end
203
201
 
204
- def to_a #:nodoc:
202
+ def to_a # :nodoc:
205
203
  response.to_a
206
204
  end
207
205
 
@@ -219,10 +217,9 @@ module ActionController
219
217
  class << self
220
218
  # Pushes the given Rack middleware and its arguments to the bottom of the
221
219
  # middleware stack.
222
- def use(*args, &block)
223
- middleware_stack.use(*args, &block)
220
+ def use(...)
221
+ middleware_stack.use(...)
224
222
  end
225
- ruby2_keywords(:use) if respond_to?(:ruby2_keywords, true)
226
223
  end
227
224
 
228
225
  # Alias for +middleware_stack+.
@@ -8,8 +8,11 @@ require "action_controller/railties/helpers"
8
8
  require "action_view/railtie"
9
9
 
10
10
  module ActionController
11
- class Railtie < Rails::Railtie #:nodoc:
11
+ class Railtie < Rails::Railtie # :nodoc:
12
12
  config.action_controller = ActiveSupport::OrderedOptions.new
13
+ config.action_controller.raise_on_open_redirects = false
14
+ config.action_controller.log_query_tags_around_actions = true
15
+ config.action_controller.wrap_parameters_by_default = false
13
16
 
14
17
  config.eager_load_namespaces << ActionController
15
18
 
@@ -25,14 +28,19 @@ module ActionController
25
28
  options = app.config.action_controller
26
29
 
27
30
  ActiveSupport.on_load(:action_controller, run_once: true) do
28
- ActionController::Parameters.permit_all_parameters = options.delete(:permit_all_parameters) { false }
31
+ ActionController::Parameters.permit_all_parameters = options.permit_all_parameters || false
29
32
  if app.config.action_controller[:always_permitted_parameters]
30
33
  ActionController::Parameters.always_permitted_parameters =
31
- app.config.action_controller.delete(:always_permitted_parameters)
34
+ app.config.action_controller.always_permitted_parameters
32
35
  end
33
- ActionController::Parameters.action_on_unpermitted_parameters = options.delete(:action_on_unpermitted_parameters) do
34
- (Rails.env.test? || Rails.env.development?) ? :log : false
36
+
37
+ action_on_unpermitted_parameters = options.action_on_unpermitted_parameters
38
+
39
+ if action_on_unpermitted_parameters.nil?
40
+ action_on_unpermitted_parameters = (Rails.env.test? || Rails.env.development?) ? :log : false
35
41
  end
42
+
43
+ ActionController::Parameters.action_on_unpermitted_parameters = action_on_unpermitted_parameters
36
44
  end
37
45
  end
38
46
 
@@ -55,7 +63,18 @@ module ActionController
55
63
  extend ::AbstractController::Railties::RoutesHelpers.with(app.routes)
56
64
  extend ::ActionController::Railties::Helpers
57
65
 
58
- options.each do |k, v|
66
+ wrap_parameters format: [:json] if options.wrap_parameters_by_default && respond_to?(:wrap_parameters)
67
+
68
+ # Configs used in other initializers
69
+ filtered_options = options.except(
70
+ :log_query_tags_around_actions,
71
+ :permit_all_parameters,
72
+ :action_on_unpermitted_parameters,
73
+ :always_permitted_parameters,
74
+ :wrap_parameters_by_default
75
+ )
76
+
77
+ filtered_options.each do |k, v|
59
78
  k = "#{k}="
60
79
  if respond_to?(k)
61
80
  send(k, v)
@@ -85,5 +104,30 @@ module ActionController
85
104
  ActionController::Metal.descendants.each(&:action_methods) if config.eager_load
86
105
  end
87
106
  end
107
+
108
+ initializer "action_controller.query_log_tags" do |app|
109
+ query_logs_tags_enabled = app.config.respond_to?(:active_record) &&
110
+ app.config.active_record.query_log_tags_enabled &&
111
+ app.config.action_controller.log_query_tags_around_actions
112
+
113
+ if query_logs_tags_enabled
114
+ app.config.active_record.query_log_tags |= [:controller] unless app.config.active_record.query_log_tags.include?(:namespaced_controller)
115
+ app.config.active_record.query_log_tags |= [:action]
116
+
117
+ ActiveSupport.on_load(:active_record) do
118
+ ActiveRecord::QueryLogs.taggings.merge!(
119
+ controller: ->(context) { context[:controller]&.controller_name },
120
+ action: ->(context) { context[:controller]&.action_name },
121
+ namespaced_controller: ->(context) { context[:controller].class.name if context[:controller] }
122
+ )
123
+ end
124
+ end
125
+ end
126
+
127
+ initializer "action_controller.test_case" do |app|
128
+ ActiveSupport.on_load(:action_controller_test_case) do
129
+ ActionController::TestCase.executor_around_each_request = app.config.active_support.executor_around_test_case
130
+ end
131
+ end
88
132
  end
89
133
  end
@@ -68,26 +68,7 @@ module ActionController
68
68
  @env = normalize_keys defaults, env
69
69
  end
70
70
 
71
- # Render templates with any options from ActionController::Base#render_to_string.
72
- #
73
- # The primary options are:
74
- # * <tt>:partial</tt> - See <tt>ActionView::PartialRenderer</tt> for details.
75
- # * <tt>:file</tt> - Renders an explicit template file. Add <tt>:locals</tt> to pass in, if so desired.
76
- # It shouldn’t be used directly with unsanitized user input due to lack of validation.
77
- # * <tt>:inline</tt> - Renders an ERB template string.
78
- # * <tt>:plain</tt> - Renders provided text and sets the content type as <tt>text/plain</tt>.
79
- # * <tt>:html</tt> - Renders the provided HTML safe string, otherwise
80
- # performs HTML escape on the string first. Sets the content type as <tt>text/html</tt>.
81
- # * <tt>:json</tt> - Renders the provided hash or object in JSON. You don't
82
- # need to call <tt>.to_json</tt> on the object you want to render.
83
- # * <tt>:body</tt> - Renders provided text and sets content type of <tt>text/plain</tt>.
84
- #
85
- # If no <tt>options</tt> hash is passed or if <tt>:update</tt> is specified, then:
86
- #
87
- # If an object responding to +render_in+ is passed, +render_in+ is called on the object,
88
- # passing in the current view context.
89
- #
90
- # Otherwise, a partial is rendered using the second parameter as the locals hash.
71
+ # Renders a template to a string, just like ActionController::Rendering#render_to_string.
91
72
  def render(*args)
92
73
  raise "missing controller" unless controller
93
74
 
@@ -31,7 +31,7 @@ module ActionController
31
31
 
32
32
  # ActionController::TestCase will be deprecated and moved to a gem in the future.
33
33
  # Please use ActionDispatch::IntegrationTest going forward.
34
- class TestRequest < ActionDispatch::TestRequest #:nodoc:
34
+ class TestRequest < ActionDispatch::TestRequest # :nodoc:
35
35
  DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
36
36
  DEFAULT_ENV.delete "PATH_INFO"
37
37
 
@@ -179,7 +179,7 @@ module ActionController
179
179
 
180
180
  # Methods #destroy and #load! are overridden to avoid calling methods on the
181
181
  # @store object, which does not exist for the TestSession class.
182
- class TestSession < Rack::Session::Abstract::PersistedSecure::SecureSessionHash #:nodoc:
182
+ class TestSession < Rack::Session::Abstract::PersistedSecure::SecureSessionHash # :nodoc:
183
183
  DEFAULT_OPTIONS = Rack::Session::Abstract::Persisted::DEFAULT_OPTIONS
184
184
 
185
185
  def initialize(session = {})
@@ -214,6 +214,10 @@ module ActionController
214
214
  @data.fetch(key.to_s, *args, &block)
215
215
  end
216
216
 
217
+ def enabled?
218
+ true
219
+ end
220
+
217
221
  private
218
222
  def load!
219
223
  @id
@@ -237,7 +241,7 @@ module ActionController
237
241
  # == Basic example
238
242
  #
239
243
  # Functional tests are written as follows:
240
- # 1. First, one uses the +get+, +post+, +patch+, +put+, +delete+ or +head+ method to simulate
244
+ # 1. First, one uses the +get+, +post+, +patch+, +put+, +delete+, or +head+ method to simulate
241
245
  # an HTTP request.
242
246
  # 2. Then, one asserts whether the current state is as expected. "State" can be anything:
243
247
  # the controller's HTTP response, the database contents, etc.
@@ -329,6 +333,8 @@ module ActionController
329
333
  #
330
334
  # assert_redirected_to page_url(title: 'foo')
331
335
  class TestCase < ActiveSupport::TestCase
336
+ singleton_class.attr_accessor :executor_around_each_request
337
+
332
338
  module Behavior
333
339
  extend ActiveSupport::Concern
334
340
  include ActionDispatch::TestProcess
@@ -385,7 +391,7 @@ module ActionController
385
391
  #
386
392
  # You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with
387
393
  # +post+, +patch+, +put+, +delete+, and +head+.
388
- # Example sending parameters, session and setting a flash message:
394
+ # Example sending parameters, session, and setting a flash message:
389
395
  #
390
396
  # get :show,
391
397
  # params: { id: 7 },
@@ -455,13 +461,19 @@ module ActionController
455
461
  # session: { user_id: 1 },
456
462
  # flash: { notice: 'This is flash message' }
457
463
  #
458
- # To simulate +GET+, +POST+, +PATCH+, +PUT+, +DELETE+ and +HEAD+ requests
464
+ # To simulate +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, and +HEAD+ requests
459
465
  # prefer using #get, #post, #patch, #put, #delete and #head methods
460
466
  # respectively which will make tests more expressive.
461
467
  #
468
+ # It's not recommended to make more than one request in the same test. Instance
469
+ # variables that are set in one request will not persist to the next request,
470
+ # but it's not guaranteed that all Rails internal state will be reset. Prefer
471
+ # ActionDispatch::IntegrationTest for making multiple requests in the same test.
472
+ #
462
473
  # Note that the request method is not verified.
463
474
  def process(action, method: "GET", params: nil, session: nil, body: nil, flash: {}, format: nil, xhr: false, as: nil)
464
475
  check_required_ivars
476
+ @controller.clear_instance_variables_between_requests
465
477
 
466
478
  action = +action.to_s
467
479
  http_method = method.to_s.upcase
@@ -574,10 +586,19 @@ module ActionController
574
586
  end
575
587
  end
576
588
 
589
+ def wrap_execution(&block)
590
+ if ActionController::TestCase.executor_around_each_request && defined?(Rails.application) && Rails.application
591
+ Rails.application.executor.wrap(&block)
592
+ else
593
+ yield
594
+ end
595
+ end
596
+
577
597
  def process_controller_response(action, cookies, xhr)
578
598
  begin
579
599
  @controller.recycle!
580
- @controller.dispatch(action, @request, @response)
600
+
601
+ wrap_execution { @controller.dispatch(action, @request, @response) }
581
602
  ensure
582
603
  @request = @controller.request
583
604
  @response = @controller.response
@@ -623,7 +644,7 @@ module ActionController
623
644
  end
624
645
 
625
646
  def check_required_ivars
626
- # Sanity check for required instance variables so we can give an
647
+ # Check for required instance variables so we can give an
627
648
  # understandable error message.
628
649
  [:@routes, :@controller, :@request, :@response].each do |iv_name|
629
650
  if !instance_variable_defined?(iv_name) || instance_variable_get(iv_name).nil?
@@ -3,6 +3,7 @@
3
3
  require "abstract_controller"
4
4
  require "action_dispatch"
5
5
  require "action_controller/metal/strong_parameters"
6
+ require "action_controller/metal/exceptions"
6
7
 
7
8
  module ActionController
8
9
  extend ActiveSupport::Autoload
@@ -18,10 +19,6 @@ module ActionController
18
19
  end
19
20
 
20
21
  autoload_under "metal" do
21
- eager_autoload do
22
- autoload :Live
23
- end
24
-
25
22
  autoload :ConditionalGet
26
23
  autoload :ContentSecurityPolicy
27
24
  autoload :Cookies
@@ -37,6 +34,7 @@ module ActionController
37
34
  autoload :BasicImplicitRender
38
35
  autoload :ImplicitRender
39
36
  autoload :Instrumentation
37
+ autoload :Live
40
38
  autoload :Logging
41
39
  autoload :MimeResponds
42
40
  autoload :ParamsWrapper
@@ -65,5 +63,4 @@ require "active_support/core_ext/module/attribute_accessors"
65
63
  require "active_support/core_ext/load_error"
66
64
  require "active_support/core_ext/module/attr_internal"
67
65
  require "active_support/core_ext/name_error"
68
- require "active_support/core_ext/uri"
69
66
  require "active_support/inflector"
@@ -32,8 +32,8 @@ module ActionDispatch
32
32
  end
33
33
  end
34
34
 
35
- # Check response freshness (Last-Modified and ETag) against request
36
- # If-Modified-Since and If-None-Match conditions. If both headers are
35
+ # Check response freshness (+Last-Modified+ and ETag) against request
36
+ # +If-Modified-Since+ and +If-None-Match+ conditions. If both headers are
37
37
  # supplied, both must match, or the request is not considered fresh.
38
38
  def fresh?(response)
39
39
  last_modified = if_modified_since
@@ -81,8 +81,8 @@ module ActionDispatch
81
81
 
82
82
  # This method sets a weak ETag validator on the response so browsers
83
83
  # and proxies may cache the response, keyed on the ETag. On subsequent
84
- # requests, the If-None-Match header is set to the cached ETag. If it
85
- # matches the current ETag, we can return a 304 Not Modified response
84
+ # requests, the +If-None-Match+ header is set to the cached ETag. If it
85
+ # matches the current ETag, we can return a <tt>304 Not Modified</tt> response
86
86
  # with no body, letting the browser or proxy know that their cache is
87
87
  # current. Big savings in request time and network bandwidth.
88
88
  #
@@ -92,7 +92,7 @@ module ActionDispatch
92
92
  # is viewing.
93
93
  #
94
94
  # Strong ETags are considered byte-for-byte identical. They allow a
95
- # browser or proxy cache to support Range requests, useful for paging
95
+ # browser or proxy cache to support +Range+ requests, useful for paging
96
96
  # through a PDF file or scrubbing through a video. Some CDNs only
97
97
  # support strong ETags and will ignore weak ETags entirely.
98
98
  #
@@ -112,12 +112,12 @@ module ActionDispatch
112
112
 
113
113
  def etag?; etag; end
114
114
 
115
- # True if an ETag is set and it's a weak validator (preceded with W/)
115
+ # True if an ETag is set, and it's a weak validator (preceded with <tt>W/</tt>).
116
116
  def weak_etag?
117
117
  etag? && etag.start_with?('W/"')
118
118
  end
119
119
 
120
- # True if an ETag is set and it isn't a weak validator (not preceded with W/)
120
+ # True if an ETag is set, and it isn't a weak validator (not preceded with <tt>W/</tt>).
121
121
  def strong_etag?
122
122
  etag? && !weak_etag?
123
123
  end
@@ -187,13 +187,20 @@ module ActionDispatch
187
187
 
188
188
  return if control.empty? && cache_control.empty? # Let middleware handle default behavior
189
189
 
190
- if extras = control.delete(:extras)
191
- cache_control[:extras] ||= []
192
- cache_control[:extras] += extras
193
- cache_control[:extras].uniq!
194
- end
190
+ if cache_control.any?
191
+ # Any caching directive coming from a controller overrides
192
+ # no-cache/no-store in the default Cache-Control header.
193
+ control.delete(:no_cache)
194
+ control.delete(:no_store)
195
195
 
196
- control.merge! cache_control
196
+ if extras = control.delete(:extras)
197
+ cache_control[:extras] ||= []
198
+ cache_control[:extras] += extras
199
+ cache_control[:extras].uniq!
200
+ end
201
+
202
+ control.merge! cache_control
203
+ end
197
204
 
198
205
  options = []
199
206
 
@@ -3,7 +3,24 @@
3
3
  require "active_support/core_ext/object/deep_dup"
4
4
  require "active_support/core_ext/array/wrap"
5
5
 
6
- module ActionDispatch #:nodoc:
6
+ module ActionDispatch # :nodoc:
7
+ # Configures the HTTP
8
+ # {Content-Security-Policy}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy]
9
+ # response header to help protect against XSS and injection attacks.
10
+ #
11
+ # Example global policy:
12
+ #
13
+ # Rails.application.config.content_security_policy do |policy|
14
+ # policy.default_src :self, :https
15
+ # policy.font_src :self, :https, :data
16
+ # policy.img_src :self, :https, :data
17
+ # policy.object_src :none
18
+ # policy.script_src :self, :https
19
+ # policy.style_src :self, :https
20
+ #
21
+ # # Specify URI for violation reports
22
+ # policy.report_uri "/csp-violation-report-endpoint"
23
+ # end
7
24
  class ContentSecurityPolicy
8
25
  class Middleware
9
26
  CONTENT_TYPE = "Content-Type"
@@ -16,7 +33,11 @@ module ActionDispatch #:nodoc:
16
33
 
17
34
  def call(env)
18
35
  request = ActionDispatch::Request.new env
19
- _, headers, _ = response = @app.call(env)
36
+ status, headers, _ = response = @app.call(env)
37
+
38
+ # Returning CSP headers with a 304 Not Modified is harmful, since nonces in the new
39
+ # CSP headers might not match nonces in the cached HTML.
40
+ return response if status == 304
20
41
 
21
42
  return response if policy_present?(headers)
22
43
 
@@ -100,43 +121,47 @@ module ActionDispatch #:nodoc:
100
121
  end
101
122
 
102
123
  MAPPINGS = {
103
- self: "'self'",
104
- unsafe_eval: "'unsafe-eval'",
105
- unsafe_inline: "'unsafe-inline'",
106
- none: "'none'",
107
- http: "http:",
108
- https: "https:",
109
- data: "data:",
110
- mediastream: "mediastream:",
111
- blob: "blob:",
112
- filesystem: "filesystem:",
113
- report_sample: "'report-sample'",
114
- strict_dynamic: "'strict-dynamic'",
115
- ws: "ws:",
116
- wss: "wss:"
124
+ self: "'self'",
125
+ unsafe_eval: "'unsafe-eval'",
126
+ unsafe_inline: "'unsafe-inline'",
127
+ none: "'none'",
128
+ http: "http:",
129
+ https: "https:",
130
+ data: "data:",
131
+ mediastream: "mediastream:",
132
+ allow_duplicates: "'allow-duplicates'",
133
+ blob: "blob:",
134
+ filesystem: "filesystem:",
135
+ report_sample: "'report-sample'",
136
+ script: "'script'",
137
+ strict_dynamic: "'strict-dynamic'",
138
+ ws: "ws:",
139
+ wss: "wss:"
117
140
  }.freeze
118
141
 
119
142
  DIRECTIVES = {
120
- base_uri: "base-uri",
121
- child_src: "child-src",
122
- connect_src: "connect-src",
123
- default_src: "default-src",
124
- font_src: "font-src",
125
- form_action: "form-action",
126
- frame_ancestors: "frame-ancestors",
127
- frame_src: "frame-src",
128
- img_src: "img-src",
129
- manifest_src: "manifest-src",
130
- media_src: "media-src",
131
- object_src: "object-src",
132
- prefetch_src: "prefetch-src",
133
- script_src: "script-src",
134
- script_src_attr: "script-src-attr",
135
- script_src_elem: "script-src-elem",
136
- style_src: "style-src",
137
- style_src_attr: "style-src-attr",
138
- style_src_elem: "style-src-elem",
139
- worker_src: "worker-src"
143
+ base_uri: "base-uri",
144
+ child_src: "child-src",
145
+ connect_src: "connect-src",
146
+ default_src: "default-src",
147
+ font_src: "font-src",
148
+ form_action: "form-action",
149
+ frame_ancestors: "frame-ancestors",
150
+ frame_src: "frame-src",
151
+ img_src: "img-src",
152
+ manifest_src: "manifest-src",
153
+ media_src: "media-src",
154
+ object_src: "object-src",
155
+ prefetch_src: "prefetch-src",
156
+ require_trusted_types_for: "require-trusted-types-for",
157
+ script_src: "script-src",
158
+ script_src_attr: "script-src-attr",
159
+ script_src_elem: "script-src-elem",
160
+ style_src: "style-src",
161
+ style_src_attr: "style-src-attr",
162
+ style_src_elem: "style-src-elem",
163
+ trusted_types: "trusted-types",
164
+ worker_src: "worker-src"
140
165
  }.freeze
141
166
 
142
167
  DEFAULT_NONCE_DIRECTIVES = %w[script-src style-src].freeze
@@ -164,6 +189,15 @@ module ActionDispatch #:nodoc:
164
189
  end
165
190
  end
166
191
 
192
+ # Specify whether to prevent the user agent from loading any assets over
193
+ # HTTP when the page uses HTTPS:
194
+ #
195
+ # policy.block_all_mixed_content
196
+ #
197
+ # Pass +false+ to allow it again:
198
+ #
199
+ # policy.block_all_mixed_content false
200
+ #
167
201
  def block_all_mixed_content(enabled = true)
168
202
  if enabled
169
203
  @directives["block-all-mixed-content"] = true
@@ -172,6 +206,14 @@ module ActionDispatch #:nodoc:
172
206
  end
173
207
  end
174
208
 
209
+ # Restricts the set of plugins that can be embedded:
210
+ #
211
+ # policy.plugin_types "application/x-shockwave-flash"
212
+ #
213
+ # Leave empty to allow all plugins:
214
+ #
215
+ # policy.plugin_types
216
+ #
175
217
  def plugin_types(*types)
176
218
  if types.first
177
219
  @directives["plugin-types"] = types
@@ -180,10 +222,24 @@ module ActionDispatch #:nodoc:
180
222
  end
181
223
  end
182
224
 
225
+ # Enable the {report-uri}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri]
226
+ # directive. Violation reports will be sent to the specified URI:
227
+ #
228
+ # policy.report_uri "/csp-violation-report-endpoint"
229
+ #
183
230
  def report_uri(uri)
184
231
  @directives["report-uri"] = [uri]
185
232
  end
186
233
 
234
+ # Specify asset types for which {Subresource Integrity}[https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity]
235
+ # is required:
236
+ #
237
+ # policy.require_sri_for :script, :style
238
+ #
239
+ # Leave empty to not require Subresource Integrity:
240
+ #
241
+ # policy.require_sri_for
242
+ #
187
243
  def require_sri_for(*types)
188
244
  if types.first
189
245
  @directives["require-sri-for"] = types
@@ -192,6 +248,19 @@ module ActionDispatch #:nodoc:
192
248
  end
193
249
  end
194
250
 
251
+ # Specify whether a {sandbox}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/sandbox]
252
+ # should be enabled for the requested resource:
253
+ #
254
+ # policy.sandbox
255
+ #
256
+ # Values can be passed as arguments:
257
+ #
258
+ # policy.sandbox "allow-scripts", "allow-modals"
259
+ #
260
+ # Pass +false+ to disable the sandbox:
261
+ #
262
+ # policy.sandbox false
263
+ #
195
264
  def sandbox(*values)
196
265
  if values.empty?
197
266
  @directives["sandbox"] = true
@@ -202,6 +271,14 @@ module ActionDispatch #:nodoc:
202
271
  end
203
272
  end
204
273
 
274
+ # Specify whether user agents should treat any assets over HTTP as HTTPS:
275
+ #
276
+ # policy.upgrade_insecure_requests
277
+ #
278
+ # Pass +false+ to disable it:
279
+ #
280
+ # policy.upgrade_insecure_requests false
281
+ #
205
282
  def upgrade_insecure_requests(enabled = true)
206
283
  if enabled
207
284
  @directives["upgrade-insecure-requests"] = true
@@ -4,28 +4,13 @@ require "active_support/parameter_filter"
4
4
 
5
5
  module ActionDispatch
6
6
  module Http
7
- # Allows you to specify sensitive parameters which will be replaced from
8
- # the request log by looking in the query string of the request and all
9
- # sub-hashes of the params hash to filter. Filtering only certain sub-keys
10
- # from a hash is possible by using the dot notation: 'credit_card.number'.
11
- # If a block is given, each key and value of the params hash and all
12
- # sub-hashes are passed to it, where the value or the key can be replaced using
13
- # String#replace or similar methods.
14
- #
15
- # env["action_dispatch.parameter_filter"] = [:password]
16
- # => replaces the value to all keys matching /password/i with "[FILTERED]"
7
+ # Allows you to specify sensitive query string and POST parameters to filter
8
+ # from the request log.
17
9
  #
10
+ # # Replaces values with "[FILTERED]" for keys that match /foo|bar/i.
18
11
  # env["action_dispatch.parameter_filter"] = [:foo, "bar"]
19
- # => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
20
- #
21
- # env["action_dispatch.parameter_filter"] = [ "credit_card.code" ]
22
- # => replaces { credit_card: {code: "xxxx"} } with "[FILTERED]", does not
23
- # change { file: { code: "xxxx"} }
24
12
  #
25
- # env["action_dispatch.parameter_filter"] = -> (k, v) do
26
- # v.reverse! if k.match?(/secret/i)
27
- # end
28
- # => reverses the value to all keys matching /secret/i
13
+ # For more information about filter behavior, see ActiveSupport::ParameterFilter.
29
14
  module FilterParameters
30
15
  ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc:
31
16
  NULL_PARAM_FILTER = ActiveSupport::ParameterFilter.new # :nodoc:
@@ -65,7 +65,7 @@ module ActionDispatch
65
65
  @req.set_header env_name(key), value
66
66
  end
67
67
 
68
- # Add a value to a multivalued header like Vary or Accept-Encoding.
68
+ # Add a value to a multivalued header like +Vary+ or +Accept-Encoding+.
69
69
  def add(key, value)
70
70
  @req.add_header env_name(key), value
71
71
  end