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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +132 -490
- data/README.rdoc +1 -1
- data/lib/abstract_controller.rb +2 -0
- data/lib/abstract_controller/asset_paths.rb +2 -0
- data/lib/abstract_controller/base.rb +10 -2
- data/lib/abstract_controller/caching.rb +3 -2
- data/lib/abstract_controller/caching/fragments.rb +30 -7
- data/lib/abstract_controller/callbacks.rb +25 -3
- data/lib/abstract_controller/collector.rb +2 -0
- data/lib/abstract_controller/error.rb +2 -0
- data/lib/abstract_controller/helpers.rb +4 -5
- data/lib/abstract_controller/logger.rb +2 -0
- data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
- data/lib/abstract_controller/rendering.rb +9 -16
- data/lib/abstract_controller/translation.rb +2 -0
- data/lib/abstract_controller/url_for.rb +2 -0
- data/lib/action_controller.rb +3 -0
- data/lib/action_controller/api.rb +2 -0
- data/lib/action_controller/api/api_rendering.rb +2 -0
- data/lib/action_controller/base.rb +3 -0
- data/lib/action_controller/caching.rb +2 -0
- data/lib/action_controller/form_builder.rb +2 -0
- data/lib/action_controller/log_subscriber.rb +5 -3
- data/lib/action_controller/metal.rb +3 -2
- data/lib/action_controller/metal/basic_implicit_render.rb +2 -0
- data/lib/action_controller/metal/conditional_get.rb +4 -3
- data/lib/action_controller/metal/content_security_policy.rb +26 -0
- data/lib/action_controller/metal/cookies.rb +2 -0
- data/lib/action_controller/metal/data_streaming.rb +7 -5
- data/lib/action_controller/metal/etag_with_flash.rb +2 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +3 -2
- data/lib/action_controller/metal/exceptions.rb +2 -3
- data/lib/action_controller/metal/flash.rb +3 -2
- data/lib/action_controller/metal/force_ssl.rb +2 -0
- data/lib/action_controller/metal/head.rb +2 -0
- data/lib/action_controller/metal/helpers.rb +4 -3
- data/lib/action_controller/metal/http_authentication.rb +8 -9
- data/lib/action_controller/metal/implicit_render.rb +2 -0
- data/lib/action_controller/metal/instrumentation.rb +4 -6
- data/lib/action_controller/metal/live.rb +3 -1
- data/lib/action_controller/metal/mime_responds.rb +3 -1
- data/lib/action_controller/metal/parameter_encoding.rb +2 -0
- data/lib/action_controller/metal/params_wrapper.rb +13 -9
- data/lib/action_controller/metal/redirecting.rb +21 -10
- data/lib/action_controller/metal/renderers.rb +4 -3
- data/lib/action_controller/metal/rendering.rb +2 -2
- data/lib/action_controller/metal/request_forgery_protection.rb +22 -6
- data/lib/action_controller/metal/rescue.rb +5 -3
- data/lib/action_controller/metal/streaming.rb +2 -0
- data/lib/action_controller/metal/strong_parameters.rb +19 -11
- data/lib/action_controller/metal/testing.rb +2 -6
- data/lib/action_controller/metal/url_for.rb +2 -0
- data/lib/action_controller/railtie.rb +16 -4
- data/lib/action_controller/railties/helpers.rb +2 -0
- data/lib/action_controller/renderer.rb +2 -0
- data/lib/action_controller/template_assertions.rb +2 -0
- data/lib/action_controller/test_case.rb +4 -1
- data/lib/action_dispatch.rb +3 -0
- data/lib/action_dispatch/http/cache.rb +15 -9
- data/lib/action_dispatch/http/content_security_policy.rb +233 -0
- data/lib/action_dispatch/http/filter_parameters.rb +4 -2
- data/lib/action_dispatch/http/filter_redirect.rb +2 -0
- data/lib/action_dispatch/http/headers.rb +2 -0
- data/lib/action_dispatch/http/mime_negotiation.rb +4 -13
- data/lib/action_dispatch/http/mime_type.rb +15 -13
- data/lib/action_dispatch/http/mime_types.rb +4 -2
- data/lib/action_dispatch/http/parameter_filter.rb +2 -0
- data/lib/action_dispatch/http/parameters.rb +6 -9
- data/lib/action_dispatch/http/rack_cache.rb +2 -0
- data/lib/action_dispatch/http/request.rb +36 -16
- data/lib/action_dispatch/http/response.rb +11 -9
- data/lib/action_dispatch/http/upload.rb +2 -0
- data/lib/action_dispatch/http/url.rb +4 -5
- data/lib/action_dispatch/journey.rb +2 -0
- data/lib/action_dispatch/journey/formatter.rb +4 -2
- data/lib/action_dispatch/journey/gtg/builder.rb +2 -0
- data/lib/action_dispatch/journey/gtg/simulator.rb +2 -8
- data/lib/action_dispatch/journey/gtg/transition_table.rb +3 -2
- data/lib/action_dispatch/journey/nfa/builder.rb +2 -0
- data/lib/action_dispatch/journey/nfa/dot.rb +2 -0
- data/lib/action_dispatch/journey/nfa/simulator.rb +2 -0
- data/lib/action_dispatch/journey/nfa/transition_table.rb +2 -0
- data/lib/action_dispatch/journey/nodes/node.rb +2 -0
- data/lib/action_dispatch/journey/parser_extras.rb +2 -0
- data/lib/action_dispatch/journey/path/pattern.rb +2 -0
- data/lib/action_dispatch/journey/route.rb +15 -6
- data/lib/action_dispatch/journey/router.rb +3 -1
- data/lib/action_dispatch/journey/router/utils.rb +14 -7
- data/lib/action_dispatch/journey/routes.rb +2 -1
- data/lib/action_dispatch/journey/scanner.rb +1 -0
- data/lib/action_dispatch/journey/visitors.rb +5 -3
- data/lib/action_dispatch/middleware/callbacks.rb +2 -0
- data/lib/action_dispatch/middleware/cookies.rb +141 -91
- data/lib/action_dispatch/middleware/debug_exceptions.rb +4 -2
- data/lib/action_dispatch/middleware/debug_locks.rb +9 -7
- data/lib/action_dispatch/middleware/exception_wrapper.rb +4 -6
- data/lib/action_dispatch/middleware/executor.rb +2 -0
- data/lib/action_dispatch/middleware/flash.rb +3 -1
- data/lib/action_dispatch/middleware/public_exceptions.rb +6 -4
- data/lib/action_dispatch/middleware/reloader.rb +2 -0
- data/lib/action_dispatch/middleware/remote_ip.rb +7 -5
- data/lib/action_dispatch/middleware/request_id.rb +2 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +3 -1
- data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
- data/lib/action_dispatch/middleware/session/cookie_store.rb +13 -25
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +2 -0
- data/lib/action_dispatch/middleware/show_exceptions.rb +3 -1
- data/lib/action_dispatch/middleware/ssl.rb +42 -37
- data/lib/action_dispatch/middleware/stack.rb +2 -0
- data/lib/action_dispatch/middleware/static.rb +10 -8
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +1 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +6 -2
- data/lib/action_dispatch/railtie.rb +7 -0
- data/lib/action_dispatch/request/session.rb +8 -4
- data/lib/action_dispatch/request/utils.rb +4 -4
- data/lib/action_dispatch/routing.rb +3 -1
- data/lib/action_dispatch/routing/endpoint.rb +8 -4
- data/lib/action_dispatch/routing/inspector.rb +5 -3
- data/lib/action_dispatch/routing/mapper.rb +62 -51
- data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
- data/lib/action_dispatch/routing/redirection.rb +7 -5
- data/lib/action_dispatch/routing/route_set.rb +26 -33
- data/lib/action_dispatch/routing/routes_proxy.rb +5 -2
- data/lib/action_dispatch/routing/url_for.rb +6 -4
- data/lib/action_dispatch/system_test_case.rb +14 -6
- data/lib/action_dispatch/system_testing/driver.rb +20 -2
- data/lib/action_dispatch/system_testing/server.rb +2 -16
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +6 -4
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +2 -0
- data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
- data/lib/action_dispatch/testing/assertion_response.rb +2 -0
- data/lib/action_dispatch/testing/assertions.rb +2 -0
- data/lib/action_dispatch/testing/assertions/response.rb +4 -2
- data/lib/action_dispatch/testing/assertions/routing.rb +5 -5
- data/lib/action_dispatch/testing/integration.rb +24 -21
- data/lib/action_dispatch/testing/request_encoder.rb +2 -0
- data/lib/action_dispatch/testing/test_process.rb +2 -0
- data/lib/action_dispatch/testing/test_request.rb +3 -1
- data/lib/action_dispatch/testing/test_response.rb +23 -3
- data/lib/action_pack.rb +2 -0
- data/lib/action_pack/gem_version.rb +5 -3
- data/lib/action_pack/version.rb +2 -0
- metadata +17 -13
@@ -1,4 +1,4 @@
|
|
1
|
-
|
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
|
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
|
-
#
|
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
|
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
|
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.
|
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.
|
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
|
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
|
12
|
-
# false
|
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 "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
|
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
|
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.
|
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
|
673
|
-
#
|
674
|
-
#
|
675
|
-
#
|
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
|
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,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
|
-
|
23
|
+
silence_redefinition_of_method :new_controller_thread
|
21
24
|
def new_controller_thread # :nodoc:
|
22
25
|
yield
|
23
26
|
end
|
data/lib/action_dispatch.rb
CHANGED
@@ -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
|
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
|
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
|
-
|
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
|