actionpack 6.1.7.5 → 7.1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +355 -435
- data/MIT-LICENSE +2 -1
- data/README.rdoc +6 -7
- data/lib/abstract_controller/asset_paths.rb +1 -1
- data/lib/abstract_controller/base.rb +33 -37
- data/lib/abstract_controller/caching/fragments.rb +4 -2
- data/lib/abstract_controller/caching.rb +1 -1
- data/lib/abstract_controller/callbacks.rb +50 -11
- data/lib/abstract_controller/collector.rb +2 -2
- data/lib/abstract_controller/deprecator.rb +7 -0
- data/lib/abstract_controller/error.rb +1 -1
- data/lib/abstract_controller/helpers.rb +78 -30
- data/lib/abstract_controller/logger.rb +1 -1
- data/lib/abstract_controller/railties/routes_helpers.rb +3 -16
- data/lib/abstract_controller/rendering.rb +12 -14
- data/lib/abstract_controller/translation.rb +26 -7
- data/lib/abstract_controller/url_for.rb +6 -6
- data/lib/abstract_controller.rb +6 -0
- data/lib/action_controller/api.rb +12 -10
- data/lib/action_controller/base.rb +8 -21
- data/lib/action_controller/caching.rb +2 -0
- data/lib/action_controller/deprecator.rb +7 -0
- data/lib/action_controller/form_builder.rb +4 -2
- data/lib/action_controller/log_subscriber.rb +20 -7
- data/lib/action_controller/metal/basic_implicit_render.rb +3 -1
- data/lib/action_controller/metal/conditional_get.rb +137 -102
- data/lib/action_controller/metal/content_security_policy.rb +37 -3
- data/lib/action_controller/metal/cookies.rb +1 -1
- data/lib/action_controller/metal/data_streaming.rb +25 -31
- data/lib/action_controller/metal/default_headers.rb +2 -0
- data/lib/action_controller/metal/etag_with_flash.rb +3 -1
- data/lib/action_controller/metal/etag_with_template_digest.rb +2 -0
- data/lib/action_controller/metal/exceptions.rb +27 -30
- data/lib/action_controller/metal/flash.rb +6 -2
- data/lib/action_controller/metal/head.rb +9 -7
- data/lib/action_controller/metal/helpers.rb +5 -16
- data/lib/action_controller/metal/http_authentication.rb +78 -42
- data/lib/action_controller/metal/implicit_render.rb +5 -3
- data/lib/action_controller/metal/instrumentation.rb +62 -50
- data/lib/action_controller/metal/live.rb +67 -2
- data/lib/action_controller/metal/mime_responds.rb +5 -5
- data/lib/action_controller/metal/params_wrapper.rb +24 -13
- data/lib/action_controller/metal/permissions_policy.rb +20 -29
- data/lib/action_controller/metal/redirecting.rb +96 -23
- data/lib/action_controller/metal/renderers.rb +14 -15
- data/lib/action_controller/metal/rendering.rb +121 -16
- data/lib/action_controller/metal/request_forgery_protection.rb +208 -68
- data/lib/action_controller/metal/rescue.rb +7 -4
- data/lib/action_controller/metal/streaming.rb +74 -36
- data/lib/action_controller/metal/strong_parameters.rb +254 -151
- data/lib/action_controller/metal/testing.rb +9 -2
- data/lib/action_controller/metal/url_for.rb +10 -5
- data/lib/action_controller/metal.rb +89 -34
- data/lib/action_controller/railtie.rb +66 -9
- data/lib/action_controller/renderer.rb +99 -85
- data/lib/action_controller/test_case.rb +42 -11
- data/lib/action_controller.rb +10 -6
- data/lib/action_dispatch/constants.rb +32 -0
- data/lib/action_dispatch/deprecator.rb +7 -0
- data/lib/action_dispatch/http/cache.rb +21 -16
- data/lib/action_dispatch/http/content_security_policy.rb +122 -44
- data/lib/action_dispatch/http/filter_parameters.rb +14 -23
- data/lib/action_dispatch/http/headers.rb +3 -1
- data/lib/action_dispatch/http/mime_negotiation.rb +25 -15
- data/lib/action_dispatch/http/mime_type.rb +43 -22
- data/lib/action_dispatch/http/mime_types.rb +3 -1
- data/lib/action_dispatch/http/parameters.rb +6 -6
- data/lib/action_dispatch/http/permissions_policy.rb +57 -19
- data/lib/action_dispatch/http/rack_cache.rb +2 -0
- data/lib/action_dispatch/http/request.rb +75 -51
- data/lib/action_dispatch/http/response.rb +81 -77
- data/lib/action_dispatch/http/upload.rb +15 -2
- data/lib/action_dispatch/http/url.rb +11 -19
- data/lib/action_dispatch/journey/formatter.rb +8 -2
- data/lib/action_dispatch/journey/gtg/builder.rb +11 -12
- data/lib/action_dispatch/journey/gtg/simulator.rb +10 -4
- data/lib/action_dispatch/journey/gtg/transition_table.rb +77 -21
- data/lib/action_dispatch/journey/nodes/node.rb +70 -5
- data/lib/action_dispatch/journey/path/pattern.rb +36 -27
- data/lib/action_dispatch/journey/route.rb +8 -14
- data/lib/action_dispatch/journey/router/utils.rb +2 -2
- data/lib/action_dispatch/journey/router.rb +10 -9
- data/lib/action_dispatch/journey/routes.rb +5 -5
- 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/log_subscriber.rb +23 -0
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +5 -7
- data/lib/action_dispatch/middleware/assume_ssl.rb +24 -0
- data/lib/action_dispatch/middleware/callbacks.rb +2 -0
- data/lib/action_dispatch/middleware/cookies.rb +97 -107
- data/lib/action_dispatch/middleware/debug_exceptions.rb +31 -28
- data/lib/action_dispatch/middleware/debug_locks.rb +7 -4
- data/lib/action_dispatch/middleware/debug_view.rb +7 -2
- data/lib/action_dispatch/middleware/exception_wrapper.rb +190 -27
- data/lib/action_dispatch/middleware/executor.rb +3 -0
- data/lib/action_dispatch/middleware/flash.rb +24 -18
- data/lib/action_dispatch/middleware/host_authorization.rb +19 -20
- data/lib/action_dispatch/middleware/public_exceptions.rb +5 -3
- data/lib/action_dispatch/middleware/reloader.rb +7 -5
- data/lib/action_dispatch/middleware/remote_ip.rb +32 -19
- data/lib/action_dispatch/middleware/request_id.rb +5 -3
- data/lib/action_dispatch/middleware/server_timing.rb +76 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +6 -1
- data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
- data/lib/action_dispatch/middleware/session/cookie_store.rb +19 -13
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +3 -1
- data/lib/action_dispatch/middleware/show_exceptions.rb +30 -25
- data/lib/action_dispatch/middleware/ssl.rb +18 -6
- data/lib/action_dispatch/middleware/stack.rb +34 -11
- data/lib/action_dispatch/middleware/static.rb +16 -16
- data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +5 -5
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -11
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +10 -5
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +7 -3
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +9 -9
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +45 -18
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -15
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +6 -6
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +7 -7
- 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 +3 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +64 -55
- data/lib/action_dispatch/railtie.rb +20 -4
- data/lib/action_dispatch/request/session.rb +59 -19
- data/lib/action_dispatch/request/utils.rb +8 -3
- data/lib/action_dispatch/routing/inspector.rb +55 -7
- data/lib/action_dispatch/routing/mapper.rb +117 -107
- data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
- data/lib/action_dispatch/routing/redirection.rb +20 -8
- data/lib/action_dispatch/routing/route_set.rb +67 -27
- data/lib/action_dispatch/routing/routes_proxy.rb +11 -16
- data/lib/action_dispatch/routing/url_for.rb +29 -26
- data/lib/action_dispatch/routing.rb +12 -13
- data/lib/action_dispatch/system_test_case.rb +8 -8
- data/lib/action_dispatch/system_testing/browser.rb +20 -29
- data/lib/action_dispatch/system_testing/driver.rb +34 -18
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +35 -20
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +0 -8
- data/lib/action_dispatch/testing/assertion_response.rb +1 -1
- data/lib/action_dispatch/testing/assertions/response.rb +14 -7
- data/lib/action_dispatch/testing/assertions/routing.rb +70 -30
- data/lib/action_dispatch/testing/assertions.rb +3 -4
- data/lib/action_dispatch/testing/integration.rb +33 -25
- data/lib/action_dispatch/testing/request_encoder.rb +4 -1
- data/lib/action_dispatch/testing/test_process.rb +5 -30
- data/lib/action_dispatch/testing/test_request.rb +1 -1
- data/lib/action_dispatch/testing/test_response.rb +34 -2
- data/lib/action_dispatch.rb +38 -4
- data/lib/action_pack/gem_version.rb +4 -4
- data/lib/action_pack/version.rb +1 -1
- data/lib/action_pack.rb +1 -1
- metadata +67 -30
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "action_dispatch/http/request"
|
4
|
-
|
5
3
|
module ActionDispatch
|
4
|
+
# = Action Dispatch \HostAuthorization
|
5
|
+
#
|
6
6
|
# This middleware guards from DNS rebinding attacks by explicitly permitting
|
7
7
|
# the hosts a request can be sent to, and is passed the options set in
|
8
8
|
# +config.host_authorization+.
|
@@ -20,6 +20,7 @@ module ActionDispatch
|
|
20
20
|
class HostAuthorization
|
21
21
|
ALLOWED_HOSTS_IN_DEVELOPMENT = [".localhost", IPAddr.new("0.0.0.0/0"), IPAddr.new("::/0")]
|
22
22
|
PORT_REGEX = /(?::\d+)/ # :nodoc:
|
23
|
+
SUBDOMAIN_REGEX = /(?:[a-z0-9-]+\.)/i # :nodoc:
|
23
24
|
IPV4_HOSTNAME = /(?<host>\d+\.\d+\.\d+\.\d+)#{PORT_REGEX}?/ # :nodoc:
|
24
25
|
IPV6_HOSTNAME = /(?<host>[a-f0-9]*:[a-f0-9.:]+)/i # :nodoc:
|
25
26
|
IPV6_HOSTNAME_WITH_PORT = /\[#{IPV6_HOSTNAME}\]#{PORT_REGEX}/i # :nodoc:
|
@@ -71,7 +72,7 @@ module ActionDispatch
|
|
71
72
|
|
72
73
|
def sanitize_string(host)
|
73
74
|
if host.start_with?(".")
|
74
|
-
/\A
|
75
|
+
/\A#{SUBDOMAIN_REGEX}?#{Regexp.escape(host[1..-1])}#{PORT_REGEX}?\z/i
|
75
76
|
else
|
76
77
|
/\A#{Regexp.escape host}#{PORT_REGEX}?\z/i
|
77
78
|
end
|
@@ -97,14 +98,14 @@ module ActionDispatch
|
|
97
98
|
def response_body(request)
|
98
99
|
return "" unless request.get_header("action_dispatch.show_detailed_exceptions")
|
99
100
|
|
100
|
-
template = DebugView.new(
|
101
|
+
template = DebugView.new(hosts: request.env["action_dispatch.blocked_hosts"])
|
101
102
|
template.render(template: "rescues/blocked_host", layout: "rescues/layout")
|
102
103
|
end
|
103
104
|
|
104
105
|
def response(format, body)
|
105
106
|
[RESPONSE_STATUS,
|
106
|
-
{
|
107
|
-
|
107
|
+
{ Rack::CONTENT_TYPE => "#{format}; charset=#{Response.default_charset}",
|
108
|
+
Rack::CONTENT_LENGTH => body.bytesize.to_s },
|
108
109
|
[body]]
|
109
110
|
end
|
110
111
|
|
@@ -113,7 +114,7 @@ module ActionDispatch
|
|
113
114
|
|
114
115
|
return unless logger
|
115
116
|
|
116
|
-
logger.error("[#{self.class.name}] Blocked
|
117
|
+
logger.error("[#{self.class.name}] Blocked hosts: #{request.env["action_dispatch.blocked_hosts"].join(", ")}")
|
117
118
|
end
|
118
119
|
|
119
120
|
def available_logger(request)
|
@@ -121,20 +122,11 @@ module ActionDispatch
|
|
121
122
|
end
|
122
123
|
end
|
123
124
|
|
124
|
-
def initialize(app, hosts,
|
125
|
+
def initialize(app, hosts, exclude: nil, response_app: nil)
|
125
126
|
@app = app
|
126
127
|
@permissions = Permissions.new(hosts)
|
127
128
|
@exclude = exclude
|
128
129
|
|
129
|
-
unless deprecated_response_app.nil?
|
130
|
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
131
|
-
`action_dispatch.hosts_response_app` is deprecated and will be ignored in Rails 7.0.
|
132
|
-
Use the Host Authorization `response_app` setting instead.
|
133
|
-
MSG
|
134
|
-
|
135
|
-
response_app ||= deprecated_response_app
|
136
|
-
end
|
137
|
-
|
138
130
|
@response_app = response_app || DefaultResponseApp.new
|
139
131
|
end
|
140
132
|
|
@@ -142,21 +134,28 @@ module ActionDispatch
|
|
142
134
|
return @app.call(env) if @permissions.empty?
|
143
135
|
|
144
136
|
request = Request.new(env)
|
137
|
+
hosts = blocked_hosts(request)
|
145
138
|
|
146
|
-
if
|
139
|
+
if hosts.empty? || excluded?(request)
|
147
140
|
mark_as_authorized(request)
|
148
141
|
@app.call(env)
|
149
142
|
else
|
143
|
+
env["action_dispatch.blocked_hosts"] = hosts
|
150
144
|
@response_app.call(env)
|
151
145
|
end
|
152
146
|
end
|
153
147
|
|
154
148
|
private
|
155
|
-
def
|
149
|
+
def blocked_hosts(request)
|
150
|
+
hosts = []
|
151
|
+
|
156
152
|
origin_host = request.get_header("HTTP_HOST")
|
153
|
+
hosts << origin_host unless @permissions.allows?(origin_host)
|
154
|
+
|
157
155
|
forwarded_host = request.x_forwarded_host&.split(/,\s?/)&.last
|
156
|
+
hosts << forwarded_host unless forwarded_host.blank? || @permissions.allows?(forwarded_host)
|
158
157
|
|
159
|
-
|
158
|
+
hosts
|
160
159
|
end
|
161
160
|
|
162
161
|
def excluded?(request)
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActionDispatch
|
4
|
+
# = Action Dispatch \PublicExceptions
|
5
|
+
#
|
4
6
|
# When called, this middleware renders an error page. By default if an HTML
|
5
7
|
# response is expected it will render static error pages from the <tt>/public</tt>
|
6
8
|
# directory. For example when this middleware receives a 500 response it will
|
@@ -42,8 +44,8 @@ module ActionDispatch
|
|
42
44
|
end
|
43
45
|
|
44
46
|
def render_format(status, content_type, body)
|
45
|
-
[status, {
|
46
|
-
|
47
|
+
[status, { Rack::CONTENT_TYPE => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
|
48
|
+
Rack::CONTENT_LENGTH => body.bytesize.to_s }, [body]]
|
47
49
|
end
|
48
50
|
|
49
51
|
def render_html(status)
|
@@ -53,7 +55,7 @@ module ActionDispatch
|
|
53
55
|
if found || File.exist?(path)
|
54
56
|
render_format(status, "text/html", File.read(path))
|
55
57
|
else
|
56
|
-
[404, {
|
58
|
+
[404, { Constants::X_CASCADE => "pass" }, []]
|
57
59
|
end
|
58
60
|
end
|
59
61
|
end
|
@@ -1,12 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActionDispatch
|
4
|
-
#
|
5
|
-
# callbacks, intended to assist with code reloading during development.
|
4
|
+
# = Action Dispatch \Reloader
|
6
5
|
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
6
|
+
# ActionDispatch::Reloader wraps the request with callbacks provided by
|
7
|
+
# ActiveSupport::Reloader, intended to assist with code reloading during
|
8
|
+
# development.
|
9
|
+
#
|
10
|
+
# ActionDispatch::Reloader is included in the middleware stack only if
|
11
|
+
# reloading is enabled, which it is by the default in +development+ mode.
|
10
12
|
class Reloader < Executor
|
11
13
|
end
|
12
14
|
end
|
@@ -3,14 +3,14 @@
|
|
3
3
|
require "ipaddr"
|
4
4
|
|
5
5
|
module ActionDispatch
|
6
|
+
# = Action Dispatch \RemoteIp
|
7
|
+
#
|
6
8
|
# This middleware calculates the IP address of the remote client that is
|
7
9
|
# making the request. It does this by checking various headers that could
|
8
10
|
# contain the address, and then picking the last-set address that is not
|
9
11
|
# on the list of trusted IPs. This follows the precedent set by e.g.
|
10
|
-
# {the Tomcat server}[https://issues.apache.org/bugzilla/show_bug.cgi?id=50453]
|
11
|
-
#
|
12
|
-
# by @gingerlime. A more detailed explanation of the algorithm is given
|
13
|
-
# at GetIp#calculate_ip.
|
12
|
+
# {the Tomcat server}[https://issues.apache.org/bugzilla/show_bug.cgi?id=50453].
|
13
|
+
# A more detailed explanation of the algorithm is given at GetIp#calculate_ip.
|
14
14
|
#
|
15
15
|
# Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]
|
16
16
|
# requires. Some Rack servers simply drop preceding headers, and only report
|
@@ -22,9 +22,10 @@ module ActionDispatch
|
|
22
22
|
# This middleware assumes that there is at least one proxy sitting around
|
23
23
|
# and setting headers with the client's remote IP address. If you don't use
|
24
24
|
# a proxy, because you are hosted on e.g. Heroku without SSL, any client can
|
25
|
-
# claim to have any IP address by setting the X-Forwarded-For header. If you
|
25
|
+
# claim to have any IP address by setting the +X-Forwarded-For+ header. If you
|
26
26
|
# care about that, then you need to explicitly drop or ignore those headers
|
27
|
-
# sometime before this middleware runs.
|
27
|
+
# sometime before this middleware runs. Alternatively, remove this middleware
|
28
|
+
# to avoid inadvertently relying on it.
|
28
29
|
class RemoteIp
|
29
30
|
class IpSpoofAttackError < StandardError; end
|
30
31
|
|
@@ -51,11 +52,9 @@ module ActionDispatch
|
|
51
52
|
# clients (like WAP devices), or behind proxies that set headers in an
|
52
53
|
# incorrect or confusing way (like AWS ELB).
|
53
54
|
#
|
54
|
-
# The +custom_proxies+ argument can take an
|
55
|
-
#
|
56
|
-
#
|
57
|
-
# addition to +TRUSTED_PROXIES+. Any proxy setup will put the value you
|
58
|
-
# want in the middle (or at the beginning) of the X-Forwarded-For list,
|
55
|
+
# The +custom_proxies+ argument can take an enumerable which will be used
|
56
|
+
# instead of +TRUSTED_PROXIES+. Any proxy setup will put the value you
|
57
|
+
# want in the middle (or at the beginning) of the +X-Forwarded-For+ list,
|
59
58
|
# with your proxy servers after it. If your proxies aren't removed, pass
|
60
59
|
# them in via the +custom_proxies+ parameter. That way, the middleware will
|
61
60
|
# ignore those IP addresses, and return the one that you want.
|
@@ -67,7 +66,19 @@ module ActionDispatch
|
|
67
66
|
elsif custom_proxies.respond_to?(:any?)
|
68
67
|
custom_proxies
|
69
68
|
else
|
70
|
-
|
69
|
+
raise(ArgumentError, <<~EOM)
|
70
|
+
Setting config.action_dispatch.trusted_proxies to a single value isn't
|
71
|
+
supported. Please set this to an enumerable instead. For
|
72
|
+
example, instead of:
|
73
|
+
|
74
|
+
config.action_dispatch.trusted_proxies = IPAddr.new("10.0.0.0/8")
|
75
|
+
|
76
|
+
Wrap the value in an Array:
|
77
|
+
|
78
|
+
config.action_dispatch.trusted_proxies = [IPAddr.new("10.0.0.0/8")]
|
79
|
+
|
80
|
+
Note that passing an enumerable will *replace* the default set of trusted proxies.
|
81
|
+
EOM
|
71
82
|
end
|
72
83
|
end
|
73
84
|
|
@@ -98,11 +109,11 @@ module ActionDispatch
|
|
98
109
|
# REMOTE_ADDR will be correct if the request is made directly against the
|
99
110
|
# Ruby process, on e.g. Heroku. When the request is proxied by another
|
100
111
|
# server like HAProxy or NGINX, the IP address that made the original
|
101
|
-
# request will be put in an X-Forwarded-For header. If there are multiple
|
112
|
+
# request will be put in an +X-Forwarded-For+ header. If there are multiple
|
102
113
|
# proxies, that header may contain a list of IPs. Other proxy services
|
103
|
-
# set the Client-Ip header instead, so we check that too.
|
114
|
+
# set the +Client-Ip+ header instead, so we check that too.
|
104
115
|
#
|
105
|
-
# As discussed in {this post about Rails IP Spoofing}[https://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/],
|
116
|
+
# As discussed in {this post about Rails IP Spoofing}[https://web.archive.org/web/20170626095448/https://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/],
|
106
117
|
# while the first IP in the list is likely to be the "originating" IP,
|
107
118
|
# it could also have been set by the client maliciously.
|
108
119
|
#
|
@@ -114,8 +125,8 @@ module ActionDispatch
|
|
114
125
|
remote_addr = ips_from(@req.remote_addr).last
|
115
126
|
|
116
127
|
# Could be a CSV list and/or repeated headers that were concatenated.
|
117
|
-
client_ips = ips_from(@req.client_ip).reverse
|
118
|
-
forwarded_ips = ips_from(@req.x_forwarded_for).reverse
|
128
|
+
client_ips = ips_from(@req.client_ip).reverse!
|
129
|
+
forwarded_ips = ips_from(@req.x_forwarded_for).reverse!
|
119
130
|
|
120
131
|
# +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set.
|
121
132
|
# If they are both set, it means that either:
|
@@ -143,7 +154,8 @@ module ActionDispatch
|
|
143
154
|
# - X-Forwarded-For will be a list of IPs, one per proxy, or blank
|
144
155
|
# - Client-Ip is propagated from the outermost proxy, or is blank
|
145
156
|
# - REMOTE_ADDR will be the IP that made the request to Rack
|
146
|
-
ips =
|
157
|
+
ips = forwarded_ips + client_ips
|
158
|
+
ips.compact!
|
147
159
|
|
148
160
|
# If every single IP option is in the trusted list, return the IP
|
149
161
|
# that's furthest away
|
@@ -161,7 +173,7 @@ module ActionDispatch
|
|
161
173
|
return [] unless header
|
162
174
|
# Split the comma-separated list into an array of strings.
|
163
175
|
ips = header.strip.split(/[,\s]+/)
|
164
|
-
ips.select do |ip|
|
176
|
+
ips.select! do |ip|
|
165
177
|
# Only return IPs that are valid according to the IPAddr#new method.
|
166
178
|
range = IPAddr.new(ip).to_range
|
167
179
|
# We want to make sure nobody is sneaking a netmask in.
|
@@ -169,6 +181,7 @@ module ActionDispatch
|
|
169
181
|
rescue ArgumentError
|
170
182
|
nil
|
171
183
|
end
|
184
|
+
ips
|
172
185
|
end
|
173
186
|
|
174
187
|
def filter_proxies(ips) # :doc:
|
@@ -4,11 +4,13 @@ require "securerandom"
|
|
4
4
|
require "active_support/core_ext/string/access"
|
5
5
|
|
6
6
|
module ActionDispatch
|
7
|
+
# = Action Dispatch \RequestId
|
8
|
+
#
|
7
9
|
# Makes a unique request id available to the +action_dispatch.request_id+ env variable (which is then accessible
|
8
|
-
# through
|
9
|
-
# the same id to the client via the X-Request-Id header.
|
10
|
+
# through ActionDispatch::Request#request_id or the alias ActionDispatch::Request#uuid) and sends
|
11
|
+
# the same id to the client via the +X-Request-Id+ header.
|
10
12
|
#
|
11
|
-
# The unique request id is either based on the X-Request-Id header in the request, which would typically be generated
|
13
|
+
# The unique request id is either based on the +X-Request-Id+ header in the request, which would typically be generated
|
12
14
|
# by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the
|
13
15
|
# header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only.
|
14
16
|
#
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/notifications"
|
4
|
+
|
5
|
+
module ActionDispatch
|
6
|
+
class ServerTiming
|
7
|
+
class Subscriber # :nodoc:
|
8
|
+
include Singleton
|
9
|
+
KEY = :action_dispatch_server_timing_events
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@mutex = Mutex.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(event)
|
16
|
+
if events = ActiveSupport::IsolatedExecutionState[KEY]
|
17
|
+
events << event
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def collect_events
|
22
|
+
events = []
|
23
|
+
ActiveSupport::IsolatedExecutionState[KEY] = events
|
24
|
+
yield
|
25
|
+
events
|
26
|
+
ensure
|
27
|
+
ActiveSupport::IsolatedExecutionState.delete(KEY)
|
28
|
+
end
|
29
|
+
|
30
|
+
def ensure_subscribed
|
31
|
+
@mutex.synchronize do
|
32
|
+
# Subscribe to all events, except those beginning with "!"
|
33
|
+
# Ideally we would be more selective of what is being measured
|
34
|
+
@subscriber ||= ActiveSupport::Notifications.subscribe(/\A[^!]/, self)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def unsubscribe
|
39
|
+
@mutex.synchronize do
|
40
|
+
ActiveSupport::Notifications.unsubscribe @subscriber
|
41
|
+
@subscriber = nil
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.unsubscribe # :nodoc:
|
47
|
+
Subscriber.instance.unsubscribe
|
48
|
+
end
|
49
|
+
|
50
|
+
def initialize(app)
|
51
|
+
@app = app
|
52
|
+
@subscriber = Subscriber.instance
|
53
|
+
@subscriber.ensure_subscribed
|
54
|
+
end
|
55
|
+
|
56
|
+
def call(env)
|
57
|
+
response = nil
|
58
|
+
events = @subscriber.collect_events do
|
59
|
+
response = @app.call(env)
|
60
|
+
end
|
61
|
+
|
62
|
+
headers = response[1]
|
63
|
+
|
64
|
+
header_info = events.group_by(&:name).map do |event_name, events_collection|
|
65
|
+
"%s;dur=%.2f" % [event_name, events_collection.sum(&:duration)]
|
66
|
+
end
|
67
|
+
|
68
|
+
if headers[ActionDispatch::Constants::SERVER_TIMING].present?
|
69
|
+
header_info.prepend(headers[ActionDispatch::Constants::SERVER_TIMING])
|
70
|
+
end
|
71
|
+
headers[ActionDispatch::Constants::SERVER_TIMING] = header_info.join(", ")
|
72
|
+
|
73
|
+
response
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -8,7 +8,7 @@ require "action_dispatch/request/session"
|
|
8
8
|
|
9
9
|
module ActionDispatch
|
10
10
|
module Session
|
11
|
-
class SessionRestoreError < StandardError
|
11
|
+
class SessionRestoreError < StandardError # :nodoc:
|
12
12
|
def initialize
|
13
13
|
super("Session contains objects whose class definition isn't available.\n" \
|
14
14
|
"Remember to require the classes for all objects kept in the session.\n" \
|
@@ -67,6 +67,11 @@ module ActionDispatch
|
|
67
67
|
end
|
68
68
|
|
69
69
|
module SessionObject # :nodoc:
|
70
|
+
def commit_session(req, res)
|
71
|
+
req.commit_csrf_token
|
72
|
+
super(req, res)
|
73
|
+
end
|
74
|
+
|
70
75
|
def prepare_session(req)
|
71
76
|
Request::Session.create(self, req, @default_options)
|
72
77
|
end
|
@@ -4,6 +4,8 @@ require "action_dispatch/middleware/session/abstract_store"
|
|
4
4
|
|
5
5
|
module ActionDispatch
|
6
6
|
module Session
|
7
|
+
# = Action Dispatch Session \CacheStore
|
8
|
+
#
|
7
9
|
# A session store that uses an ActiveSupport::Cache::Store to store the sessions. This store is most useful
|
8
10
|
# if you don't store critical data in your sessions and you don't need them to live for extended periods
|
9
11
|
# of time.
|
@@ -6,46 +6,48 @@ require "rack/session/cookie"
|
|
6
6
|
|
7
7
|
module ActionDispatch
|
8
8
|
module Session
|
9
|
-
#
|
9
|
+
# = Action Dispatch Session \CookieStore
|
10
|
+
#
|
11
|
+
# This cookie-based session store is the \Rails default. It is
|
10
12
|
# dramatically faster than the alternatives.
|
11
13
|
#
|
12
|
-
# Sessions typically contain at most a
|
13
|
-
# within the 4096 bytes cookie size limit. A CookieOverflow exception is raised if
|
14
|
+
# Sessions typically contain at most a user ID and flash message; both fit
|
15
|
+
# within the 4096 bytes cookie size limit. A +CookieOverflow+ exception is raised if
|
14
16
|
# you attempt to store more than 4096 bytes of data.
|
15
17
|
#
|
16
18
|
# The cookie jar used for storage is automatically configured to be the
|
17
19
|
# best possible option given your application's configuration.
|
18
20
|
#
|
19
|
-
# Your cookies will be encrypted using your
|
21
|
+
# Your cookies will be encrypted using your application's +secret_key_base+. This
|
20
22
|
# goes a step further than signed cookies in that encrypted cookies cannot
|
21
|
-
# be altered or read by users. This is the default starting in Rails 4.
|
23
|
+
# be altered or read by users. This is the default starting in \Rails 4.
|
22
24
|
#
|
23
25
|
# Configure your session store in an initializer:
|
24
26
|
#
|
25
27
|
# Rails.application.config.session_store :cookie_store, key: '_your_app_session'
|
26
28
|
#
|
27
|
-
# In the development and test environments your application's
|
28
|
-
# generated by Rails and stored in a temporary file in <tt>tmp/
|
29
|
+
# In the development and test environments your application's +secret_key_base+ is
|
30
|
+
# generated by \Rails and stored in a temporary file in <tt>tmp/local_secret.txt</tt>.
|
29
31
|
# In all other environments, it is stored encrypted in the
|
30
32
|
# <tt>config/credentials.yml.enc</tt> file.
|
31
33
|
#
|
32
|
-
# If your application was not updated to Rails 5.2 defaults, the secret_key_base
|
34
|
+
# If your application was not updated to \Rails 5.2 defaults, the +secret_key_base+
|
33
35
|
# will be found in the old <tt>config/secrets.yml</tt> file.
|
34
36
|
#
|
35
|
-
# Note that changing your secret_key_base will invalidate all existing session.
|
37
|
+
# Note that changing your +secret_key_base+ will invalidate all existing session.
|
36
38
|
# Additionally, you should take care to make sure you are not relying on the
|
37
39
|
# ability to decode signed cookies generated by your app in external
|
38
40
|
# applications or JavaScript before changing it.
|
39
41
|
#
|
40
|
-
# Because CookieStore extends Rack::Session::Abstract::Persisted
|
42
|
+
# Because CookieStore extends +Rack::Session::Abstract::Persisted+, many of the
|
41
43
|
# options described there can be used to customize the session cookie that
|
42
44
|
# is generated. For example:
|
43
45
|
#
|
44
46
|
# Rails.application.config.session_store :cookie_store, expire_after: 14.days
|
45
47
|
#
|
46
48
|
# would set the session cookie to expire automatically 14 days after creation.
|
47
|
-
# Other useful options include <tt>:key</tt>, <tt>:secure</tt
|
48
|
-
# <tt>:httponly</tt>.
|
49
|
+
# Other useful options include <tt>:key</tt>, <tt>:secure</tt>,
|
50
|
+
# <tt>:httponly</tt>, and <tt>:same_site</tt>.
|
49
51
|
class CookieStore < AbstractSecureStore
|
50
52
|
class SessionId < DelegateClass(Rack::Session::SessionId)
|
51
53
|
attr_reader :cookie_value
|
@@ -56,8 +58,12 @@ module ActionDispatch
|
|
56
58
|
end
|
57
59
|
end
|
58
60
|
|
61
|
+
DEFAULT_SAME_SITE = proc { |request| request.cookies_same_site_protection } # :nodoc:
|
62
|
+
|
59
63
|
def initialize(app, options = {})
|
60
|
-
|
64
|
+
options[:cookie_only] = true
|
65
|
+
options[:same_site] = DEFAULT_SAME_SITE if !options.key?(:same_site)
|
66
|
+
super
|
61
67
|
end
|
62
68
|
|
63
69
|
def delete_session(req, session_id, options)
|
@@ -4,12 +4,14 @@ require "action_dispatch/middleware/session/abstract_store"
|
|
4
4
|
begin
|
5
5
|
require "rack/session/dalli"
|
6
6
|
rescue LoadError => e
|
7
|
-
|
7
|
+
warn "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install"
|
8
8
|
raise e
|
9
9
|
end
|
10
10
|
|
11
11
|
module ActionDispatch
|
12
12
|
module Session
|
13
|
+
# = Action Dispatch Session \MemCacheStore
|
14
|
+
#
|
13
15
|
# A session store that uses MemCache to implement storage.
|
14
16
|
#
|
15
17
|
# ==== Options
|
@@ -1,49 +1,48 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "action_dispatch/http/request"
|
4
3
|
require "action_dispatch/middleware/exception_wrapper"
|
5
4
|
|
6
5
|
module ActionDispatch
|
6
|
+
# = Action Dispatch \ShowExceptions
|
7
|
+
#
|
7
8
|
# This middleware rescues any exception returned by the application
|
8
9
|
# and calls an exceptions app that will wrap it in a format for the end user.
|
9
10
|
#
|
10
|
-
# The exceptions app should be passed as parameter on initialization
|
11
|
-
#
|
12
|
-
# store the exception in env["action_dispatch.exception"]
|
13
|
-
# PATH_INFO to the exception status code and call the Rack app.
|
11
|
+
# The exceptions app should be passed as a parameter on initialization of
|
12
|
+
# +ShowExceptions+. Every time there is an exception, +ShowExceptions+ will
|
13
|
+
# store the exception in <tt>env["action_dispatch.exception"]</tt>, rewrite
|
14
|
+
# the +PATH_INFO+ to the exception status code, and call the Rack app.
|
15
|
+
#
|
16
|
+
# In \Rails applications, the exceptions app can be configured with
|
17
|
+
# +config.exceptions_app+, which defaults to ActionDispatch::PublicExceptions.
|
14
18
|
#
|
15
|
-
# If the application returns a
|
16
|
-
# will send an empty response as
|
17
|
-
# If any exception happens inside the
|
18
|
-
# catches the exceptions and returns a
|
19
|
+
# If the application returns a response with the <tt>X-Cascade</tt> header
|
20
|
+
# set to <tt>"pass"</tt>, this middleware will send an empty response as a
|
21
|
+
# result with the correct status code. If any exception happens inside the
|
22
|
+
# exceptions app, this middleware catches the exceptions and returns a
|
23
|
+
# failsafe response.
|
19
24
|
class ShowExceptions
|
20
|
-
FAILSAFE_RESPONSE = [500, { "Content-Type" => "text/plain" },
|
21
|
-
["500 Internal Server Error\n" \
|
22
|
-
"If you are the administrator of this website, then please read this web " \
|
23
|
-
"application's log file and/or the web server's log file to find out what " \
|
24
|
-
"went wrong."]]
|
25
|
-
|
26
25
|
def initialize(app, exceptions_app)
|
27
26
|
@app = app
|
28
27
|
@exceptions_app = exceptions_app
|
29
28
|
end
|
30
29
|
|
31
30
|
def call(env)
|
32
|
-
request = ActionDispatch::Request.new env
|
33
31
|
@app.call(env)
|
34
32
|
rescue Exception => exception
|
35
|
-
|
36
|
-
|
33
|
+
request = ActionDispatch::Request.new env
|
34
|
+
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
|
35
|
+
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
|
36
|
+
if wrapper.show?(request)
|
37
|
+
render_exception(request, wrapper)
|
37
38
|
else
|
38
39
|
raise exception
|
39
40
|
end
|
40
41
|
end
|
41
42
|
|
42
43
|
private
|
43
|
-
def render_exception(request,
|
44
|
-
|
45
|
-
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
|
46
|
-
status = wrapper.status_code
|
44
|
+
def render_exception(request, wrapper)
|
45
|
+
status = wrapper.status_code
|
47
46
|
request.set_header "action_dispatch.exception", wrapper.unwrapped_exception
|
48
47
|
request.set_header "action_dispatch.original_path", request.path_info
|
49
48
|
request.set_header "action_dispatch.original_request_method", request.raw_request_method
|
@@ -51,10 +50,15 @@ module ActionDispatch
|
|
51
50
|
request.path_info = "/#{status}"
|
52
51
|
request.request_method = "GET"
|
53
52
|
response = @exceptions_app.call(request.env)
|
54
|
-
response[1][
|
53
|
+
response[1][Constants::X_CASCADE] == "pass" ? pass_response(status) : response
|
55
54
|
rescue Exception => failsafe_error
|
56
55
|
$stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
|
57
|
-
|
56
|
+
|
57
|
+
[500, { Rack::CONTENT_TYPE => "text/plain; charset=utf-8" },
|
58
|
+
["500 Internal Server Error\n" \
|
59
|
+
"If you are the administrator of this website, then please read this web " \
|
60
|
+
"application's log file and/or the web server's log file to find out what " \
|
61
|
+
"went wrong."]]
|
58
62
|
end
|
59
63
|
|
60
64
|
def fallback_to_html_format_if_invalid_mime_type(request)
|
@@ -67,7 +71,8 @@ module ActionDispatch
|
|
67
71
|
end
|
68
72
|
|
69
73
|
def pass_response(status)
|
70
|
-
[status, {
|
74
|
+
[status, { Rack::CONTENT_TYPE => "text/html; charset=#{Response.default_charset}",
|
75
|
+
Rack::CONTENT_LENGTH => "0" }, []]
|
71
76
|
end
|
72
77
|
end
|
73
78
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActionDispatch
|
4
|
+
# = Action Dispatch \SSL
|
5
|
+
#
|
4
6
|
# This middleware is added to the stack when <tt>config.force_ssl = true</tt>, and is passed
|
5
7
|
# the options set in +config.ssl_options+. It does three jobs to enforce secure HTTP
|
6
8
|
# requests:
|
@@ -86,7 +88,7 @@ module ActionDispatch
|
|
86
88
|
|
87
89
|
private
|
88
90
|
def set_hsts_header!(headers)
|
89
|
-
headers[
|
91
|
+
headers[Constants::STRICT_TRANSPORT_SECURITY] ||= @hsts_header
|
90
92
|
end
|
91
93
|
|
92
94
|
def normalize_hsts_options(options)
|
@@ -112,23 +114,33 @@ module ActionDispatch
|
|
112
114
|
end
|
113
115
|
|
114
116
|
def flag_cookies_as_secure!(headers)
|
115
|
-
|
116
|
-
|
117
|
+
cookies = headers[Rack::SET_COOKIE]
|
118
|
+
return unless cookies
|
117
119
|
|
118
|
-
|
120
|
+
if Gem::Version.new(Rack::RELEASE) < Gem::Version.new("3")
|
121
|
+
cookies = cookies.split("\n")
|
122
|
+
headers[Rack::SET_COOKIE] = cookies.map { |cookie|
|
119
123
|
if !/;\s*secure\s*(;|$)/i.match?(cookie)
|
120
124
|
"#{cookie}; secure"
|
121
125
|
else
|
122
126
|
cookie
|
123
127
|
end
|
124
128
|
}.join("\n")
|
129
|
+
else
|
130
|
+
headers[Rack::SET_COOKIE] = Array(cookies).map do |cookie|
|
131
|
+
if !/;\s*secure\s*(;|$)/i.match?(cookie)
|
132
|
+
"#{cookie}; secure"
|
133
|
+
else
|
134
|
+
cookie
|
135
|
+
end
|
136
|
+
end
|
125
137
|
end
|
126
138
|
end
|
127
139
|
|
128
140
|
def redirect_to_https(request)
|
129
141
|
[ @redirect.fetch(:status, redirection_status(request)),
|
130
|
-
{
|
131
|
-
|
142
|
+
{ Rack::CONTENT_TYPE => "text/html; charset=utf-8",
|
143
|
+
Constants::LOCATION => https_location_for(request) },
|
132
144
|
(@redirect[:body] || []) ]
|
133
145
|
end
|
134
146
|
|