omg-actionpack 8.0.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +129 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +57 -0
- data/lib/abstract_controller/asset_paths.rb +14 -0
- data/lib/abstract_controller/base.rb +299 -0
- data/lib/abstract_controller/caching/fragments.rb +149 -0
- data/lib/abstract_controller/caching.rb +68 -0
- data/lib/abstract_controller/callbacks.rb +265 -0
- data/lib/abstract_controller/collector.rb +44 -0
- data/lib/abstract_controller/deprecator.rb +9 -0
- data/lib/abstract_controller/error.rb +8 -0
- data/lib/abstract_controller/helpers.rb +243 -0
- data/lib/abstract_controller/logger.rb +16 -0
- data/lib/abstract_controller/railties/routes_helpers.rb +25 -0
- data/lib/abstract_controller/rendering.rb +126 -0
- data/lib/abstract_controller/translation.rb +42 -0
- data/lib/abstract_controller/url_for.rb +37 -0
- data/lib/abstract_controller.rb +36 -0
- data/lib/action_controller/api/api_rendering.rb +18 -0
- data/lib/action_controller/api.rb +155 -0
- data/lib/action_controller/base.rb +332 -0
- data/lib/action_controller/caching.rb +49 -0
- data/lib/action_controller/deprecator.rb +9 -0
- data/lib/action_controller/form_builder.rb +55 -0
- data/lib/action_controller/log_subscriber.rb +96 -0
- data/lib/action_controller/metal/allow_browser.rb +123 -0
- data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
- data/lib/action_controller/metal/conditional_get.rb +341 -0
- data/lib/action_controller/metal/content_security_policy.rb +86 -0
- data/lib/action_controller/metal/cookies.rb +20 -0
- data/lib/action_controller/metal/data_streaming.rb +154 -0
- 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 +59 -0
- data/lib/action_controller/metal/exceptions.rb +106 -0
- data/lib/action_controller/metal/flash.rb +67 -0
- data/lib/action_controller/metal/head.rb +67 -0
- data/lib/action_controller/metal/helpers.rb +129 -0
- data/lib/action_controller/metal/http_authentication.rb +565 -0
- data/lib/action_controller/metal/implicit_render.rb +67 -0
- data/lib/action_controller/metal/instrumentation.rb +120 -0
- data/lib/action_controller/metal/live.rb +398 -0
- data/lib/action_controller/metal/logging.rb +22 -0
- data/lib/action_controller/metal/mime_responds.rb +337 -0
- data/lib/action_controller/metal/parameter_encoding.rb +84 -0
- data/lib/action_controller/metal/params_wrapper.rb +312 -0
- 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 +251 -0
- data/lib/action_controller/metal/renderers.rb +181 -0
- data/lib/action_controller/metal/rendering.rb +260 -0
- data/lib/action_controller/metal/request_forgery_protection.rb +667 -0
- data/lib/action_controller/metal/rescue.rb +33 -0
- data/lib/action_controller/metal/streaming.rb +183 -0
- data/lib/action_controller/metal/strong_parameters.rb +1546 -0
- data/lib/action_controller/metal/testing.rb +25 -0
- data/lib/action_controller/metal/url_for.rb +65 -0
- data/lib/action_controller/metal.rb +339 -0
- data/lib/action_controller/railtie.rb +149 -0
- data/lib/action_controller/railties/helpers.rb +26 -0
- data/lib/action_controller/renderer.rb +161 -0
- data/lib/action_controller/template_assertions.rb +13 -0
- data/lib/action_controller/test_case.rb +691 -0
- data/lib/action_controller.rb +80 -0
- data/lib/action_dispatch/constants.rb +34 -0
- data/lib/action_dispatch/deprecator.rb +9 -0
- data/lib/action_dispatch/http/cache.rb +249 -0
- data/lib/action_dispatch/http/content_disposition.rb +47 -0
- data/lib/action_dispatch/http/content_security_policy.rb +365 -0
- data/lib/action_dispatch/http/filter_parameters.rb +80 -0
- data/lib/action_dispatch/http/filter_redirect.rb +50 -0
- data/lib/action_dispatch/http/headers.rb +134 -0
- data/lib/action_dispatch/http/mime_negotiation.rb +187 -0
- data/lib/action_dispatch/http/mime_type.rb +389 -0
- data/lib/action_dispatch/http/mime_types.rb +54 -0
- data/lib/action_dispatch/http/parameters.rb +119 -0
- data/lib/action_dispatch/http/permissions_policy.rb +189 -0
- data/lib/action_dispatch/http/rack_cache.rb +67 -0
- data/lib/action_dispatch/http/request.rb +498 -0
- data/lib/action_dispatch/http/response.rb +556 -0
- data/lib/action_dispatch/http/upload.rb +107 -0
- data/lib/action_dispatch/http/url.rb +344 -0
- data/lib/action_dispatch/journey/formatter.rb +226 -0
- data/lib/action_dispatch/journey/gtg/builder.rb +149 -0
- data/lib/action_dispatch/journey/gtg/simulator.rb +50 -0
- data/lib/action_dispatch/journey/gtg/transition_table.rb +217 -0
- data/lib/action_dispatch/journey/nfa/dot.rb +27 -0
- data/lib/action_dispatch/journey/nodes/node.rb +208 -0
- data/lib/action_dispatch/journey/parser.rb +103 -0
- data/lib/action_dispatch/journey/path/pattern.rb +209 -0
- data/lib/action_dispatch/journey/route.rb +189 -0
- data/lib/action_dispatch/journey/router/utils.rb +105 -0
- data/lib/action_dispatch/journey/router.rb +151 -0
- data/lib/action_dispatch/journey/routes.rb +82 -0
- data/lib/action_dispatch/journey/scanner.rb +70 -0
- data/lib/action_dispatch/journey/visitors.rb +267 -0
- data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
- data/lib/action_dispatch/journey/visualizer/fsm.js +159 -0
- data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
- data/lib/action_dispatch/journey.rb +7 -0
- 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 +38 -0
- data/lib/action_dispatch/middleware/cookies.rb +719 -0
- data/lib/action_dispatch/middleware/debug_exceptions.rb +206 -0
- 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 +350 -0
- data/lib/action_dispatch/middleware/executor.rb +32 -0
- data/lib/action_dispatch/middleware/flash.rb +318 -0
- data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +64 -0
- data/lib/action_dispatch/middleware/reloader.rb +16 -0
- data/lib/action_dispatch/middleware/remote_ip.rb +199 -0
- data/lib/action_dispatch/middleware/request_id.rb +50 -0
- data/lib/action_dispatch/middleware/server_timing.rb +78 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +112 -0
- data/lib/action_dispatch/middleware/session/cache_store.rb +66 -0
- data/lib/action_dispatch/middleware/session/cookie_store.rb +129 -0
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +34 -0
- data/lib/action_dispatch/middleware/show_exceptions.rb +88 -0
- data/lib/action_dispatch/middleware/ssl.rb +180 -0
- data/lib/action_dispatch/middleware/stack.rb +194 -0
- data/lib/action_dispatch/middleware/static.rb +192 -0
- 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 +17 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
- 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 +62 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
- 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 +35 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
- 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 +284 -0
- 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 +11 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +19 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +232 -0
- data/lib/action_dispatch/railtie.rb +77 -0
- data/lib/action_dispatch/request/session.rb +283 -0
- data/lib/action_dispatch/request/utils.rb +109 -0
- data/lib/action_dispatch/routing/endpoint.rb +19 -0
- data/lib/action_dispatch/routing/inspector.rb +323 -0
- data/lib/action_dispatch/routing/mapper.rb +2372 -0
- data/lib/action_dispatch/routing/polymorphic_routes.rb +363 -0
- data/lib/action_dispatch/routing/redirection.rb +218 -0
- data/lib/action_dispatch/routing/route_set.rb +958 -0
- data/lib/action_dispatch/routing/routes_proxy.rb +66 -0
- data/lib/action_dispatch/routing/url_for.rb +244 -0
- data/lib/action_dispatch/routing.rb +262 -0
- data/lib/action_dispatch/system_test_case.rb +206 -0
- data/lib/action_dispatch/system_testing/browser.rb +75 -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 +114 -0
- data/lib/action_dispatch/testing/assertions/routing.rb +343 -0
- data/lib/action_dispatch/testing/assertions.rb +25 -0
- data/lib/action_dispatch/testing/integration.rb +694 -0
- 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 +57 -0
- data/lib/action_dispatch/testing/test_request.rb +73 -0
- data/lib/action_dispatch/testing/test_response.rb +58 -0
- data/lib/action_dispatch.rb +147 -0
- data/lib/action_pack/gem_version.rb +19 -0
- data/lib/action_pack/version.rb +12 -0
- data/lib/action_pack.rb +27 -0
- metadata +375 -0
@@ -0,0 +1,365 @@
|
|
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
|
+
wasm_unsafe_eval: "'wasm-unsafe-eval'",
|
130
|
+
unsafe_hashes: "'unsafe-hashes'",
|
131
|
+
unsafe_inline: "'unsafe-inline'",
|
132
|
+
none: "'none'",
|
133
|
+
http: "http:",
|
134
|
+
https: "https:",
|
135
|
+
data: "data:",
|
136
|
+
mediastream: "mediastream:",
|
137
|
+
allow_duplicates: "'allow-duplicates'",
|
138
|
+
blob: "blob:",
|
139
|
+
filesystem: "filesystem:",
|
140
|
+
report_sample: "'report-sample'",
|
141
|
+
script: "'script'",
|
142
|
+
strict_dynamic: "'strict-dynamic'",
|
143
|
+
ws: "ws:",
|
144
|
+
wss: "wss:"
|
145
|
+
}.freeze
|
146
|
+
|
147
|
+
DIRECTIVES = {
|
148
|
+
base_uri: "base-uri",
|
149
|
+
child_src: "child-src",
|
150
|
+
connect_src: "connect-src",
|
151
|
+
default_src: "default-src",
|
152
|
+
font_src: "font-src",
|
153
|
+
form_action: "form-action",
|
154
|
+
frame_ancestors: "frame-ancestors",
|
155
|
+
frame_src: "frame-src",
|
156
|
+
img_src: "img-src",
|
157
|
+
manifest_src: "manifest-src",
|
158
|
+
media_src: "media-src",
|
159
|
+
object_src: "object-src",
|
160
|
+
prefetch_src: "prefetch-src",
|
161
|
+
require_trusted_types_for: "require-trusted-types-for",
|
162
|
+
script_src: "script-src",
|
163
|
+
script_src_attr: "script-src-attr",
|
164
|
+
script_src_elem: "script-src-elem",
|
165
|
+
style_src: "style-src",
|
166
|
+
style_src_attr: "style-src-attr",
|
167
|
+
style_src_elem: "style-src-elem",
|
168
|
+
trusted_types: "trusted-types",
|
169
|
+
worker_src: "worker-src"
|
170
|
+
}.freeze
|
171
|
+
|
172
|
+
DEFAULT_NONCE_DIRECTIVES = %w[script-src style-src].freeze
|
173
|
+
|
174
|
+
private_constant :MAPPINGS, :DIRECTIVES, :DEFAULT_NONCE_DIRECTIVES
|
175
|
+
|
176
|
+
attr_reader :directives
|
177
|
+
|
178
|
+
def initialize
|
179
|
+
@directives = {}
|
180
|
+
yield self if block_given?
|
181
|
+
end
|
182
|
+
|
183
|
+
def initialize_copy(other)
|
184
|
+
@directives = other.directives.deep_dup
|
185
|
+
end
|
186
|
+
|
187
|
+
DIRECTIVES.each do |name, directive|
|
188
|
+
define_method(name) do |*sources|
|
189
|
+
if sources.first
|
190
|
+
@directives[directive] = apply_mappings(sources)
|
191
|
+
else
|
192
|
+
@directives.delete(directive)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# Specify whether to prevent the user agent from loading any assets over HTTP
|
198
|
+
# when the page uses HTTPS:
|
199
|
+
#
|
200
|
+
# policy.block_all_mixed_content
|
201
|
+
#
|
202
|
+
# Pass `false` to allow it again:
|
203
|
+
#
|
204
|
+
# policy.block_all_mixed_content false
|
205
|
+
#
|
206
|
+
def block_all_mixed_content(enabled = true)
|
207
|
+
if enabled
|
208
|
+
@directives["block-all-mixed-content"] = true
|
209
|
+
else
|
210
|
+
@directives.delete("block-all-mixed-content")
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Restricts the set of plugins that can be embedded:
|
215
|
+
#
|
216
|
+
# policy.plugin_types "application/x-shockwave-flash"
|
217
|
+
#
|
218
|
+
# Leave empty to allow all plugins:
|
219
|
+
#
|
220
|
+
# policy.plugin_types
|
221
|
+
#
|
222
|
+
def plugin_types(*types)
|
223
|
+
if types.first
|
224
|
+
@directives["plugin-types"] = types
|
225
|
+
else
|
226
|
+
@directives.delete("plugin-types")
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
# Enable the [report-uri]
|
231
|
+
# (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri)
|
232
|
+
# directive. Violation reports will be sent to the
|
233
|
+
# specified URI:
|
234
|
+
#
|
235
|
+
# policy.report_uri "/csp-violation-report-endpoint"
|
236
|
+
#
|
237
|
+
def report_uri(uri)
|
238
|
+
@directives["report-uri"] = [uri]
|
239
|
+
end
|
240
|
+
|
241
|
+
# Specify asset types for which [Subresource Integrity]
|
242
|
+
# (https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) is required:
|
243
|
+
#
|
244
|
+
# policy.require_sri_for :script, :style
|
245
|
+
#
|
246
|
+
# Leave empty to not require Subresource Integrity:
|
247
|
+
#
|
248
|
+
# policy.require_sri_for
|
249
|
+
#
|
250
|
+
def require_sri_for(*types)
|
251
|
+
if types.first
|
252
|
+
@directives["require-sri-for"] = types
|
253
|
+
else
|
254
|
+
@directives.delete("require-sri-for")
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
# Specify whether a [sandbox]
|
259
|
+
# (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/sandbox)
|
260
|
+
# should be enabled for the requested resource:
|
261
|
+
#
|
262
|
+
# policy.sandbox
|
263
|
+
#
|
264
|
+
# Values can be passed as arguments:
|
265
|
+
#
|
266
|
+
# policy.sandbox "allow-scripts", "allow-modals"
|
267
|
+
#
|
268
|
+
# Pass `false` to disable the sandbox:
|
269
|
+
#
|
270
|
+
# policy.sandbox false
|
271
|
+
#
|
272
|
+
def sandbox(*values)
|
273
|
+
if values.empty?
|
274
|
+
@directives["sandbox"] = true
|
275
|
+
elsif values.first
|
276
|
+
@directives["sandbox"] = values
|
277
|
+
else
|
278
|
+
@directives.delete("sandbox")
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# Specify whether user agents should treat any assets over HTTP as HTTPS:
|
283
|
+
#
|
284
|
+
# policy.upgrade_insecure_requests
|
285
|
+
#
|
286
|
+
# Pass `false` to disable it:
|
287
|
+
#
|
288
|
+
# policy.upgrade_insecure_requests false
|
289
|
+
#
|
290
|
+
def upgrade_insecure_requests(enabled = true)
|
291
|
+
if enabled
|
292
|
+
@directives["upgrade-insecure-requests"] = true
|
293
|
+
else
|
294
|
+
@directives.delete("upgrade-insecure-requests")
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
def build(context = nil, nonce = nil, nonce_directives = nil)
|
299
|
+
nonce_directives = DEFAULT_NONCE_DIRECTIVES if nonce_directives.nil?
|
300
|
+
build_directives(context, nonce, nonce_directives).compact.join("; ")
|
301
|
+
end
|
302
|
+
|
303
|
+
private
|
304
|
+
def apply_mappings(sources)
|
305
|
+
sources.map do |source|
|
306
|
+
case source
|
307
|
+
when Symbol
|
308
|
+
apply_mapping(source)
|
309
|
+
when String, Proc
|
310
|
+
source
|
311
|
+
else
|
312
|
+
raise ArgumentError, "Invalid content security policy source: #{source.inspect}"
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
def apply_mapping(source)
|
318
|
+
MAPPINGS.fetch(source) do
|
319
|
+
raise ArgumentError, "Unknown content security policy source mapping: #{source.inspect}"
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
def build_directives(context, nonce, nonce_directives)
|
324
|
+
@directives.map do |directive, sources|
|
325
|
+
if sources.is_a?(Array)
|
326
|
+
if nonce && nonce_directive?(directive, nonce_directives)
|
327
|
+
"#{directive} #{build_directive(sources, context).join(' ')} 'nonce-#{nonce}'"
|
328
|
+
else
|
329
|
+
"#{directive} #{build_directive(sources, context).join(' ')}"
|
330
|
+
end
|
331
|
+
elsif sources
|
332
|
+
directive
|
333
|
+
else
|
334
|
+
nil
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
def build_directive(sources, context)
|
340
|
+
sources.map { |source| resolve_source(source, context) }
|
341
|
+
end
|
342
|
+
|
343
|
+
def resolve_source(source, context)
|
344
|
+
case source
|
345
|
+
when String
|
346
|
+
source
|
347
|
+
when Symbol
|
348
|
+
source.to_s
|
349
|
+
when Proc
|
350
|
+
if context.nil?
|
351
|
+
raise RuntimeError, "Missing context for the dynamic content security policy source: #{source.inspect}"
|
352
|
+
else
|
353
|
+
resolved = context.instance_exec(&source)
|
354
|
+
apply_mappings(Array.wrap(resolved))
|
355
|
+
end
|
356
|
+
else
|
357
|
+
raise RuntimeError, "Unexpected content security policy source: #{source.inspect}"
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
def nonce_directive?(directive, nonce_directives)
|
362
|
+
nonce_directives.include?(directive)
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
require "active_support/parameter_filter"
|
6
|
+
|
7
|
+
module ActionDispatch
|
8
|
+
module Http
|
9
|
+
# # Action Dispatch HTTP Filter Parameters
|
10
|
+
#
|
11
|
+
# Allows you to specify sensitive query string and POST parameters to filter
|
12
|
+
# from the request log.
|
13
|
+
#
|
14
|
+
# # Replaces values with "[FILTERED]" for keys that match /foo|bar/i.
|
15
|
+
# env["action_dispatch.parameter_filter"] = [:foo, "bar"]
|
16
|
+
#
|
17
|
+
# For more information about filter behavior, see
|
18
|
+
# ActiveSupport::ParameterFilter.
|
19
|
+
module FilterParameters
|
20
|
+
ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc:
|
21
|
+
NULL_PARAM_FILTER = ActiveSupport::ParameterFilter.new # :nodoc:
|
22
|
+
NULL_ENV_FILTER = ActiveSupport::ParameterFilter.new ENV_MATCH # :nodoc:
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
super
|
26
|
+
@filtered_parameters = nil
|
27
|
+
@filtered_env = nil
|
28
|
+
@filtered_path = nil
|
29
|
+
@parameter_filter = nil
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns a hash of parameters with all sensitive data replaced.
|
33
|
+
def filtered_parameters
|
34
|
+
@filtered_parameters ||= parameter_filter.filter(parameters)
|
35
|
+
rescue ActionDispatch::Http::Parameters::ParseError
|
36
|
+
@filtered_parameters = {}
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns a hash of request.env with all sensitive data replaced.
|
40
|
+
def filtered_env
|
41
|
+
@filtered_env ||= env_filter.filter(@env)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Reconstructs a path with all sensitive GET parameters replaced.
|
45
|
+
def filtered_path
|
46
|
+
@filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}"
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns the `ActiveSupport::ParameterFilter` object used to filter in this
|
50
|
+
# request.
|
51
|
+
def parameter_filter
|
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
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
def env_filter # :doc:
|
61
|
+
user_key = fetch_header("action_dispatch.parameter_filter") {
|
62
|
+
return NULL_ENV_FILTER
|
63
|
+
}
|
64
|
+
parameter_filter_for(Array(user_key) + ENV_MATCH)
|
65
|
+
end
|
66
|
+
|
67
|
+
def parameter_filter_for(filters) # :doc:
|
68
|
+
ActiveSupport::ParameterFilter.new(filters)
|
69
|
+
end
|
70
|
+
|
71
|
+
KV_RE = "[^&;=]+"
|
72
|
+
PAIR_RE = %r{(#{KV_RE})=(#{KV_RE})}
|
73
|
+
def filtered_query_string # :doc:
|
74
|
+
query_string.gsub(PAIR_RE) do |_|
|
75
|
+
parameter_filter.filter($1 => $2).first.join("=")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionDispatch
|
6
|
+
module Http
|
7
|
+
module FilterRedirect
|
8
|
+
FILTERED = "[FILTERED]" # :nodoc:
|
9
|
+
|
10
|
+
def filtered_location # :nodoc:
|
11
|
+
if location_filter_match?
|
12
|
+
FILTERED
|
13
|
+
else
|
14
|
+
parameter_filtered_location
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def location_filters
|
20
|
+
if request
|
21
|
+
request.get_header("action_dispatch.redirect_filter") || []
|
22
|
+
else
|
23
|
+
[]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def location_filter_match?
|
28
|
+
location_filters.any? do |filter|
|
29
|
+
if String === filter
|
30
|
+
location.include?(filter)
|
31
|
+
elsif Regexp === filter
|
32
|
+
location.match?(filter)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
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
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionDispatch
|
6
|
+
module Http
|
7
|
+
# # Action Dispatch HTTP Headers
|
8
|
+
#
|
9
|
+
# Provides access to the request's HTTP headers from the environment.
|
10
|
+
#
|
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"
|
28
|
+
class Headers
|
29
|
+
CGI_VARIABLES = Set.new(%W[
|
30
|
+
AUTH_TYPE
|
31
|
+
CONTENT_LENGTH
|
32
|
+
CONTENT_TYPE
|
33
|
+
GATEWAY_INTERFACE
|
34
|
+
HTTPS
|
35
|
+
PATH_INFO
|
36
|
+
PATH_TRANSLATED
|
37
|
+
QUERY_STRING
|
38
|
+
REMOTE_ADDR
|
39
|
+
REMOTE_HOST
|
40
|
+
REMOTE_IDENT
|
41
|
+
REMOTE_USER
|
42
|
+
REQUEST_METHOD
|
43
|
+
SCRIPT_NAME
|
44
|
+
SERVER_NAME
|
45
|
+
SERVER_PORT
|
46
|
+
SERVER_PROTOCOL
|
47
|
+
SERVER_SOFTWARE
|
48
|
+
]).freeze
|
49
|
+
|
50
|
+
HTTP_HEADER = /\A[A-Za-z0-9-]+\z/
|
51
|
+
|
52
|
+
include Enumerable
|
53
|
+
|
54
|
+
def self.from_hash(hash)
|
55
|
+
new ActionDispatch::Request.new hash
|
56
|
+
end
|
57
|
+
|
58
|
+
def initialize(request) # :nodoc:
|
59
|
+
@req = request
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns the value for the given key mapped to @env.
|
63
|
+
def [](key)
|
64
|
+
@req.get_header env_name(key)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Sets the given value for the key mapped to @env.
|
68
|
+
def []=(key, value)
|
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
|
75
|
+
end
|
76
|
+
|
77
|
+
def key?(key)
|
78
|
+
@req.has_header? env_name(key)
|
79
|
+
end
|
80
|
+
alias :include? :key?
|
81
|
+
|
82
|
+
DEFAULT = Object.new # :nodoc:
|
83
|
+
|
84
|
+
# Returns the value for the given key mapped to @env.
|
85
|
+
#
|
86
|
+
# If the key is not found and an optional code block is not provided, raises a
|
87
|
+
# `KeyError` exception.
|
88
|
+
#
|
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
|
96
|
+
end
|
97
|
+
|
98
|
+
def each(&block)
|
99
|
+
@req.each_header(&block)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns a new Http::Headers instance containing the contents of
|
103
|
+
# `headers_or_env` and the original instance.
|
104
|
+
def merge(headers_or_env)
|
105
|
+
headers = @req.dup.headers
|
106
|
+
headers.merge!(headers_or_env)
|
107
|
+
headers
|
108
|
+
end
|
109
|
+
|
110
|
+
# Adds the contents of `headers_or_env` to original instance entries; duplicate
|
111
|
+
# keys are overwritten with the values from `headers_or_env`.
|
112
|
+
def merge!(headers_or_env)
|
113
|
+
headers_or_env.each do |key, value|
|
114
|
+
@req.set_header env_name(key), value
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def env; @req.env.dup; end
|
119
|
+
|
120
|
+
private
|
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
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|