actionpack 6.1.7.5 → 7.0.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +323 -399
  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 +27 -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