actionpack 4.2.11.1 → 6.1.3.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +291 -489
- data/MIT-LICENSE +1 -1
- data/README.rdoc +9 -9
- data/lib/abstract_controller/asset_paths.rb +2 -0
- data/lib/abstract_controller/base.rb +81 -51
- data/lib/{action_controller → abstract_controller}/caching/fragments.rb +64 -17
- data/lib/abstract_controller/caching.rb +66 -0
- data/lib/abstract_controller/callbacks.rb +61 -33
- data/lib/abstract_controller/collector.rb +9 -13
- data/lib/abstract_controller/error.rb +6 -0
- data/lib/abstract_controller/helpers.rb +115 -99
- data/lib/abstract_controller/logger.rb +2 -0
- data/lib/abstract_controller/railties/routes_helpers.rb +21 -3
- data/lib/abstract_controller/rendering.rb +48 -47
- data/lib/abstract_controller/translation.rb +17 -8
- data/lib/abstract_controller/url_for.rb +2 -0
- data/lib/abstract_controller.rb +13 -5
- data/lib/action_controller/api/api_rendering.rb +16 -0
- data/lib/action_controller/api.rb +150 -0
- data/lib/action_controller/base.rb +29 -24
- data/lib/action_controller/caching.rb +12 -57
- data/lib/action_controller/form_builder.rb +50 -0
- data/lib/action_controller/log_subscriber.rb +17 -19
- data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
- data/lib/action_controller/metal/conditional_get.rb +134 -46
- data/lib/action_controller/metal/content_security_policy.rb +51 -0
- data/lib/action_controller/metal/cookies.rb +6 -4
- data/lib/action_controller/metal/data_streaming.rb +30 -50
- data/lib/action_controller/metal/default_headers.rb +17 -0
- data/lib/action_controller/metal/etag_with_flash.rb +18 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +21 -16
- data/lib/action_controller/metal/exceptions.rb +63 -15
- data/lib/action_controller/metal/flash.rb +9 -8
- data/lib/action_controller/metal/head.rb +26 -21
- data/lib/action_controller/metal/helpers.rb +37 -18
- data/lib/action_controller/metal/http_authentication.rb +81 -73
- data/lib/action_controller/metal/implicit_render.rb +53 -9
- data/lib/action_controller/metal/instrumentation.rb +32 -35
- data/lib/action_controller/metal/live.rb +102 -120
- data/lib/action_controller/metal/logging.rb +20 -0
- data/lib/action_controller/metal/mime_responds.rb +49 -47
- data/lib/action_controller/metal/parameter_encoding.rb +82 -0
- data/lib/action_controller/metal/params_wrapper.rb +83 -66
- data/lib/action_controller/metal/permissions_policy.rb +46 -0
- data/lib/action_controller/metal/redirecting.rb +53 -32
- data/lib/action_controller/metal/renderers.rb +87 -44
- data/lib/action_controller/metal/rendering.rb +77 -50
- data/lib/action_controller/metal/request_forgery_protection.rb +267 -103
- data/lib/action_controller/metal/rescue.rb +10 -17
- data/lib/action_controller/metal/streaming.rb +12 -11
- data/lib/action_controller/metal/strong_parameters.rb +714 -186
- data/lib/action_controller/metal/testing.rb +2 -17
- data/lib/action_controller/metal/url_for.rb +19 -10
- data/lib/action_controller/metal.rb +104 -87
- data/lib/action_controller/railtie.rb +28 -10
- data/lib/action_controller/railties/helpers.rb +3 -1
- data/lib/action_controller/renderer.rb +141 -0
- data/lib/action_controller/template_assertions.rb +11 -0
- data/lib/action_controller/test_case.rb +296 -422
- data/lib/action_controller.rb +34 -23
- data/lib/action_dispatch/http/cache.rb +107 -56
- data/lib/action_dispatch/http/content_disposition.rb +45 -0
- data/lib/action_dispatch/http/content_security_policy.rb +286 -0
- data/lib/action_dispatch/http/filter_parameters.rb +32 -25
- data/lib/action_dispatch/http/filter_redirect.rb +10 -12
- data/lib/action_dispatch/http/headers.rb +55 -22
- data/lib/action_dispatch/http/mime_negotiation.rb +79 -51
- data/lib/action_dispatch/http/mime_type.rb +153 -121
- data/lib/action_dispatch/http/mime_types.rb +20 -6
- data/lib/action_dispatch/http/parameters.rb +90 -40
- data/lib/action_dispatch/http/permissions_policy.rb +173 -0
- data/lib/action_dispatch/http/rack_cache.rb +2 -0
- data/lib/action_dispatch/http/request.rb +226 -121
- data/lib/action_dispatch/http/response.rb +248 -113
- data/lib/action_dispatch/http/upload.rb +21 -7
- data/lib/action_dispatch/http/url.rb +182 -100
- data/lib/action_dispatch/journey/formatter.rb +90 -43
- data/lib/action_dispatch/journey/gtg/builder.rb +28 -41
- data/lib/action_dispatch/journey/gtg/simulator.rb +11 -16
- data/lib/action_dispatch/journey/gtg/transition_table.rb +23 -21
- data/lib/action_dispatch/journey/nfa/dot.rb +3 -14
- data/lib/action_dispatch/journey/nodes/node.rb +29 -15
- data/lib/action_dispatch/journey/parser.rb +17 -16
- data/lib/action_dispatch/journey/parser.y +4 -3
- data/lib/action_dispatch/journey/parser_extras.rb +12 -4
- data/lib/action_dispatch/journey/path/pattern.rb +58 -54
- data/lib/action_dispatch/journey/route.rb +100 -32
- data/lib/action_dispatch/journey/router/utils.rb +29 -18
- data/lib/action_dispatch/journey/router.rb +55 -51
- data/lib/action_dispatch/journey/routes.rb +17 -17
- data/lib/action_dispatch/journey/scanner.rb +26 -17
- data/lib/action_dispatch/journey/visitors.rb +98 -54
- data/lib/action_dispatch/journey.rb +5 -5
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
- data/lib/action_dispatch/middleware/callbacks.rb +3 -6
- data/lib/action_dispatch/middleware/cookies.rb +347 -217
- data/lib/action_dispatch/middleware/debug_exceptions.rb +135 -63
- data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
- data/lib/action_dispatch/middleware/debug_view.rb +66 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +115 -71
- data/lib/action_dispatch/middleware/executor.rb +21 -0
- data/lib/action_dispatch/middleware/flash.rb +78 -54
- data/lib/action_dispatch/middleware/host_authorization.rb +130 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +32 -27
- data/lib/action_dispatch/middleware/reloader.rb +5 -91
- data/lib/action_dispatch/middleware/remote_ip.rb +53 -45
- data/lib/action_dispatch/middleware/request_id.rb +17 -10
- data/lib/action_dispatch/middleware/session/abstract_store.rb +41 -26
- data/lib/action_dispatch/middleware/session/cache_store.rb +24 -14
- data/lib/action_dispatch/middleware/session/cookie_store.rb +74 -75
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -2
- data/lib/action_dispatch/middleware/show_exceptions.rb +28 -23
- data/lib/action_dispatch/middleware/ssl.rb +118 -35
- data/lib/action_dispatch/middleware/stack.rb +82 -41
- data/lib/action_dispatch/middleware/static.rb +156 -89
- 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 +4 -14
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +4 -2
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +45 -35
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +23 -4
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +15 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +105 -8
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -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 +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +87 -64
- data/lib/action_dispatch/railtie.rb +27 -13
- data/lib/action_dispatch/request/session.rb +109 -61
- data/lib/action_dispatch/request/utils.rb +90 -23
- data/lib/action_dispatch/routing/endpoint.rb +9 -2
- data/lib/action_dispatch/routing/inspector.rb +141 -102
- data/lib/action_dispatch/routing/mapper.rb +811 -473
- data/lib/action_dispatch/routing/polymorphic_routes.rb +167 -143
- data/lib/action_dispatch/routing/redirection.rb +37 -27
- data/lib/action_dispatch/routing/route_set.rb +363 -331
- data/lib/action_dispatch/routing/routes_proxy.rb +32 -5
- data/lib/action_dispatch/routing/url_for.rb +66 -26
- data/lib/action_dispatch/routing.rb +36 -36
- data/lib/action_dispatch/system_test_case.rb +190 -0
- data/lib/action_dispatch/system_testing/browser.rb +86 -0
- data/lib/action_dispatch/system_testing/driver.rb +67 -0
- data/lib/action_dispatch/system_testing/server.rb +31 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +138 -0
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +29 -0
- data/lib/action_dispatch/testing/assertion_response.rb +46 -0
- data/lib/action_dispatch/testing/assertions/response.rb +44 -22
- data/lib/action_dispatch/testing/assertions/routing.rb +47 -31
- data/lib/action_dispatch/testing/assertions.rb +6 -4
- data/lib/action_dispatch/testing/integration.rb +391 -220
- data/lib/action_dispatch/testing/request_encoder.rb +55 -0
- data/lib/action_dispatch/testing/test_process.rb +53 -22
- data/lib/action_dispatch/testing/test_request.rb +27 -34
- data/lib/action_dispatch/testing/test_response.rb +11 -11
- data/lib/action_dispatch.rb +35 -21
- data/lib/action_pack/gem_version.rb +6 -4
- data/lib/action_pack/version.rb +3 -1
- data/lib/action_pack.rb +4 -2
- metadata +78 -48
- data/lib/action_controller/metal/force_ssl.rb +0 -97
- data/lib/action_controller/metal/hide_actions.rb +0 -40
- data/lib/action_controller/metal/rack_delegation.rb +0 -32
- data/lib/action_controller/middleware.rb +0 -39
- data/lib/action_controller/model_naming.rb +0 -12
- data/lib/action_dispatch/http/parameter_filter.rb +0 -72
- data/lib/action_dispatch/journey/backwards.rb +0 -5
- data/lib/action_dispatch/journey/nfa/builder.rb +0 -76
- data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
- data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -163
- data/lib/action_dispatch/journey/router/strexp.rb +0 -27
- data/lib/action_dispatch/middleware/params_parser.rb +0 -60
- data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
- data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
- data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,6 +1,8 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/hash/keys"
|
4
|
+
require "action_dispatch/middleware/session/abstract_store"
|
5
|
+
require "rack/session/cookie"
|
4
6
|
|
5
7
|
module ActionDispatch
|
6
8
|
module Session
|
@@ -8,116 +10,113 @@ module ActionDispatch
|
|
8
10
|
# dramatically faster than the alternatives.
|
9
11
|
#
|
10
12
|
# Sessions typically contain at most a user_id and flash message; both fit
|
11
|
-
# within the
|
12
|
-
# you attempt to store more than
|
13
|
+
# within the 4096 bytes cookie size limit. A CookieOverflow exception is raised if
|
14
|
+
# you attempt to store more than 4096 bytes of data.
|
13
15
|
#
|
14
16
|
# The cookie jar used for storage is automatically configured to be the
|
15
17
|
# best possible option given your application's configuration.
|
16
18
|
#
|
17
|
-
#
|
18
|
-
# not encrypted. This means a user cannot alter their +user_id+ without
|
19
|
-
# knowing your app's secret key, but can easily read their +user_id+. This
|
20
|
-
# was the default for Rails 3 apps.
|
21
|
-
#
|
22
|
-
# If you have secret_key_base set, your cookies will be encrypted. This
|
19
|
+
# Your cookies will be encrypted using your apps secret_key_base. This
|
23
20
|
# goes a step further than signed cookies in that encrypted cookies cannot
|
24
21
|
# be altered or read by users. This is the default starting in Rails 4.
|
25
22
|
#
|
26
|
-
#
|
27
|
-
# be encrypted, and signed cookies generated by Rails 3 will be
|
28
|
-
# transparently read and encrypted to provide a smooth upgrade path.
|
29
|
-
#
|
30
|
-
# Configure your session store in config/initializers/session_store.rb:
|
23
|
+
# Configure your session store in an initializer:
|
31
24
|
#
|
32
25
|
# Rails.application.config.session_store :cookie_store, key: '_your_app_session'
|
33
26
|
#
|
34
|
-
#
|
27
|
+
# In the development and test environments your application's secret key base is
|
28
|
+
# generated by Rails and stored in a temporary file in <tt>tmp/development_secret.txt</tt>.
|
29
|
+
# In all other environments, it is stored encrypted in the
|
30
|
+
# <tt>config/credentials.yml.enc</tt> file.
|
35
31
|
#
|
36
|
-
#
|
37
|
-
#
|
32
|
+
# If your application was not updated to Rails 5.2 defaults, the secret_key_base
|
33
|
+
# will be found in the old <tt>config/secrets.yml</tt> file.
|
38
34
|
#
|
39
|
-
#
|
35
|
+
# Note that changing your secret_key_base will invalidate all existing session.
|
36
|
+
# Additionally, you should take care to make sure you are not relying on the
|
37
|
+
# ability to decode signed cookies generated by your app in external
|
38
|
+
# applications or JavaScript before changing it.
|
40
39
|
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
# your userbase on Rails 4 and are reasonably sure you will not need to
|
45
|
-
# rollback to Rails 3. This is because cookies signed based on the new
|
46
|
-
# secret_key_base in Rails 4 are not backwards compatible with Rails 3.
|
47
|
-
# You are free to leave your existing secret_token in place, not set the
|
48
|
-
# new secret_key_base, and ignore the deprecation warnings until you are
|
49
|
-
# reasonably sure that your upgrade is otherwise complete. Additionally,
|
50
|
-
# you should take care to make sure you are not relying on the ability to
|
51
|
-
# decode signed cookies generated by your app in external applications or
|
52
|
-
# JavaScript before upgrading.
|
40
|
+
# Because CookieStore extends Rack::Session::Abstract::Persisted, many of the
|
41
|
+
# options described there can be used to customize the session cookie that
|
42
|
+
# is generated. For example:
|
53
43
|
#
|
54
|
-
#
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
44
|
+
# Rails.application.config.session_store :cookie_store, expire_after: 14.days
|
45
|
+
#
|
46
|
+
# would set the session cookie to expire automatically 14 days after creation.
|
47
|
+
# Other useful options include <tt>:key</tt>, <tt>:secure</tt> and
|
48
|
+
# <tt>:httponly</tt>.
|
49
|
+
class CookieStore < AbstractSecureStore
|
50
|
+
class SessionId < DelegateClass(Rack::Session::SessionId)
|
51
|
+
attr_reader :cookie_value
|
52
|
+
|
53
|
+
def initialize(session_id, cookie_value = {})
|
54
|
+
super(session_id)
|
55
|
+
@cookie_value = cookie_value
|
56
|
+
end
|
57
|
+
end
|
59
58
|
|
60
|
-
def initialize(app, options={})
|
61
|
-
super(app, options.merge!(:
|
59
|
+
def initialize(app, options = {})
|
60
|
+
super(app, options.merge!(cookie_only: true))
|
62
61
|
end
|
63
62
|
|
64
|
-
def
|
63
|
+
def delete_session(req, session_id, options)
|
65
64
|
new_sid = generate_sid unless options[:drop]
|
66
65
|
# Reset hash and Assign the new session id
|
67
|
-
|
66
|
+
req.set_header("action_dispatch.request.unsigned_session_cookie", new_sid ? { "session_id" => new_sid.public_id } : {})
|
68
67
|
new_sid
|
69
68
|
end
|
70
69
|
|
71
|
-
def load_session(
|
70
|
+
def load_session(req)
|
72
71
|
stale_session_check! do
|
73
|
-
data = unpacked_cookie_data(
|
72
|
+
data = unpacked_cookie_data(req)
|
74
73
|
data = persistent_session_id!(data)
|
75
|
-
[data["session_id"], data]
|
74
|
+
[Rack::Session::SessionId.new(data["session_id"]), data]
|
76
75
|
end
|
77
76
|
end
|
78
77
|
|
79
78
|
private
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
79
|
+
def extract_session_id(req)
|
80
|
+
stale_session_check! do
|
81
|
+
sid = unpacked_cookie_data(req)["session_id"]
|
82
|
+
sid && Rack::Session::SessionId.new(sid)
|
83
|
+
end
|
84
84
|
end
|
85
|
-
end
|
86
85
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
86
|
+
def unpacked_cookie_data(req)
|
87
|
+
req.fetch_header("action_dispatch.request.unsigned_session_cookie") do |k|
|
88
|
+
v = stale_session_check! do
|
89
|
+
if data = get_cookie(req)
|
90
|
+
data.stringify_keys!
|
91
|
+
end
|
92
|
+
data || {}
|
92
93
|
end
|
93
|
-
|
94
|
+
req.set_header k, v
|
94
95
|
end
|
95
96
|
end
|
96
|
-
end
|
97
97
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
98
|
+
def persistent_session_id!(data, sid = nil)
|
99
|
+
data ||= {}
|
100
|
+
data["session_id"] ||= sid || generate_sid.public_id
|
101
|
+
data
|
102
|
+
end
|
103
103
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
104
|
+
def write_session(req, sid, session_data, options)
|
105
|
+
session_data["session_id"] = sid.public_id
|
106
|
+
SessionId.new(sid, session_data)
|
107
|
+
end
|
108
108
|
|
109
|
-
|
110
|
-
|
111
|
-
|
109
|
+
def set_cookie(request, session_id, cookie)
|
110
|
+
cookie_jar(request)[@key] = cookie
|
111
|
+
end
|
112
112
|
|
113
|
-
|
114
|
-
|
115
|
-
|
113
|
+
def get_cookie(req)
|
114
|
+
cookie_jar(req)[@key]
|
115
|
+
end
|
116
116
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
end
|
117
|
+
def cookie_jar(request)
|
118
|
+
request.cookie_jar.signed_or_encrypted
|
119
|
+
end
|
121
120
|
end
|
122
121
|
end
|
123
122
|
end
|
@@ -1,6 +1,8 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_dispatch/middleware/session/abstract_store"
|
2
4
|
begin
|
3
|
-
require
|
5
|
+
require "rack/session/dalli"
|
4
6
|
rescue LoadError => e
|
5
7
|
$stderr.puts "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install"
|
6
8
|
raise e
|
@@ -8,6 +10,10 @@ end
|
|
8
10
|
|
9
11
|
module ActionDispatch
|
10
12
|
module Session
|
13
|
+
# A session store that uses MemCache to implement storage.
|
14
|
+
#
|
15
|
+
# ==== Options
|
16
|
+
# * <tt>expire_after</tt> - The length of time a session will be stored before automatically expiring.
|
11
17
|
class MemCacheStore < Rack::Session::Dalli
|
12
18
|
include Compatibility
|
13
19
|
include StaleSessionCheck
|
@@ -1,5 +1,7 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_dispatch/http/request"
|
4
|
+
require "action_dispatch/middleware/exception_wrapper"
|
3
5
|
|
4
6
|
module ActionDispatch
|
5
7
|
# This middleware rescues any exception returned by the application
|
@@ -8,14 +10,14 @@ module ActionDispatch
|
|
8
10
|
# The exceptions app should be passed as parameter on initialization
|
9
11
|
# of ShowExceptions. Every time there is an exception, ShowExceptions will
|
10
12
|
# store the exception in env["action_dispatch.exception"], rewrite the
|
11
|
-
# PATH_INFO to the exception status code and call the
|
13
|
+
# PATH_INFO to the exception status code and call the Rack app.
|
12
14
|
#
|
13
15
|
# If the application returns a "X-Cascade" pass response, this middleware
|
14
16
|
# will send an empty response as result with the correct status code.
|
15
17
|
# If any exception happens inside the exceptions app, this middleware
|
16
18
|
# catches the exceptions and returns a FAILSAFE_RESPONSE.
|
17
19
|
class ShowExceptions
|
18
|
-
FAILSAFE_RESPONSE = [500, {
|
20
|
+
FAILSAFE_RESPONSE = [500, { "Content-Type" => "text/plain" },
|
19
21
|
["500 Internal Server Error\n" \
|
20
22
|
"If you are the administrator of this website, then please read this web " \
|
21
23
|
"application's log file and/or the web server's log file to find out what " \
|
@@ -27,32 +29,35 @@ module ActionDispatch
|
|
27
29
|
end
|
28
30
|
|
29
31
|
def call(env)
|
32
|
+
request = ActionDispatch::Request.new env
|
30
33
|
@app.call(env)
|
31
34
|
rescue Exception => exception
|
32
|
-
if
|
33
|
-
|
35
|
+
if request.show_exceptions?
|
36
|
+
render_exception(request, exception)
|
34
37
|
else
|
35
|
-
|
38
|
+
raise exception
|
36
39
|
end
|
37
40
|
end
|
38
41
|
|
39
42
|
private
|
43
|
+
def render_exception(request, exception)
|
44
|
+
backtrace_cleaner = request.get_header "action_dispatch.backtrace_cleaner"
|
45
|
+
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
|
46
|
+
status = wrapper.status_code
|
47
|
+
request.set_header "action_dispatch.exception", wrapper.unwrapped_exception
|
48
|
+
request.set_header "action_dispatch.original_path", request.path_info
|
49
|
+
request.set_header "action_dispatch.original_request_method", request.raw_request_method
|
50
|
+
request.path_info = "/#{status}"
|
51
|
+
request.request_method = "GET"
|
52
|
+
response = @exceptions_app.call(request.env)
|
53
|
+
response[1]["X-Cascade"] == "pass" ? pass_response(status) : response
|
54
|
+
rescue Exception => failsafe_error
|
55
|
+
$stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
|
56
|
+
FAILSAFE_RESPONSE
|
57
|
+
end
|
40
58
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
env["action_dispatch.exception"] = wrapper.exception
|
45
|
-
env["action_dispatch.original_path"] = env["PATH_INFO"]
|
46
|
-
env["PATH_INFO"] = "/#{status}"
|
47
|
-
response = @exceptions_app.call(env)
|
48
|
-
response[1]['X-Cascade'] == 'pass' ? pass_response(status) : response
|
49
|
-
rescue Exception => failsafe_error
|
50
|
-
$stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
|
51
|
-
FAILSAFE_RESPONSE
|
52
|
-
end
|
53
|
-
|
54
|
-
def pass_response(status)
|
55
|
-
[status, {"Content-Type" => "text/html; charset=#{Response.default_charset}", "Content-Length" => "0"}, []]
|
56
|
-
end
|
59
|
+
def pass_response(status)
|
60
|
+
[status, { "Content-Type" => "text/html; charset=#{Response.default_charset}", "Content-Length" => "0" }, []]
|
61
|
+
end
|
57
62
|
end
|
58
63
|
end
|
@@ -1,66 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActionDispatch
|
4
|
+
# This middleware is added to the stack when <tt>config.force_ssl = true</tt>, and is passed
|
5
|
+
# the options set in +config.ssl_options+. It does three jobs to enforce secure HTTP
|
6
|
+
# requests:
|
7
|
+
#
|
8
|
+
# 1. <b>TLS redirect</b>: Permanently redirects +http://+ requests to +https://+
|
9
|
+
# with the same URL host, path, etc. Enabled by default. Set +config.ssl_options+
|
10
|
+
# to modify the destination URL
|
11
|
+
# (e.g. <tt>redirect: { host: "secure.widgets.com", port: 8080 }</tt>), or set
|
12
|
+
# <tt>redirect: false</tt> to disable this feature.
|
13
|
+
#
|
14
|
+
# Requests can opt-out of redirection with +exclude+:
|
15
|
+
#
|
16
|
+
# config.ssl_options = { redirect: { exclude: -> request { /healthcheck/.match?(request.path) } } }
|
17
|
+
#
|
18
|
+
# Cookies will not be flagged as secure for excluded requests.
|
19
|
+
#
|
20
|
+
# 2. <b>Secure cookies</b>: Sets the +secure+ flag on cookies to tell browsers they
|
21
|
+
# must not be sent along with +http://+ requests. Enabled by default. Set
|
22
|
+
# +config.ssl_options+ with <tt>secure_cookies: false</tt> to disable this feature.
|
23
|
+
#
|
24
|
+
# 3. <b>HTTP Strict Transport Security (HSTS)</b>: Tells the browser to remember
|
25
|
+
# this site as TLS-only and automatically redirect non-TLS requests.
|
26
|
+
# Enabled by default. Configure +config.ssl_options+ with <tt>hsts: false</tt> to disable.
|
27
|
+
#
|
28
|
+
# Set +config.ssl_options+ with <tt>hsts: { ... }</tt> to configure HSTS:
|
29
|
+
#
|
30
|
+
# * +expires+: How long, in seconds, these settings will stick. The minimum
|
31
|
+
# required to qualify for browser preload lists is 1 year. Defaults to
|
32
|
+
# 2 years (recommended).
|
33
|
+
#
|
34
|
+
# * +subdomains+: Set to +true+ to tell the browser to apply these settings
|
35
|
+
# to all subdomains. This protects your cookies from interception by a
|
36
|
+
# vulnerable site on a subdomain. Defaults to +true+.
|
37
|
+
#
|
38
|
+
# * +preload+: Advertise that this site may be included in browsers'
|
39
|
+
# preloaded HSTS lists. HSTS protects your site on every visit <i>except the
|
40
|
+
# first visit</i> since it hasn't seen your HSTS header yet. To close this
|
41
|
+
# gap, browser vendors include a baked-in list of HSTS-enabled sites.
|
42
|
+
# Go to https://hstspreload.org to submit your site for inclusion.
|
43
|
+
# Defaults to +false+.
|
44
|
+
#
|
45
|
+
# To turn off HSTS, omitting the header is not enough. Browsers will remember the
|
46
|
+
# original HSTS directive until it expires. Instead, use the header to tell browsers to
|
47
|
+
# expire HSTS immediately. Setting <tt>hsts: false</tt> is a shortcut for
|
48
|
+
# <tt>hsts: { expires: 0 }</tt>.
|
2
49
|
class SSL
|
3
|
-
|
50
|
+
# :stopdoc:
|
51
|
+
|
52
|
+
# Default to 2 years as recommended on hstspreload.org.
|
53
|
+
HSTS_EXPIRES_IN = 63072000
|
54
|
+
|
55
|
+
PERMANENT_REDIRECT_REQUEST_METHODS = %w[GET HEAD] # :nodoc:
|
4
56
|
|
5
57
|
def self.default_hsts_options
|
6
|
-
{ :
|
58
|
+
{ expires: HSTS_EXPIRES_IN, subdomains: true, preload: false }
|
7
59
|
end
|
8
60
|
|
9
|
-
def initialize(app,
|
61
|
+
def initialize(app, redirect: {}, hsts: {}, secure_cookies: true, ssl_default_redirect_status: nil)
|
10
62
|
@app = app
|
11
63
|
|
12
|
-
@
|
13
|
-
@hsts = {} if @hsts == true
|
14
|
-
@hsts = self.class.default_hsts_options.merge(@hsts) if @hsts
|
64
|
+
@redirect = redirect
|
15
65
|
|
16
|
-
@
|
17
|
-
@
|
66
|
+
@exclude = @redirect && @redirect[:exclude] || proc { !@redirect }
|
67
|
+
@secure_cookies = secure_cookies
|
68
|
+
|
69
|
+
@hsts_header = build_hsts_header(normalize_hsts_options(hsts))
|
70
|
+
@ssl_default_redirect_status = ssl_default_redirect_status
|
18
71
|
end
|
19
72
|
|
20
73
|
def call(env)
|
21
|
-
request = Request.new
|
74
|
+
request = Request.new env
|
22
75
|
|
23
76
|
if request.ssl?
|
24
|
-
status, headers, body
|
25
|
-
|
26
|
-
|
27
|
-
|
77
|
+
@app.call(env).tap do |status, headers, body|
|
78
|
+
set_hsts_header! headers
|
79
|
+
flag_cookies_as_secure! headers if @secure_cookies && !@exclude.call(request)
|
80
|
+
end
|
28
81
|
else
|
29
|
-
redirect_to_https(request)
|
82
|
+
return redirect_to_https request unless @exclude.call(request)
|
83
|
+
@app.call(env)
|
30
84
|
end
|
31
85
|
end
|
32
86
|
|
33
87
|
private
|
34
|
-
def
|
35
|
-
|
36
|
-
port = @port || request.port
|
37
|
-
|
38
|
-
location = "https://#{host}"
|
39
|
-
location << ":#{port}" if port != 80
|
40
|
-
location << request.fullpath
|
41
|
-
|
42
|
-
headers = { 'Content-Type' => 'text/html', 'Location' => location }
|
43
|
-
|
44
|
-
[301, headers, []]
|
88
|
+
def set_hsts_header!(headers)
|
89
|
+
headers["Strict-Transport-Security"] ||= @hsts_header
|
45
90
|
end
|
46
91
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
92
|
+
def normalize_hsts_options(options)
|
93
|
+
case options
|
94
|
+
# Explicitly disabling HSTS clears the existing setting from browsers
|
95
|
+
# by setting expiry to 0.
|
96
|
+
when false
|
97
|
+
self.class.default_hsts_options.merge(expires: 0)
|
98
|
+
# Default to enabled, with default options.
|
99
|
+
when nil, true
|
100
|
+
self.class.default_hsts_options
|
53
101
|
else
|
54
|
-
|
102
|
+
self.class.default_hsts_options.merge(options)
|
55
103
|
end
|
56
104
|
end
|
57
105
|
|
106
|
+
# https://tools.ietf.org/html/rfc6797#section-6.1
|
107
|
+
def build_hsts_header(hsts)
|
108
|
+
value = +"max-age=#{hsts[:expires].to_i}"
|
109
|
+
value << "; includeSubDomains" if hsts[:subdomains]
|
110
|
+
value << "; preload" if hsts[:preload]
|
111
|
+
value
|
112
|
+
end
|
113
|
+
|
58
114
|
def flag_cookies_as_secure!(headers)
|
59
|
-
if cookies = headers[
|
115
|
+
if cookies = headers["Set-Cookie"]
|
60
116
|
cookies = cookies.split("\n")
|
61
117
|
|
62
|
-
headers[
|
63
|
-
if
|
118
|
+
headers["Set-Cookie"] = cookies.map { |cookie|
|
119
|
+
if !/;\s*secure\s*(;|$)/i.match?(cookie)
|
64
120
|
"#{cookie}; secure"
|
65
121
|
else
|
66
122
|
cookie
|
@@ -68,5 +124,32 @@ module ActionDispatch
|
|
68
124
|
}.join("\n")
|
69
125
|
end
|
70
126
|
end
|
127
|
+
|
128
|
+
def redirect_to_https(request)
|
129
|
+
[ @redirect.fetch(:status, redirection_status(request)),
|
130
|
+
{ "Content-Type" => "text/html",
|
131
|
+
"Location" => https_location_for(request) },
|
132
|
+
(@redirect[:body] || []) ]
|
133
|
+
end
|
134
|
+
|
135
|
+
def redirection_status(request)
|
136
|
+
if PERMANENT_REDIRECT_REQUEST_METHODS.include?(request.raw_request_method)
|
137
|
+
301 # Issue a permanent redirect via a GET request.
|
138
|
+
elsif @ssl_default_redirect_status
|
139
|
+
@ssl_default_redirect_status
|
140
|
+
else
|
141
|
+
307 # Issue a fresh request redirect to preserve the HTTP method.
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def https_location_for(request)
|
146
|
+
host = @redirect[:host] || request.host
|
147
|
+
port = @redirect[:port] || request.port
|
148
|
+
|
149
|
+
location = +"https://#{host}"
|
150
|
+
location << ":#{port}" if port != 80 && port != 443
|
151
|
+
location << request.fullpath
|
152
|
+
location
|
153
|
+
end
|
71
154
|
end
|
72
155
|
end
|