actionpack 4.2.10 → 7.2.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +86 -600
- data/MIT-LICENSE +1 -1
- data/README.rdoc +13 -14
- data/lib/abstract_controller/asset_paths.rb +5 -1
- data/lib/abstract_controller/base.rb +166 -136
- data/lib/abstract_controller/caching/fragments.rb +149 -0
- data/lib/abstract_controller/caching.rb +68 -0
- data/lib/abstract_controller/callbacks.rb +126 -57
- data/lib/abstract_controller/collector.rb +13 -15
- data/lib/abstract_controller/deprecator.rb +9 -0
- data/lib/abstract_controller/error.rb +8 -0
- data/lib/abstract_controller/helpers.rb +181 -132
- data/lib/abstract_controller/logger.rb +5 -1
- data/lib/abstract_controller/railties/routes_helpers.rb +10 -3
- data/lib/abstract_controller/rendering.rb +56 -56
- data/lib/abstract_controller/translation.rb +29 -15
- data/lib/abstract_controller/url_for.rb +15 -11
- data/lib/abstract_controller.rb +21 -5
- data/lib/action_controller/api/api_rendering.rb +18 -0
- data/lib/action_controller/api.rb +154 -0
- data/lib/action_controller/base.rb +219 -155
- data/lib/action_controller/caching.rb +28 -68
- data/lib/action_controller/deprecator.rb +9 -0
- data/lib/action_controller/form_builder.rb +55 -0
- data/lib/action_controller/log_subscriber.rb +35 -22
- data/lib/action_controller/metal/allow_browser.rb +119 -0
- data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
- data/lib/action_controller/metal/conditional_get.rb +259 -122
- data/lib/action_controller/metal/content_security_policy.rb +86 -0
- data/lib/action_controller/metal/cookies.rb +9 -5
- data/lib/action_controller/metal/data_streaming.rb +87 -104
- data/lib/action_controller/metal/default_headers.rb +21 -0
- data/lib/action_controller/metal/etag_with_flash.rb +22 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +35 -26
- data/lib/action_controller/metal/exceptions.rb +71 -24
- data/lib/action_controller/metal/flash.rb +26 -19
- data/lib/action_controller/metal/head.rb +45 -36
- data/lib/action_controller/metal/helpers.rb +80 -64
- data/lib/action_controller/metal/http_authentication.rb +297 -244
- data/lib/action_controller/metal/implicit_render.rb +57 -9
- data/lib/action_controller/metal/instrumentation.rb +76 -64
- data/lib/action_controller/metal/live.rb +238 -176
- data/lib/action_controller/metal/logging.rb +22 -0
- data/lib/action_controller/metal/mime_responds.rb +177 -166
- data/lib/action_controller/metal/parameter_encoding.rb +84 -0
- data/lib/action_controller/metal/params_wrapper.rb +145 -118
- data/lib/action_controller/metal/permissions_policy.rb +38 -0
- data/lib/action_controller/metal/rate_limiting.rb +62 -0
- data/lib/action_controller/metal/redirecting.rb +203 -64
- data/lib/action_controller/metal/renderers.rb +108 -65
- data/lib/action_controller/metal/rendering.rb +216 -56
- data/lib/action_controller/metal/request_forgery_protection.rb +496 -163
- data/lib/action_controller/metal/rescue.rb +19 -21
- data/lib/action_controller/metal/streaming.rb +179 -138
- data/lib/action_controller/metal/strong_parameters.rb +1058 -382
- data/lib/action_controller/metal/testing.rb +11 -17
- data/lib/action_controller/metal/url_for.rb +37 -21
- data/lib/action_controller/metal.rb +236 -138
- data/lib/action_controller/railtie.rb +89 -11
- data/lib/action_controller/railties/helpers.rb +5 -1
- data/lib/action_controller/renderer.rb +161 -0
- data/lib/action_controller/template_assertions.rb +13 -0
- data/lib/action_controller/test_case.rb +425 -497
- data/lib/action_controller.rb +44 -22
- data/lib/action_dispatch/constants.rb +34 -0
- data/lib/action_dispatch/deprecator.rb +9 -0
- data/lib/action_dispatch/http/cache.rb +119 -63
- data/lib/action_dispatch/http/content_disposition.rb +47 -0
- data/lib/action_dispatch/http/content_security_policy.rb +364 -0
- data/lib/action_dispatch/http/filter_parameters.rb +36 -34
- data/lib/action_dispatch/http/filter_redirect.rb +24 -12
- data/lib/action_dispatch/http/headers.rb +66 -31
- data/lib/action_dispatch/http/mime_negotiation.rb +106 -75
- data/lib/action_dispatch/http/mime_type.rb +196 -136
- data/lib/action_dispatch/http/mime_types.rb +25 -7
- data/lib/action_dispatch/http/parameters.rb +97 -45
- data/lib/action_dispatch/http/permissions_policy.rb +187 -0
- data/lib/action_dispatch/http/rack_cache.rb +6 -0
- data/lib/action_dispatch/http/request.rb +299 -170
- data/lib/action_dispatch/http/response.rb +311 -160
- data/lib/action_dispatch/http/upload.rb +52 -23
- data/lib/action_dispatch/http/url.rb +201 -125
- data/lib/action_dispatch/journey/formatter.rb +110 -50
- data/lib/action_dispatch/journey/gtg/builder.rb +37 -50
- data/lib/action_dispatch/journey/gtg/simulator.rb +20 -17
- data/lib/action_dispatch/journey/gtg/transition_table.rb +96 -36
- data/lib/action_dispatch/journey/nfa/dot.rb +5 -14
- data/lib/action_dispatch/journey/nodes/node.rb +100 -20
- data/lib/action_dispatch/journey/parser.rb +19 -17
- data/lib/action_dispatch/journey/parser.y +4 -3
- data/lib/action_dispatch/journey/parser_extras.rb +14 -4
- data/lib/action_dispatch/journey/path/pattern.rb +79 -63
- data/lib/action_dispatch/journey/route.rb +108 -44
- data/lib/action_dispatch/journey/router/utils.rb +41 -29
- data/lib/action_dispatch/journey/router.rb +64 -57
- data/lib/action_dispatch/journey/routes.rb +23 -21
- data/lib/action_dispatch/journey/scanner.rb +28 -17
- data/lib/action_dispatch/journey/visitors.rb +100 -54
- data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
- data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
- data/lib/action_dispatch/journey.rb +7 -5
- data/lib/action_dispatch/log_subscriber.rb +25 -0
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
- data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
- data/lib/action_dispatch/middleware/callbacks.rb +7 -6
- data/lib/action_dispatch/middleware/cookies.rb +471 -328
- data/lib/action_dispatch/middleware/debug_exceptions.rb +149 -66
- data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
- data/lib/action_dispatch/middleware/debug_view.rb +73 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +275 -73
- data/lib/action_dispatch/middleware/executor.rb +32 -0
- data/lib/action_dispatch/middleware/flash.rb +143 -101
- data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +36 -27
- data/lib/action_dispatch/middleware/reloader.rb +10 -92
- data/lib/action_dispatch/middleware/remote_ip.rb +133 -107
- data/lib/action_dispatch/middleware/request_id.rb +29 -15
- data/lib/action_dispatch/middleware/server_timing.rb +78 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +49 -27
- data/lib/action_dispatch/middleware/session/cache_store.rb +33 -16
- data/lib/action_dispatch/middleware/session/cookie_store.rb +86 -80
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +15 -3
- data/lib/action_dispatch/middleware/show_exceptions.rb +66 -36
- data/lib/action_dispatch/middleware/ssl.rb +134 -36
- data/lib/action_dispatch/middleware/stack.rb +109 -44
- data/lib/action_dispatch/middleware/static.rb +159 -90
- data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
- data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +7 -24
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +12 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +26 -7
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +16 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +139 -15
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +23 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +6 -6
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +7 -7
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +9 -9
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +7 -4
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +125 -93
- data/lib/action_dispatch/railtie.rb +44 -16
- data/lib/action_dispatch/request/session.rb +159 -69
- data/lib/action_dispatch/request/utils.rb +97 -23
- data/lib/action_dispatch/routing/endpoint.rb +11 -2
- data/lib/action_dispatch/routing/inspector.rb +195 -106
- data/lib/action_dispatch/routing/mapper.rb +1338 -955
- data/lib/action_dispatch/routing/polymorphic_routes.rb +234 -201
- data/lib/action_dispatch/routing/redirection.rb +78 -51
- data/lib/action_dispatch/routing/route_set.rb +460 -374
- data/lib/action_dispatch/routing/routes_proxy.rb +36 -12
- data/lib/action_dispatch/routing/url_for.rb +172 -124
- data/lib/action_dispatch/routing.rb +159 -158
- data/lib/action_dispatch/system_test_case.rb +206 -0
- data/lib/action_dispatch/system_testing/browser.rb +84 -0
- data/lib/action_dispatch/system_testing/driver.rb +85 -0
- data/lib/action_dispatch/system_testing/server.rb +33 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
- data/lib/action_dispatch/testing/assertion_response.rb +48 -0
- data/lib/action_dispatch/testing/assertions/response.rb +71 -39
- data/lib/action_dispatch/testing/assertions/routing.rb +228 -103
- data/lib/action_dispatch/testing/assertions.rb +9 -6
- data/lib/action_dispatch/testing/integration.rb +486 -306
- data/lib/action_dispatch/testing/request_encoder.rb +60 -0
- data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
- data/lib/action_dispatch/testing/test_process.rb +35 -22
- data/lib/action_dispatch/testing/test_request.rb +29 -34
- data/lib/action_dispatch/testing/test_response.rb +48 -15
- data/lib/action_dispatch.rb +82 -40
- data/lib/action_pack/gem_version.rb +8 -4
- data/lib/action_pack/version.rb +6 -2
- data/lib/action_pack.rb +21 -18
- metadata +146 -56
- data/lib/action_controller/caching/fragments.rb +0 -103
- data/lib/action_controller/metal/force_ssl.rb +0 -97
- data/lib/action_controller/metal/hide_actions.rb +0 -40
- data/lib/action_controller/metal/rack_delegation.rb +0 -32
- data/lib/action_controller/middleware.rb +0 -39
- data/lib/action_controller/model_naming.rb +0 -12
- data/lib/action_dispatch/http/parameter_filter.rb +0 -72
- data/lib/action_dispatch/journey/backwards.rb +0 -5
- data/lib/action_dispatch/journey/nfa/builder.rb +0 -76
- data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
- data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -163
- data/lib/action_dispatch/journey/router/strexp.rb +0 -27
- data/lib/action_dispatch/middleware/params_parser.rb +0 -60
- data/lib/action_dispatch/middleware/templates/rescues/_source.erb +0 -27
- data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
- data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
- data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -0,0 +1,364 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
require "active_support/core_ext/object/deep_dup"
|
6
|
+
require "active_support/core_ext/array/wrap"
|
7
|
+
|
8
|
+
module ActionDispatch # :nodoc:
|
9
|
+
# # Action Dispatch Content Security Policy
|
10
|
+
#
|
11
|
+
# Configures the HTTP [Content-Security-Policy]
|
12
|
+
# (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy)
|
13
|
+
# response header to help protect against XSS and
|
14
|
+
# injection attacks.
|
15
|
+
#
|
16
|
+
# Example global policy:
|
17
|
+
#
|
18
|
+
# Rails.application.config.content_security_policy do |policy|
|
19
|
+
# policy.default_src :self, :https
|
20
|
+
# policy.font_src :self, :https, :data
|
21
|
+
# policy.img_src :self, :https, :data
|
22
|
+
# policy.object_src :none
|
23
|
+
# policy.script_src :self, :https
|
24
|
+
# policy.style_src :self, :https
|
25
|
+
#
|
26
|
+
# # Specify URI for violation reports
|
27
|
+
# policy.report_uri "/csp-violation-report-endpoint"
|
28
|
+
# end
|
29
|
+
class ContentSecurityPolicy
|
30
|
+
class Middleware
|
31
|
+
def initialize(app)
|
32
|
+
@app = app
|
33
|
+
end
|
34
|
+
|
35
|
+
def 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
|
39
|
+
# new CSP headers might not match nonces in the cached HTML.
|
40
|
+
return response if status == 304
|
41
|
+
|
42
|
+
return response if policy_present?(headers)
|
43
|
+
|
44
|
+
request = ActionDispatch::Request.new env
|
45
|
+
|
46
|
+
if policy = request.content_security_policy
|
47
|
+
nonce = request.content_security_policy_nonce
|
48
|
+
nonce_directives = request.content_security_policy_nonce_directives
|
49
|
+
context = request.controller_instance || request
|
50
|
+
headers[header_name(request)] = policy.build(context, nonce, nonce_directives)
|
51
|
+
end
|
52
|
+
|
53
|
+
response
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
def header_name(request)
|
58
|
+
if request.content_security_policy_report_only
|
59
|
+
ActionDispatch::Constants::CONTENT_SECURITY_POLICY_REPORT_ONLY
|
60
|
+
else
|
61
|
+
ActionDispatch::Constants::CONTENT_SECURITY_POLICY
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def policy_present?(headers)
|
66
|
+
headers[ActionDispatch::Constants::CONTENT_SECURITY_POLICY] ||
|
67
|
+
headers[ActionDispatch::Constants::CONTENT_SECURITY_POLICY_REPORT_ONLY]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
module Request
|
72
|
+
POLICY = "action_dispatch.content_security_policy"
|
73
|
+
POLICY_REPORT_ONLY = "action_dispatch.content_security_policy_report_only"
|
74
|
+
NONCE_GENERATOR = "action_dispatch.content_security_policy_nonce_generator"
|
75
|
+
NONCE = "action_dispatch.content_security_policy_nonce"
|
76
|
+
NONCE_DIRECTIVES = "action_dispatch.content_security_policy_nonce_directives"
|
77
|
+
|
78
|
+
def content_security_policy
|
79
|
+
get_header(POLICY)
|
80
|
+
end
|
81
|
+
|
82
|
+
def content_security_policy=(policy)
|
83
|
+
set_header(POLICY, policy)
|
84
|
+
end
|
85
|
+
|
86
|
+
def content_security_policy_report_only
|
87
|
+
get_header(POLICY_REPORT_ONLY)
|
88
|
+
end
|
89
|
+
|
90
|
+
def content_security_policy_report_only=(value)
|
91
|
+
set_header(POLICY_REPORT_ONLY, value)
|
92
|
+
end
|
93
|
+
|
94
|
+
def content_security_policy_nonce_generator
|
95
|
+
get_header(NONCE_GENERATOR)
|
96
|
+
end
|
97
|
+
|
98
|
+
def content_security_policy_nonce_generator=(generator)
|
99
|
+
set_header(NONCE_GENERATOR, generator)
|
100
|
+
end
|
101
|
+
|
102
|
+
def content_security_policy_nonce_directives
|
103
|
+
get_header(NONCE_DIRECTIVES)
|
104
|
+
end
|
105
|
+
|
106
|
+
def content_security_policy_nonce_directives=(generator)
|
107
|
+
set_header(NONCE_DIRECTIVES, generator)
|
108
|
+
end
|
109
|
+
|
110
|
+
def content_security_policy_nonce
|
111
|
+
if content_security_policy_nonce_generator
|
112
|
+
if nonce = get_header(NONCE)
|
113
|
+
nonce
|
114
|
+
else
|
115
|
+
set_header(NONCE, generate_content_security_policy_nonce)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
def generate_content_security_policy_nonce
|
122
|
+
content_security_policy_nonce_generator.call(self)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
MAPPINGS = {
|
127
|
+
self: "'self'",
|
128
|
+
unsafe_eval: "'unsafe-eval'",
|
129
|
+
unsafe_hashes: "'unsafe-hashes'",
|
130
|
+
unsafe_inline: "'unsafe-inline'",
|
131
|
+
none: "'none'",
|
132
|
+
http: "http:",
|
133
|
+
https: "https:",
|
134
|
+
data: "data:",
|
135
|
+
mediastream: "mediastream:",
|
136
|
+
allow_duplicates: "'allow-duplicates'",
|
137
|
+
blob: "blob:",
|
138
|
+
filesystem: "filesystem:",
|
139
|
+
report_sample: "'report-sample'",
|
140
|
+
script: "'script'",
|
141
|
+
strict_dynamic: "'strict-dynamic'",
|
142
|
+
ws: "ws:",
|
143
|
+
wss: "wss:"
|
144
|
+
}.freeze
|
145
|
+
|
146
|
+
DIRECTIVES = {
|
147
|
+
base_uri: "base-uri",
|
148
|
+
child_src: "child-src",
|
149
|
+
connect_src: "connect-src",
|
150
|
+
default_src: "default-src",
|
151
|
+
font_src: "font-src",
|
152
|
+
form_action: "form-action",
|
153
|
+
frame_ancestors: "frame-ancestors",
|
154
|
+
frame_src: "frame-src",
|
155
|
+
img_src: "img-src",
|
156
|
+
manifest_src: "manifest-src",
|
157
|
+
media_src: "media-src",
|
158
|
+
object_src: "object-src",
|
159
|
+
prefetch_src: "prefetch-src",
|
160
|
+
require_trusted_types_for: "require-trusted-types-for",
|
161
|
+
script_src: "script-src",
|
162
|
+
script_src_attr: "script-src-attr",
|
163
|
+
script_src_elem: "script-src-elem",
|
164
|
+
style_src: "style-src",
|
165
|
+
style_src_attr: "style-src-attr",
|
166
|
+
style_src_elem: "style-src-elem",
|
167
|
+
trusted_types: "trusted-types",
|
168
|
+
worker_src: "worker-src"
|
169
|
+
}.freeze
|
170
|
+
|
171
|
+
DEFAULT_NONCE_DIRECTIVES = %w[script-src style-src].freeze
|
172
|
+
|
173
|
+
private_constant :MAPPINGS, :DIRECTIVES, :DEFAULT_NONCE_DIRECTIVES
|
174
|
+
|
175
|
+
attr_reader :directives
|
176
|
+
|
177
|
+
def initialize
|
178
|
+
@directives = {}
|
179
|
+
yield self if block_given?
|
180
|
+
end
|
181
|
+
|
182
|
+
def initialize_copy(other)
|
183
|
+
@directives = other.directives.deep_dup
|
184
|
+
end
|
185
|
+
|
186
|
+
DIRECTIVES.each do |name, directive|
|
187
|
+
define_method(name) do |*sources|
|
188
|
+
if sources.first
|
189
|
+
@directives[directive] = apply_mappings(sources)
|
190
|
+
else
|
191
|
+
@directives.delete(directive)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# Specify whether to prevent the user agent from loading any assets over HTTP
|
197
|
+
# when the page uses HTTPS:
|
198
|
+
#
|
199
|
+
# policy.block_all_mixed_content
|
200
|
+
#
|
201
|
+
# Pass `false` to allow it again:
|
202
|
+
#
|
203
|
+
# policy.block_all_mixed_content false
|
204
|
+
#
|
205
|
+
def block_all_mixed_content(enabled = true)
|
206
|
+
if enabled
|
207
|
+
@directives["block-all-mixed-content"] = true
|
208
|
+
else
|
209
|
+
@directives.delete("block-all-mixed-content")
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Restricts the set of plugins that can be embedded:
|
214
|
+
#
|
215
|
+
# policy.plugin_types "application/x-shockwave-flash"
|
216
|
+
#
|
217
|
+
# Leave empty to allow all plugins:
|
218
|
+
#
|
219
|
+
# policy.plugin_types
|
220
|
+
#
|
221
|
+
def plugin_types(*types)
|
222
|
+
if types.first
|
223
|
+
@directives["plugin-types"] = types
|
224
|
+
else
|
225
|
+
@directives.delete("plugin-types")
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# Enable the [report-uri]
|
230
|
+
# (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri)
|
231
|
+
# directive. Violation reports will be sent to the
|
232
|
+
# specified URI:
|
233
|
+
#
|
234
|
+
# policy.report_uri "/csp-violation-report-endpoint"
|
235
|
+
#
|
236
|
+
def report_uri(uri)
|
237
|
+
@directives["report-uri"] = [uri]
|
238
|
+
end
|
239
|
+
|
240
|
+
# Specify asset types for which [Subresource Integrity]
|
241
|
+
# (https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) is required:
|
242
|
+
#
|
243
|
+
# policy.require_sri_for :script, :style
|
244
|
+
#
|
245
|
+
# Leave empty to not require Subresource Integrity:
|
246
|
+
#
|
247
|
+
# policy.require_sri_for
|
248
|
+
#
|
249
|
+
def require_sri_for(*types)
|
250
|
+
if types.first
|
251
|
+
@directives["require-sri-for"] = types
|
252
|
+
else
|
253
|
+
@directives.delete("require-sri-for")
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
# Specify whether a [sandbox]
|
258
|
+
# (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/sandbox)
|
259
|
+
# should be enabled for the requested resource:
|
260
|
+
#
|
261
|
+
# policy.sandbox
|
262
|
+
#
|
263
|
+
# Values can be passed as arguments:
|
264
|
+
#
|
265
|
+
# policy.sandbox "allow-scripts", "allow-modals"
|
266
|
+
#
|
267
|
+
# Pass `false` to disable the sandbox:
|
268
|
+
#
|
269
|
+
# policy.sandbox false
|
270
|
+
#
|
271
|
+
def sandbox(*values)
|
272
|
+
if values.empty?
|
273
|
+
@directives["sandbox"] = true
|
274
|
+
elsif values.first
|
275
|
+
@directives["sandbox"] = values
|
276
|
+
else
|
277
|
+
@directives.delete("sandbox")
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
# Specify whether user agents should treat any assets over HTTP as HTTPS:
|
282
|
+
#
|
283
|
+
# policy.upgrade_insecure_requests
|
284
|
+
#
|
285
|
+
# Pass `false` to disable it:
|
286
|
+
#
|
287
|
+
# policy.upgrade_insecure_requests false
|
288
|
+
#
|
289
|
+
def upgrade_insecure_requests(enabled = true)
|
290
|
+
if enabled
|
291
|
+
@directives["upgrade-insecure-requests"] = true
|
292
|
+
else
|
293
|
+
@directives.delete("upgrade-insecure-requests")
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
def build(context = nil, nonce = nil, nonce_directives = nil)
|
298
|
+
nonce_directives = DEFAULT_NONCE_DIRECTIVES if nonce_directives.nil?
|
299
|
+
build_directives(context, nonce, nonce_directives).compact.join("; ")
|
300
|
+
end
|
301
|
+
|
302
|
+
private
|
303
|
+
def apply_mappings(sources)
|
304
|
+
sources.map do |source|
|
305
|
+
case source
|
306
|
+
when Symbol
|
307
|
+
apply_mapping(source)
|
308
|
+
when String, Proc
|
309
|
+
source
|
310
|
+
else
|
311
|
+
raise ArgumentError, "Invalid content security policy source: #{source.inspect}"
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
def apply_mapping(source)
|
317
|
+
MAPPINGS.fetch(source) do
|
318
|
+
raise ArgumentError, "Unknown content security policy source mapping: #{source.inspect}"
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def build_directives(context, nonce, nonce_directives)
|
323
|
+
@directives.map do |directive, sources|
|
324
|
+
if sources.is_a?(Array)
|
325
|
+
if nonce && nonce_directive?(directive, nonce_directives)
|
326
|
+
"#{directive} #{build_directive(sources, context).join(' ')} 'nonce-#{nonce}'"
|
327
|
+
else
|
328
|
+
"#{directive} #{build_directive(sources, context).join(' ')}"
|
329
|
+
end
|
330
|
+
elsif sources
|
331
|
+
directive
|
332
|
+
else
|
333
|
+
nil
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
def build_directive(sources, context)
|
339
|
+
sources.map { |source| resolve_source(source, context) }
|
340
|
+
end
|
341
|
+
|
342
|
+
def resolve_source(source, context)
|
343
|
+
case source
|
344
|
+
when String
|
345
|
+
source
|
346
|
+
when Symbol
|
347
|
+
source.to_s
|
348
|
+
when Proc
|
349
|
+
if context.nil?
|
350
|
+
raise RuntimeError, "Missing context for the dynamic content security policy source: #{source.inspect}"
|
351
|
+
else
|
352
|
+
resolved = context.instance_exec(&source)
|
353
|
+
apply_mappings(Array.wrap(resolved))
|
354
|
+
end
|
355
|
+
else
|
356
|
+
raise RuntimeError, "Unexpected content security policy source: #{source.inspect}"
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
def nonce_directive?(directive, nonce_directives)
|
361
|
+
nonce_directives.include?(directive)
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
@@ -1,76 +1,78 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
require "active_support/parameter_filter"
|
4
6
|
|
5
7
|
module ActionDispatch
|
6
8
|
module Http
|
7
|
-
#
|
8
|
-
# the request log by looking in the query string of the request and all
|
9
|
-
# sub-hashes of the params hash to filter. If a block is given, each key and
|
10
|
-
# value of the params hash and all sub-hashes is passed to it, the value
|
11
|
-
# or key can be replaced using String#replace or similar method.
|
9
|
+
# # Action Dispatch HTTP Filter Parameters
|
12
10
|
#
|
13
|
-
#
|
14
|
-
#
|
11
|
+
# Allows you to specify sensitive query string and POST parameters to filter
|
12
|
+
# from the request log.
|
15
13
|
#
|
16
|
-
#
|
17
|
-
#
|
14
|
+
# # Replaces values with "[FILTERED]" for keys that match /foo|bar/i.
|
15
|
+
# env["action_dispatch.parameter_filter"] = [:foo, "bar"]
|
18
16
|
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
# end
|
22
|
-
# => reverses the value to all keys matching /secret/i
|
17
|
+
# For more information about filter behavior, see
|
18
|
+
# ActiveSupport::ParameterFilter.
|
23
19
|
module FilterParameters
|
24
20
|
ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc:
|
25
|
-
NULL_PARAM_FILTER = ParameterFilter.new # :nodoc:
|
26
|
-
NULL_ENV_FILTER = ParameterFilter.new ENV_MATCH # :nodoc:
|
21
|
+
NULL_PARAM_FILTER = ActiveSupport::ParameterFilter.new # :nodoc:
|
22
|
+
NULL_ENV_FILTER = ActiveSupport::ParameterFilter.new ENV_MATCH # :nodoc:
|
27
23
|
|
28
|
-
def initialize
|
24
|
+
def initialize
|
29
25
|
super
|
30
26
|
@filtered_parameters = nil
|
31
27
|
@filtered_env = nil
|
32
28
|
@filtered_path = nil
|
29
|
+
@parameter_filter = nil
|
33
30
|
end
|
34
31
|
|
35
|
-
#
|
32
|
+
# Returns a hash of parameters with all sensitive data replaced.
|
36
33
|
def filtered_parameters
|
37
34
|
@filtered_parameters ||= parameter_filter.filter(parameters)
|
35
|
+
rescue ActionDispatch::Http::Parameters::ParseError
|
36
|
+
@filtered_parameters = {}
|
38
37
|
end
|
39
38
|
|
40
|
-
#
|
39
|
+
# Returns a hash of request.env with all sensitive data replaced.
|
41
40
|
def filtered_env
|
42
41
|
@filtered_env ||= env_filter.filter(@env)
|
43
42
|
end
|
44
43
|
|
45
|
-
#
|
44
|
+
# Reconstructs a path with all sensitive GET parameters replaced.
|
46
45
|
def filtered_path
|
47
46
|
@filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}"
|
48
47
|
end
|
49
48
|
|
50
|
-
|
51
|
-
|
49
|
+
# Returns the `ActiveSupport::ParameterFilter` object used to filter in this
|
50
|
+
# request.
|
52
51
|
def parameter_filter
|
53
|
-
|
54
|
-
|
55
|
-
|
52
|
+
@parameter_filter ||= if has_header?("action_dispatch.parameter_filter")
|
53
|
+
parameter_filter_for get_header("action_dispatch.parameter_filter")
|
54
|
+
else
|
55
|
+
NULL_PARAM_FILTER
|
56
|
+
end
|
56
57
|
end
|
57
58
|
|
58
|
-
|
59
|
-
|
59
|
+
private
|
60
|
+
def env_filter # :doc:
|
61
|
+
user_key = fetch_header("action_dispatch.parameter_filter") {
|
60
62
|
return NULL_ENV_FILTER
|
61
63
|
}
|
62
64
|
parameter_filter_for(Array(user_key) + ENV_MATCH)
|
63
65
|
end
|
64
66
|
|
65
|
-
def parameter_filter_for(filters)
|
66
|
-
ParameterFilter.new(filters)
|
67
|
+
def parameter_filter_for(filters) # :doc:
|
68
|
+
ActiveSupport::ParameterFilter.new(filters)
|
67
69
|
end
|
68
70
|
|
69
|
-
KV_RE =
|
71
|
+
KV_RE = "[^&;=]+"
|
70
72
|
PAIR_RE = %r{(#{KV_RE})=(#{KV_RE})}
|
71
|
-
def filtered_query_string
|
73
|
+
def filtered_query_string # :doc:
|
72
74
|
query_string.gsub(PAIR_RE) do |_|
|
73
|
-
parameter_filter.filter(
|
75
|
+
parameter_filter.filter($1 => $2).first.join("=")
|
74
76
|
end
|
75
77
|
end
|
76
78
|
end
|
@@ -1,38 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
1
5
|
module ActionDispatch
|
2
6
|
module Http
|
3
7
|
module FilterRedirect
|
8
|
+
FILTERED = "[FILTERED]" # :nodoc:
|
4
9
|
|
5
|
-
|
6
|
-
|
7
|
-
def filtered_location
|
8
|
-
filters = location_filter
|
9
|
-
if !filters.empty? && location_filter_match?(filters)
|
10
|
+
def filtered_location # :nodoc:
|
11
|
+
if location_filter_match?
|
10
12
|
FILTERED
|
11
13
|
else
|
12
|
-
|
14
|
+
parameter_filtered_location
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
16
18
|
private
|
17
|
-
|
18
|
-
def location_filter
|
19
|
+
def location_filters
|
19
20
|
if request
|
20
|
-
request.
|
21
|
+
request.get_header("action_dispatch.redirect_filter") || []
|
21
22
|
else
|
22
23
|
[]
|
23
24
|
end
|
24
25
|
end
|
25
26
|
|
26
|
-
def location_filter_match?
|
27
|
-
|
27
|
+
def location_filter_match?
|
28
|
+
location_filters.any? do |filter|
|
28
29
|
if String === filter
|
29
30
|
location.include?(filter)
|
30
31
|
elsif Regexp === filter
|
31
|
-
location.match(filter)
|
32
|
+
location.match?(filter)
|
32
33
|
end
|
33
34
|
end
|
34
35
|
end
|
35
36
|
|
37
|
+
def parameter_filtered_location
|
38
|
+
uri = URI.parse(location)
|
39
|
+
unless uri.query.nil? || uri.query.empty?
|
40
|
+
uri.query.gsub!(FilterParameters::PAIR_RE) do
|
41
|
+
request.parameter_filter.filter($1 => $2).first.join("=")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
uri.to_s
|
45
|
+
rescue URI::Error
|
46
|
+
FILTERED
|
47
|
+
end
|
36
48
|
end
|
37
49
|
end
|
38
50
|
end
|
@@ -1,10 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
1
5
|
module ActionDispatch
|
2
6
|
module Http
|
7
|
+
# # Action Dispatch HTTP Headers
|
8
|
+
#
|
3
9
|
# Provides access to the request's HTTP headers from the environment.
|
4
10
|
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
11
|
+
# env = { "CONTENT_TYPE" => "text/plain", "HTTP_USER_AGENT" => "curl/7.43.0" }
|
12
|
+
# headers = ActionDispatch::Http::Headers.from_hash(env)
|
13
|
+
# headers["Content-Type"] # => "text/plain"
|
14
|
+
# headers["User-Agent"] # => "curl/7.43.0"
|
15
|
+
#
|
16
|
+
# Also note that when headers are mapped to CGI-like variables by the Rack
|
17
|
+
# server, both dashes and underscores are converted to underscores. This
|
18
|
+
# ambiguity cannot be resolved at this stage anymore. Both underscores and
|
19
|
+
# dashes have to be interpreted as if they were originally sent as dashes.
|
20
|
+
#
|
21
|
+
# # GET / HTTP/1.1
|
22
|
+
# # ...
|
23
|
+
# # User-Agent: curl/7.43.0
|
24
|
+
# # X_Custom_Header: token
|
25
|
+
#
|
26
|
+
# headers["X_Custom_Header"] # => nil
|
27
|
+
# headers["X-Custom-Header"] # => "token"
|
8
28
|
class Headers
|
9
29
|
CGI_VARIABLES = Set.new(%W[
|
10
30
|
AUTH_TYPE
|
@@ -30,70 +50,85 @@ module ActionDispatch
|
|
30
50
|
HTTP_HEADER = /\A[A-Za-z0-9-]+\z/
|
31
51
|
|
32
52
|
include Enumerable
|
33
|
-
attr_reader :env
|
34
53
|
|
35
|
-
def
|
36
|
-
|
54
|
+
def self.from_hash(hash)
|
55
|
+
new ActionDispatch::Request.new hash
|
56
|
+
end
|
57
|
+
|
58
|
+
def initialize(request) # :nodoc:
|
59
|
+
@req = request
|
37
60
|
end
|
38
61
|
|
39
62
|
# Returns the value for the given key mapped to @env.
|
40
63
|
def [](key)
|
41
|
-
@
|
64
|
+
@req.get_header env_name(key)
|
42
65
|
end
|
43
66
|
|
44
67
|
# Sets the given value for the key mapped to @env.
|
45
68
|
def []=(key, value)
|
46
|
-
@
|
69
|
+
@req.set_header env_name(key), value
|
70
|
+
end
|
71
|
+
|
72
|
+
# Add a value to a multivalued header like `Vary` or `Accept-Encoding`.
|
73
|
+
def add(key, value)
|
74
|
+
@req.add_header env_name(key), value
|
47
75
|
end
|
48
76
|
|
49
77
|
def key?(key)
|
50
|
-
@
|
78
|
+
@req.has_header? env_name(key)
|
51
79
|
end
|
52
80
|
alias :include? :key?
|
53
81
|
|
82
|
+
DEFAULT = Object.new # :nodoc:
|
83
|
+
|
54
84
|
# Returns the value for the given key mapped to @env.
|
55
85
|
#
|
56
|
-
# If the key is not found and an optional code block is not provided,
|
57
|
-
#
|
86
|
+
# If the key is not found and an optional code block is not provided, raises a
|
87
|
+
# `KeyError` exception.
|
58
88
|
#
|
59
|
-
# If the code block is provided, then it will be run and
|
60
|
-
|
61
|
-
|
62
|
-
|
89
|
+
# If the code block is provided, then it will be run and its result returned.
|
90
|
+
def fetch(key, default = DEFAULT)
|
91
|
+
@req.fetch_header(env_name(key)) do
|
92
|
+
return default unless default == DEFAULT
|
93
|
+
return yield if block_given?
|
94
|
+
raise KeyError, key
|
95
|
+
end
|
63
96
|
end
|
64
97
|
|
65
98
|
def each(&block)
|
66
|
-
@
|
99
|
+
@req.each_header(&block)
|
67
100
|
end
|
68
101
|
|
69
102
|
# Returns a new Http::Headers instance containing the contents of
|
70
|
-
#
|
103
|
+
# `headers_or_env` and the original instance.
|
71
104
|
def merge(headers_or_env)
|
72
|
-
headers =
|
105
|
+
headers = @req.dup.headers
|
73
106
|
headers.merge!(headers_or_env)
|
74
107
|
headers
|
75
108
|
end
|
76
109
|
|
77
|
-
# Adds the contents of
|
78
|
-
#
|
79
|
-
# <tt>headers_or_env</tt>.
|
110
|
+
# Adds the contents of `headers_or_env` to original instance entries; duplicate
|
111
|
+
# keys are overwritten with the values from `headers_or_env`.
|
80
112
|
def merge!(headers_or_env)
|
81
113
|
headers_or_env.each do |key, value|
|
82
|
-
|
114
|
+
@req.set_header env_name(key), value
|
83
115
|
end
|
84
116
|
end
|
85
117
|
|
118
|
+
def env; @req.env.dup; end
|
119
|
+
|
86
120
|
private
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
121
|
+
# Converts an HTTP header name to an environment variable name if it is not
|
122
|
+
# contained within the headers hash.
|
123
|
+
def env_name(key)
|
124
|
+
key = key.to_s
|
125
|
+
if HTTP_HEADER.match?(key)
|
126
|
+
key = key.upcase
|
127
|
+
key.tr!("-", "_")
|
128
|
+
key.prepend("HTTP_") unless CGI_VARIABLES.include?(key)
|
129
|
+
end
|
130
|
+
key
|
94
131
|
end
|
95
|
-
key
|
96
|
-
end
|
97
132
|
end
|
98
133
|
end
|
99
134
|
end
|