actionpack 6.1.7.5 → 7.1.3.1
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 +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
|
|