omg-actionpack 8.0.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +129 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +57 -0
- data/lib/abstract_controller/asset_paths.rb +14 -0
- data/lib/abstract_controller/base.rb +299 -0
- data/lib/abstract_controller/caching/fragments.rb +149 -0
- data/lib/abstract_controller/caching.rb +68 -0
- data/lib/abstract_controller/callbacks.rb +265 -0
- data/lib/abstract_controller/collector.rb +44 -0
- data/lib/abstract_controller/deprecator.rb +9 -0
- data/lib/abstract_controller/error.rb +8 -0
- data/lib/abstract_controller/helpers.rb +243 -0
- data/lib/abstract_controller/logger.rb +16 -0
- data/lib/abstract_controller/railties/routes_helpers.rb +25 -0
- data/lib/abstract_controller/rendering.rb +126 -0
- data/lib/abstract_controller/translation.rb +42 -0
- data/lib/abstract_controller/url_for.rb +37 -0
- data/lib/abstract_controller.rb +36 -0
- data/lib/action_controller/api/api_rendering.rb +18 -0
- data/lib/action_controller/api.rb +155 -0
- data/lib/action_controller/base.rb +332 -0
- data/lib/action_controller/caching.rb +49 -0
- data/lib/action_controller/deprecator.rb +9 -0
- data/lib/action_controller/form_builder.rb +55 -0
- data/lib/action_controller/log_subscriber.rb +96 -0
- data/lib/action_controller/metal/allow_browser.rb +123 -0
- data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
- data/lib/action_controller/metal/conditional_get.rb +341 -0
- data/lib/action_controller/metal/content_security_policy.rb +86 -0
- data/lib/action_controller/metal/cookies.rb +20 -0
- data/lib/action_controller/metal/data_streaming.rb +154 -0
- data/lib/action_controller/metal/default_headers.rb +21 -0
- data/lib/action_controller/metal/etag_with_flash.rb +22 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +59 -0
- data/lib/action_controller/metal/exceptions.rb +106 -0
- data/lib/action_controller/metal/flash.rb +67 -0
- data/lib/action_controller/metal/head.rb +67 -0
- data/lib/action_controller/metal/helpers.rb +129 -0
- data/lib/action_controller/metal/http_authentication.rb +565 -0
- data/lib/action_controller/metal/implicit_render.rb +67 -0
- data/lib/action_controller/metal/instrumentation.rb +120 -0
- data/lib/action_controller/metal/live.rb +398 -0
- data/lib/action_controller/metal/logging.rb +22 -0
- data/lib/action_controller/metal/mime_responds.rb +337 -0
- data/lib/action_controller/metal/parameter_encoding.rb +84 -0
- data/lib/action_controller/metal/params_wrapper.rb +312 -0
- data/lib/action_controller/metal/permissions_policy.rb +38 -0
- data/lib/action_controller/metal/rate_limiting.rb +62 -0
- data/lib/action_controller/metal/redirecting.rb +251 -0
- data/lib/action_controller/metal/renderers.rb +181 -0
- data/lib/action_controller/metal/rendering.rb +260 -0
- data/lib/action_controller/metal/request_forgery_protection.rb +667 -0
- data/lib/action_controller/metal/rescue.rb +33 -0
- data/lib/action_controller/metal/streaming.rb +183 -0
- data/lib/action_controller/metal/strong_parameters.rb +1546 -0
- data/lib/action_controller/metal/testing.rb +25 -0
- data/lib/action_controller/metal/url_for.rb +65 -0
- data/lib/action_controller/metal.rb +339 -0
- data/lib/action_controller/railtie.rb +149 -0
- data/lib/action_controller/railties/helpers.rb +26 -0
- data/lib/action_controller/renderer.rb +161 -0
- data/lib/action_controller/template_assertions.rb +13 -0
- data/lib/action_controller/test_case.rb +691 -0
- data/lib/action_controller.rb +80 -0
- data/lib/action_dispatch/constants.rb +34 -0
- data/lib/action_dispatch/deprecator.rb +9 -0
- data/lib/action_dispatch/http/cache.rb +249 -0
- data/lib/action_dispatch/http/content_disposition.rb +47 -0
- data/lib/action_dispatch/http/content_security_policy.rb +365 -0
- data/lib/action_dispatch/http/filter_parameters.rb +80 -0
- data/lib/action_dispatch/http/filter_redirect.rb +50 -0
- data/lib/action_dispatch/http/headers.rb +134 -0
- data/lib/action_dispatch/http/mime_negotiation.rb +187 -0
- data/lib/action_dispatch/http/mime_type.rb +389 -0
- data/lib/action_dispatch/http/mime_types.rb +54 -0
- data/lib/action_dispatch/http/parameters.rb +119 -0
- data/lib/action_dispatch/http/permissions_policy.rb +189 -0
- data/lib/action_dispatch/http/rack_cache.rb +67 -0
- data/lib/action_dispatch/http/request.rb +498 -0
- data/lib/action_dispatch/http/response.rb +556 -0
- data/lib/action_dispatch/http/upload.rb +107 -0
- data/lib/action_dispatch/http/url.rb +344 -0
- data/lib/action_dispatch/journey/formatter.rb +226 -0
- data/lib/action_dispatch/journey/gtg/builder.rb +149 -0
- data/lib/action_dispatch/journey/gtg/simulator.rb +50 -0
- data/lib/action_dispatch/journey/gtg/transition_table.rb +217 -0
- data/lib/action_dispatch/journey/nfa/dot.rb +27 -0
- data/lib/action_dispatch/journey/nodes/node.rb +208 -0
- data/lib/action_dispatch/journey/parser.rb +103 -0
- data/lib/action_dispatch/journey/path/pattern.rb +209 -0
- data/lib/action_dispatch/journey/route.rb +189 -0
- data/lib/action_dispatch/journey/router/utils.rb +105 -0
- data/lib/action_dispatch/journey/router.rb +151 -0
- data/lib/action_dispatch/journey/routes.rb +82 -0
- data/lib/action_dispatch/journey/scanner.rb +70 -0
- data/lib/action_dispatch/journey/visitors.rb +267 -0
- data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
- data/lib/action_dispatch/journey/visualizer/fsm.js +159 -0
- data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
- data/lib/action_dispatch/journey.rb +7 -0
- data/lib/action_dispatch/log_subscriber.rb +25 -0
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
- data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
- data/lib/action_dispatch/middleware/callbacks.rb +38 -0
- data/lib/action_dispatch/middleware/cookies.rb +719 -0
- data/lib/action_dispatch/middleware/debug_exceptions.rb +206 -0
- data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
- data/lib/action_dispatch/middleware/debug_view.rb +73 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +350 -0
- data/lib/action_dispatch/middleware/executor.rb +32 -0
- data/lib/action_dispatch/middleware/flash.rb +318 -0
- data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +64 -0
- data/lib/action_dispatch/middleware/reloader.rb +16 -0
- data/lib/action_dispatch/middleware/remote_ip.rb +199 -0
- data/lib/action_dispatch/middleware/request_id.rb +50 -0
- data/lib/action_dispatch/middleware/server_timing.rb +78 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +112 -0
- data/lib/action_dispatch/middleware/session/cache_store.rb +66 -0
- data/lib/action_dispatch/middleware/session/cookie_store.rb +129 -0
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +34 -0
- data/lib/action_dispatch/middleware/show_exceptions.rb +88 -0
- data/lib/action_dispatch/middleware/ssl.rb +180 -0
- data/lib/action_dispatch/middleware/stack.rb +194 -0
- data/lib/action_dispatch/middleware/static.rb +192 -0
- data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
- data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +17 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +62 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +12 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +35 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +16 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +284 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +23 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +19 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +232 -0
- data/lib/action_dispatch/railtie.rb +77 -0
- data/lib/action_dispatch/request/session.rb +283 -0
- data/lib/action_dispatch/request/utils.rb +109 -0
- data/lib/action_dispatch/routing/endpoint.rb +19 -0
- data/lib/action_dispatch/routing/inspector.rb +323 -0
- data/lib/action_dispatch/routing/mapper.rb +2372 -0
- data/lib/action_dispatch/routing/polymorphic_routes.rb +363 -0
- data/lib/action_dispatch/routing/redirection.rb +218 -0
- data/lib/action_dispatch/routing/route_set.rb +958 -0
- data/lib/action_dispatch/routing/routes_proxy.rb +66 -0
- data/lib/action_dispatch/routing/url_for.rb +244 -0
- data/lib/action_dispatch/routing.rb +262 -0
- data/lib/action_dispatch/system_test_case.rb +206 -0
- data/lib/action_dispatch/system_testing/browser.rb +75 -0
- data/lib/action_dispatch/system_testing/driver.rb +85 -0
- data/lib/action_dispatch/system_testing/server.rb +33 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
- data/lib/action_dispatch/testing/assertion_response.rb +48 -0
- data/lib/action_dispatch/testing/assertions/response.rb +114 -0
- data/lib/action_dispatch/testing/assertions/routing.rb +343 -0
- data/lib/action_dispatch/testing/assertions.rb +25 -0
- data/lib/action_dispatch/testing/integration.rb +694 -0
- data/lib/action_dispatch/testing/request_encoder.rb +60 -0
- data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
- data/lib/action_dispatch/testing/test_process.rb +57 -0
- data/lib/action_dispatch/testing/test_request.rb +73 -0
- data/lib/action_dispatch/testing/test_response.rb +58 -0
- data/lib/action_dispatch.rb +147 -0
- data/lib/action_pack/gem_version.rb +19 -0
- data/lib/action_pack/version.rb +12 -0
- data/lib/action_pack.rb +27 -0
- metadata +375 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
require "action_dispatch/middleware/session/abstract_store"
|
6
|
+
|
7
|
+
module ActionDispatch
|
8
|
+
module Session
|
9
|
+
# # Action Dispatch Session CacheStore
|
10
|
+
#
|
11
|
+
# A session store that uses an ActiveSupport::Cache::Store to store the
|
12
|
+
# sessions. This store is most useful if you don't store critical data in your
|
13
|
+
# sessions and you don't need them to live for extended periods of time.
|
14
|
+
#
|
15
|
+
# #### Options
|
16
|
+
# * `cache` - The cache to use. If it is not specified, `Rails.cache`
|
17
|
+
# will be used.
|
18
|
+
# * `expire_after` - The length of time a session will be stored before
|
19
|
+
# automatically expiring. By default, the `:expires_in` option of the cache
|
20
|
+
# is used.
|
21
|
+
#
|
22
|
+
class CacheStore < AbstractSecureStore
|
23
|
+
def initialize(app, options = {})
|
24
|
+
@cache = options[:cache] || Rails.cache
|
25
|
+
options[:expire_after] ||= @cache.options[:expires_in]
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
29
|
+
# Get a session from the cache.
|
30
|
+
def find_session(env, sid)
|
31
|
+
unless sid && (session = get_session_with_fallback(sid))
|
32
|
+
sid, session = generate_sid, {}
|
33
|
+
end
|
34
|
+
[sid, session]
|
35
|
+
end
|
36
|
+
|
37
|
+
# Set a session in the cache.
|
38
|
+
def write_session(env, sid, session, options)
|
39
|
+
key = cache_key(sid.private_id)
|
40
|
+
if session
|
41
|
+
@cache.write(key, session, expires_in: options[:expire_after])
|
42
|
+
else
|
43
|
+
@cache.delete(key)
|
44
|
+
end
|
45
|
+
sid
|
46
|
+
end
|
47
|
+
|
48
|
+
# Remove a session from the cache.
|
49
|
+
def delete_session(env, sid, options)
|
50
|
+
@cache.delete(cache_key(sid.private_id))
|
51
|
+
@cache.delete(cache_key(sid.public_id))
|
52
|
+
generate_sid
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
# Turn the session id into a cache key.
|
57
|
+
def cache_key(id)
|
58
|
+
"_session_id:#{id}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def get_session_with_fallback(sid)
|
62
|
+
@cache.read(cache_key(sid.private_id)) || @cache.read(cache_key(sid.public_id))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
require "active_support/core_ext/hash/keys"
|
6
|
+
require "action_dispatch/middleware/session/abstract_store"
|
7
|
+
require "rack/session/cookie"
|
8
|
+
|
9
|
+
module ActionDispatch
|
10
|
+
module Session
|
11
|
+
# # Action Dispatch Session CookieStore
|
12
|
+
#
|
13
|
+
# This cookie-based session store is the Rails default. It is dramatically
|
14
|
+
# faster than the alternatives.
|
15
|
+
#
|
16
|
+
# Sessions typically contain at most a user ID and flash message; both fit
|
17
|
+
# within the 4096 bytes cookie size limit. A `CookieOverflow` exception is
|
18
|
+
# raised if you attempt to store more than 4096 bytes of data.
|
19
|
+
#
|
20
|
+
# The cookie jar used for storage is automatically configured to be the best
|
21
|
+
# possible option given your application's configuration.
|
22
|
+
#
|
23
|
+
# Your cookies will be encrypted using your application's `secret_key_base`.
|
24
|
+
# This goes a step further than signed cookies in that encrypted cookies cannot
|
25
|
+
# be altered or read by users. This is the default starting in Rails 4.
|
26
|
+
#
|
27
|
+
# Configure your session store in an initializer:
|
28
|
+
#
|
29
|
+
# Rails.application.config.session_store :cookie_store, key: '_your_app_session'
|
30
|
+
#
|
31
|
+
# In the development and test environments your application's `secret_key_base`
|
32
|
+
# is generated by Rails and stored in a temporary file in
|
33
|
+
# `tmp/local_secret.txt`. In all other environments, it is stored encrypted in
|
34
|
+
# the `config/credentials.yml.enc` file.
|
35
|
+
#
|
36
|
+
# If your application was not updated to Rails 5.2 defaults, the
|
37
|
+
# `secret_key_base` will be found in the old `config/secrets.yml` file.
|
38
|
+
#
|
39
|
+
# Note that changing your `secret_key_base` will invalidate all existing
|
40
|
+
# session. Additionally, you should take care to make sure you are not relying
|
41
|
+
# on the ability to decode signed cookies generated by your app in external
|
42
|
+
# applications or JavaScript before changing it.
|
43
|
+
#
|
44
|
+
# Because CookieStore extends `Rack::Session::Abstract::Persisted`, many of the
|
45
|
+
# options described there can be used to customize the session cookie that is
|
46
|
+
# generated. For example:
|
47
|
+
#
|
48
|
+
# Rails.application.config.session_store :cookie_store, expire_after: 14.days
|
49
|
+
#
|
50
|
+
# would set the session cookie to expire automatically 14 days after creation.
|
51
|
+
# Other useful options include `:key`, `:secure`, `:httponly`, and `:same_site`.
|
52
|
+
class CookieStore < AbstractSecureStore
|
53
|
+
class SessionId < DelegateClass(Rack::Session::SessionId)
|
54
|
+
attr_reader :cookie_value
|
55
|
+
|
56
|
+
def initialize(session_id, cookie_value = {})
|
57
|
+
super(session_id)
|
58
|
+
@cookie_value = cookie_value
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
DEFAULT_SAME_SITE = proc { |request| request.cookies_same_site_protection } # :nodoc:
|
63
|
+
|
64
|
+
def initialize(app, options = {})
|
65
|
+
options[:cookie_only] = true
|
66
|
+
options[:same_site] = DEFAULT_SAME_SITE if !options.key?(:same_site)
|
67
|
+
super
|
68
|
+
end
|
69
|
+
|
70
|
+
def delete_session(req, session_id, options)
|
71
|
+
new_sid = generate_sid unless options[:drop]
|
72
|
+
# Reset hash and Assign the new session id
|
73
|
+
req.set_header("action_dispatch.request.unsigned_session_cookie", new_sid ? { "session_id" => new_sid.public_id } : {})
|
74
|
+
new_sid
|
75
|
+
end
|
76
|
+
|
77
|
+
def load_session(req)
|
78
|
+
stale_session_check! do
|
79
|
+
data = unpacked_cookie_data(req)
|
80
|
+
data = persistent_session_id!(data)
|
81
|
+
[Rack::Session::SessionId.new(data["session_id"]), data]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
def extract_session_id(req)
|
87
|
+
stale_session_check! do
|
88
|
+
sid = unpacked_cookie_data(req)["session_id"]
|
89
|
+
sid && Rack::Session::SessionId.new(sid)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def unpacked_cookie_data(req)
|
94
|
+
req.fetch_header("action_dispatch.request.unsigned_session_cookie") do |k|
|
95
|
+
v = stale_session_check! do
|
96
|
+
if data = get_cookie(req)
|
97
|
+
data.stringify_keys!
|
98
|
+
end
|
99
|
+
data || {}
|
100
|
+
end
|
101
|
+
req.set_header k, v
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def persistent_session_id!(data, sid = nil)
|
106
|
+
data ||= {}
|
107
|
+
data["session_id"] ||= sid || generate_sid.public_id
|
108
|
+
data
|
109
|
+
end
|
110
|
+
|
111
|
+
def write_session(req, sid, session_data, options)
|
112
|
+
session_data["session_id"] = sid.public_id
|
113
|
+
SessionId.new(sid, session_data)
|
114
|
+
end
|
115
|
+
|
116
|
+
def set_cookie(request, session_id, cookie)
|
117
|
+
cookie_jar(request)[@key] = cookie
|
118
|
+
end
|
119
|
+
|
120
|
+
def get_cookie(req)
|
121
|
+
cookie_jar(req)[@key]
|
122
|
+
end
|
123
|
+
|
124
|
+
def cookie_jar(request)
|
125
|
+
request.cookie_jar.signed_or_encrypted
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
require "action_dispatch/middleware/session/abstract_store"
|
6
|
+
begin
|
7
|
+
require "rack/session/dalli"
|
8
|
+
rescue LoadError => e
|
9
|
+
warn "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install"
|
10
|
+
raise e
|
11
|
+
end
|
12
|
+
|
13
|
+
module ActionDispatch
|
14
|
+
module Session
|
15
|
+
# # Action Dispatch Session MemCacheStore
|
16
|
+
#
|
17
|
+
# A session store that uses MemCache to implement storage.
|
18
|
+
#
|
19
|
+
# #### Options
|
20
|
+
# * `expire_after` - The length of time a session will be stored before
|
21
|
+
# automatically expiring.
|
22
|
+
#
|
23
|
+
class MemCacheStore < Rack::Session::Dalli
|
24
|
+
include Compatibility
|
25
|
+
include StaleSessionCheck
|
26
|
+
include SessionObject
|
27
|
+
|
28
|
+
def initialize(app, options = {})
|
29
|
+
options[:expire_after] ||= options[:expires]
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
require "action_dispatch/middleware/exception_wrapper"
|
6
|
+
|
7
|
+
module ActionDispatch
|
8
|
+
# # Action Dispatch ShowExceptions
|
9
|
+
#
|
10
|
+
# This middleware rescues any exception returned by the application and calls an
|
11
|
+
# exceptions app that will wrap it in a format for the end user.
|
12
|
+
#
|
13
|
+
# The exceptions app should be passed as a parameter on initialization of
|
14
|
+
# `ShowExceptions`. Every time there is an exception, `ShowExceptions` will
|
15
|
+
# store the exception in `env["action_dispatch.exception"]`, rewrite the
|
16
|
+
# `PATH_INFO` to the exception status code, and call the Rack app.
|
17
|
+
#
|
18
|
+
# In Rails applications, the exceptions app can be configured with
|
19
|
+
# `config.exceptions_app`, which defaults to ActionDispatch::PublicExceptions.
|
20
|
+
#
|
21
|
+
# If the application returns a response with the `X-Cascade` header set to
|
22
|
+
# `"pass"`, this middleware will send an empty response as a result with the
|
23
|
+
# correct status code. If any exception happens inside the exceptions app, this
|
24
|
+
# middleware catches the exceptions and returns a failsafe response.
|
25
|
+
class ShowExceptions
|
26
|
+
def initialize(app, exceptions_app)
|
27
|
+
@app = app
|
28
|
+
@exceptions_app = exceptions_app
|
29
|
+
end
|
30
|
+
|
31
|
+
def call(env)
|
32
|
+
@app.call(env)
|
33
|
+
rescue Exception => exception
|
34
|
+
request = ActionDispatch::Request.new env
|
35
|
+
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
|
36
|
+
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
|
37
|
+
request.set_header "action_dispatch.exception", wrapper.unwrapped_exception
|
38
|
+
request.set_header "action_dispatch.report_exception", !wrapper.rescue_response?
|
39
|
+
|
40
|
+
if wrapper.show?(request)
|
41
|
+
render_exception(request.dup, wrapper)
|
42
|
+
else
|
43
|
+
raise exception
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def render_exception(request, wrapper)
|
49
|
+
status = wrapper.status_code
|
50
|
+
request.set_header "action_dispatch.original_path", request.path_info
|
51
|
+
request.set_header "action_dispatch.original_request_method", request.raw_request_method
|
52
|
+
fallback_to_html_format_if_invalid_mime_type(request)
|
53
|
+
request.path_info = "/#{status}"
|
54
|
+
request.request_method = "GET"
|
55
|
+
response = @exceptions_app.call(request.env)
|
56
|
+
response[1][Constants::X_CASCADE] == "pass" ? pass_response(status) : response
|
57
|
+
rescue Exception => failsafe_error
|
58
|
+
$stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
|
59
|
+
|
60
|
+
[500, { Rack::CONTENT_TYPE => "text/plain; charset=utf-8" },
|
61
|
+
["500 Internal Server Error\n" \
|
62
|
+
"If you are the administrator of this website, then please read this web " \
|
63
|
+
"application's log file and/or the web server's log file to find out what " \
|
64
|
+
"went wrong."]]
|
65
|
+
end
|
66
|
+
|
67
|
+
def fallback_to_html_format_if_invalid_mime_type(request)
|
68
|
+
# If the MIME type for the request is invalid then the @exceptions_app may not
|
69
|
+
# be able to handle it. To make it easier to handle, we switch to HTML.
|
70
|
+
begin
|
71
|
+
request.content_mime_type
|
72
|
+
rescue ActionDispatch::Http::MimeNegotiation::InvalidType
|
73
|
+
request.set_header "CONTENT_TYPE", "text/html"
|
74
|
+
end
|
75
|
+
|
76
|
+
begin
|
77
|
+
request.formats
|
78
|
+
rescue ActionDispatch::Http::MimeNegotiation::InvalidType
|
79
|
+
request.set_header "HTTP_ACCEPT", "text/html"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def pass_response(status)
|
84
|
+
[status, { Rack::CONTENT_TYPE => "text/html; charset=#{Response.default_charset}",
|
85
|
+
Rack::CONTENT_LENGTH => "0" }, []]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionDispatch
|
6
|
+
# # Action Dispatch SSL
|
7
|
+
#
|
8
|
+
# This middleware is added to the stack when `config.force_ssl = true`, and is
|
9
|
+
# passed the options set in `config.ssl_options`. It does three jobs to enforce
|
10
|
+
# secure HTTP requests:
|
11
|
+
#
|
12
|
+
# 1. **TLS redirect**: Permanently redirects `http://` requests to `https://`
|
13
|
+
# with the same URL host, path, etc. Enabled by default. Set
|
14
|
+
# `config.ssl_options` to modify the destination URL:
|
15
|
+
#
|
16
|
+
# config.ssl_options = { redirect: { host: "secure.widgets.com", port: 8080 }`
|
17
|
+
#
|
18
|
+
# Or set `redirect: false` to disable redirection.
|
19
|
+
#
|
20
|
+
# Requests can opt-out of redirection with `exclude`:
|
21
|
+
#
|
22
|
+
# config.ssl_options = { redirect: { exclude: -> request { /healthcheck/.match?(request.path) } } }
|
23
|
+
#
|
24
|
+
# Cookies will not be flagged as secure for excluded requests.
|
25
|
+
#
|
26
|
+
# When proxying through a load balancer that terminates SSL, the forwarded
|
27
|
+
# request will appear as though it's HTTP instead of HTTPS to the application.
|
28
|
+
# This makes redirects and cookie security target HTTP instead of HTTPS.
|
29
|
+
# To make the server assume that the proxy already terminated SSL, and
|
30
|
+
# that the request really is HTTPS, set `config.assume_ssl` to `true`:
|
31
|
+
#
|
32
|
+
# config.assume_ssl = true
|
33
|
+
#
|
34
|
+
# 2. **Secure cookies**: Sets the `secure` flag on cookies to tell browsers
|
35
|
+
# they must not be sent along with `http://` requests. Enabled by default.
|
36
|
+
# Set `config.ssl_options` with `secure_cookies: false` to disable this
|
37
|
+
# feature.
|
38
|
+
#
|
39
|
+
# 3. **HTTP Strict Transport Security (HSTS)**: Tells the browser to remember
|
40
|
+
# this site as TLS-only and automatically redirect non-TLS requests. Enabled
|
41
|
+
# by default. Configure `config.ssl_options` with `hsts: false` to disable.
|
42
|
+
#
|
43
|
+
# Set `config.ssl_options` with `hsts: { ... }` to configure HSTS:
|
44
|
+
#
|
45
|
+
# * `expires`: How long, in seconds, these settings will stick. The
|
46
|
+
# minimum required to qualify for browser preload lists is 1 year.
|
47
|
+
# Defaults to 2 years (recommended).
|
48
|
+
#
|
49
|
+
# * `subdomains`: Set to `true` to tell the browser to apply these
|
50
|
+
# settings to all subdomains. This protects your cookies from
|
51
|
+
# interception by a vulnerable site on a subdomain. Defaults to `true`.
|
52
|
+
#
|
53
|
+
# * `preload`: Advertise that this site may be included in browsers'
|
54
|
+
# preloaded HSTS lists. HSTS protects your site on every visit *except
|
55
|
+
# the first visit* since it hasn't seen your HSTS header yet. To close
|
56
|
+
# this gap, browser vendors include a baked-in list of HSTS-enabled
|
57
|
+
# sites. Go to https://hstspreload.org to submit your site for
|
58
|
+
# inclusion. Defaults to `false`.
|
59
|
+
#
|
60
|
+
#
|
61
|
+
# To turn off HSTS, omitting the header is not enough. Browsers will
|
62
|
+
# remember the original HSTS directive until it expires. Instead, use the
|
63
|
+
# header to tell browsers to expire HSTS immediately. Setting `hsts: false`
|
64
|
+
# is a shortcut for `hsts: { expires: 0 }`.
|
65
|
+
#
|
66
|
+
class SSL
|
67
|
+
# :stopdoc: Default to 2 years as recommended on hstspreload.org.
|
68
|
+
HSTS_EXPIRES_IN = 63072000
|
69
|
+
|
70
|
+
PERMANENT_REDIRECT_REQUEST_METHODS = %w[GET HEAD] # :nodoc:
|
71
|
+
|
72
|
+
def self.default_hsts_options
|
73
|
+
{ expires: HSTS_EXPIRES_IN, subdomains: true, preload: false }
|
74
|
+
end
|
75
|
+
|
76
|
+
def initialize(app, redirect: {}, hsts: {}, secure_cookies: true, ssl_default_redirect_status: nil)
|
77
|
+
@app = app
|
78
|
+
|
79
|
+
@redirect = redirect
|
80
|
+
|
81
|
+
@exclude = @redirect && @redirect[:exclude] || proc { !@redirect }
|
82
|
+
@secure_cookies = secure_cookies
|
83
|
+
|
84
|
+
@hsts_header = build_hsts_header(normalize_hsts_options(hsts))
|
85
|
+
@ssl_default_redirect_status = ssl_default_redirect_status
|
86
|
+
end
|
87
|
+
|
88
|
+
def call(env)
|
89
|
+
request = Request.new env
|
90
|
+
|
91
|
+
if request.ssl?
|
92
|
+
@app.call(env).tap do |status, headers, body|
|
93
|
+
set_hsts_header! headers
|
94
|
+
flag_cookies_as_secure! headers if @secure_cookies && !@exclude.call(request)
|
95
|
+
end
|
96
|
+
else
|
97
|
+
return redirect_to_https request unless @exclude.call(request)
|
98
|
+
@app.call(env)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
def set_hsts_header!(headers)
|
104
|
+
headers[Constants::STRICT_TRANSPORT_SECURITY] ||= @hsts_header
|
105
|
+
end
|
106
|
+
|
107
|
+
def normalize_hsts_options(options)
|
108
|
+
case options
|
109
|
+
# Explicitly disabling HSTS clears the existing setting from browsers by setting
|
110
|
+
# expiry to 0.
|
111
|
+
when false
|
112
|
+
self.class.default_hsts_options.merge(expires: 0)
|
113
|
+
# Default to enabled, with default options.
|
114
|
+
when nil, true
|
115
|
+
self.class.default_hsts_options
|
116
|
+
else
|
117
|
+
self.class.default_hsts_options.merge(options)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# https://tools.ietf.org/html/rfc6797#section-6.1
|
122
|
+
def build_hsts_header(hsts)
|
123
|
+
value = +"max-age=#{hsts[:expires].to_i}"
|
124
|
+
value << "; includeSubDomains" if hsts[:subdomains]
|
125
|
+
value << "; preload" if hsts[:preload]
|
126
|
+
value
|
127
|
+
end
|
128
|
+
|
129
|
+
def flag_cookies_as_secure!(headers)
|
130
|
+
cookies = headers[Rack::SET_COOKIE]
|
131
|
+
return unless cookies
|
132
|
+
|
133
|
+
if Gem::Version.new(Rack::RELEASE) < Gem::Version.new("3")
|
134
|
+
cookies = cookies.split("\n")
|
135
|
+
headers[Rack::SET_COOKIE] = cookies.map { |cookie|
|
136
|
+
if !/;\s*secure\s*(;|$)/i.match?(cookie)
|
137
|
+
"#{cookie}; secure"
|
138
|
+
else
|
139
|
+
cookie
|
140
|
+
end
|
141
|
+
}.join("\n")
|
142
|
+
else
|
143
|
+
headers[Rack::SET_COOKIE] = Array(cookies).map do |cookie|
|
144
|
+
if !/;\s*secure\s*(;|$)/i.match?(cookie)
|
145
|
+
"#{cookie}; secure"
|
146
|
+
else
|
147
|
+
cookie
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def redirect_to_https(request)
|
154
|
+
[ @redirect.fetch(:status, redirection_status(request)),
|
155
|
+
{ Rack::CONTENT_TYPE => "text/html; charset=utf-8",
|
156
|
+
Constants::LOCATION => https_location_for(request) },
|
157
|
+
(@redirect[:body] || []) ]
|
158
|
+
end
|
159
|
+
|
160
|
+
def redirection_status(request)
|
161
|
+
if PERMANENT_REDIRECT_REQUEST_METHODS.include?(request.raw_request_method)
|
162
|
+
301 # Issue a permanent redirect via a GET request.
|
163
|
+
elsif @ssl_default_redirect_status
|
164
|
+
@ssl_default_redirect_status
|
165
|
+
else
|
166
|
+
307 # Issue a fresh request redirect to preserve the HTTP method.
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def https_location_for(request)
|
171
|
+
host = @redirect[:host] || request.host
|
172
|
+
port = @redirect[:port] || request.port
|
173
|
+
|
174
|
+
location = +"https://#{host}"
|
175
|
+
location << ":#{port}" if port != 80 && port != 443
|
176
|
+
location << request.fullpath
|
177
|
+
location
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
require "active_support/inflector/methods"
|
6
|
+
require "active_support/dependencies"
|
7
|
+
|
8
|
+
module ActionDispatch
|
9
|
+
# # Action Dispatch MiddlewareStack
|
10
|
+
#
|
11
|
+
# Read more about [Rails middleware
|
12
|
+
# stack](https://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack)
|
13
|
+
# in the guides.
|
14
|
+
class MiddlewareStack
|
15
|
+
class Middleware
|
16
|
+
attr_reader :args, :block, :klass
|
17
|
+
|
18
|
+
def initialize(klass, args, block)
|
19
|
+
@klass = klass
|
20
|
+
@args = args
|
21
|
+
@block = block
|
22
|
+
end
|
23
|
+
|
24
|
+
def name; klass.name; end
|
25
|
+
|
26
|
+
def ==(middleware)
|
27
|
+
case middleware
|
28
|
+
when Middleware
|
29
|
+
klass == middleware.klass
|
30
|
+
when Module
|
31
|
+
klass == middleware
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def inspect
|
36
|
+
if klass.is_a?(Module)
|
37
|
+
klass.to_s
|
38
|
+
else
|
39
|
+
klass.class.to_s
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def build(app)
|
44
|
+
klass.new(app, *args, &block)
|
45
|
+
end
|
46
|
+
|
47
|
+
def build_instrumented(app)
|
48
|
+
InstrumentationProxy.new(build(app), inspect)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# This class is used to instrument the execution of a single middleware. It
|
53
|
+
# proxies the `call` method transparently and instruments the method call.
|
54
|
+
class InstrumentationProxy
|
55
|
+
EVENT_NAME = "process_middleware.action_dispatch"
|
56
|
+
|
57
|
+
def initialize(middleware, class_name)
|
58
|
+
@middleware = middleware
|
59
|
+
|
60
|
+
@payload = {
|
61
|
+
middleware: class_name,
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
def call(env)
|
66
|
+
ActiveSupport::Notifications.instrument(EVENT_NAME, @payload) do
|
67
|
+
@middleware.call(env)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
include Enumerable
|
73
|
+
|
74
|
+
attr_accessor :middlewares
|
75
|
+
|
76
|
+
def initialize(*args)
|
77
|
+
@middlewares = []
|
78
|
+
yield(self) if block_given?
|
79
|
+
end
|
80
|
+
|
81
|
+
def each(&block)
|
82
|
+
@middlewares.each(&block)
|
83
|
+
end
|
84
|
+
|
85
|
+
def size
|
86
|
+
middlewares.size
|
87
|
+
end
|
88
|
+
|
89
|
+
def last
|
90
|
+
middlewares.last
|
91
|
+
end
|
92
|
+
|
93
|
+
def [](i)
|
94
|
+
middlewares[i]
|
95
|
+
end
|
96
|
+
|
97
|
+
def unshift(klass, *args, &block)
|
98
|
+
middlewares.unshift(build_middleware(klass, args, block))
|
99
|
+
end
|
100
|
+
ruby2_keywords(:unshift)
|
101
|
+
|
102
|
+
def initialize_copy(other)
|
103
|
+
self.middlewares = other.middlewares.dup
|
104
|
+
end
|
105
|
+
|
106
|
+
def insert(index, klass, *args, &block)
|
107
|
+
index = assert_index(index, :before)
|
108
|
+
middlewares.insert(index, build_middleware(klass, args, block))
|
109
|
+
end
|
110
|
+
ruby2_keywords(:insert)
|
111
|
+
|
112
|
+
alias_method :insert_before, :insert
|
113
|
+
|
114
|
+
def insert_after(index, *args, &block)
|
115
|
+
index = assert_index(index, :after)
|
116
|
+
insert(index + 1, *args, &block)
|
117
|
+
end
|
118
|
+
ruby2_keywords(:insert_after)
|
119
|
+
|
120
|
+
def swap(target, *args, &block)
|
121
|
+
index = assert_index(target, :before)
|
122
|
+
insert(index, *args, &block)
|
123
|
+
middlewares.delete_at(index + 1)
|
124
|
+
end
|
125
|
+
ruby2_keywords(:swap)
|
126
|
+
|
127
|
+
# Deletes a middleware from the middleware stack.
|
128
|
+
#
|
129
|
+
# Returns the array of middlewares not including the deleted item, or returns
|
130
|
+
# nil if the target is not found.
|
131
|
+
def delete(target)
|
132
|
+
middlewares.reject! { |m| m.name == target.name }
|
133
|
+
end
|
134
|
+
|
135
|
+
# Deletes a middleware from the middleware stack.
|
136
|
+
#
|
137
|
+
# Returns the array of middlewares not including the deleted item, or raises
|
138
|
+
# `RuntimeError` if the target is not found.
|
139
|
+
def delete!(target)
|
140
|
+
delete(target) || (raise "No such middleware to remove: #{target.inspect}")
|
141
|
+
end
|
142
|
+
|
143
|
+
def move(target, source)
|
144
|
+
source_index = assert_index(source, :before)
|
145
|
+
source_middleware = middlewares.delete_at(source_index)
|
146
|
+
|
147
|
+
target_index = assert_index(target, :before)
|
148
|
+
middlewares.insert(target_index, source_middleware)
|
149
|
+
end
|
150
|
+
|
151
|
+
alias_method :move_before, :move
|
152
|
+
|
153
|
+
def move_after(target, source)
|
154
|
+
source_index = assert_index(source, :after)
|
155
|
+
source_middleware = middlewares.delete_at(source_index)
|
156
|
+
|
157
|
+
target_index = assert_index(target, :after)
|
158
|
+
middlewares.insert(target_index + 1, source_middleware)
|
159
|
+
end
|
160
|
+
|
161
|
+
def use(klass, *args, &block)
|
162
|
+
middlewares.push(build_middleware(klass, args, block))
|
163
|
+
end
|
164
|
+
ruby2_keywords(:use)
|
165
|
+
|
166
|
+
def build(app = nil, &block)
|
167
|
+
instrumenting = ActiveSupport::Notifications.notifier.listening?(InstrumentationProxy::EVENT_NAME)
|
168
|
+
middlewares.freeze.reverse.inject(app || block) do |a, e|
|
169
|
+
if instrumenting
|
170
|
+
e.build_instrumented(a)
|
171
|
+
else
|
172
|
+
e.build(a)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
private
|
178
|
+
def assert_index(index, where)
|
179
|
+
i = index.is_a?(Integer) ? index : index_of(index)
|
180
|
+
raise "No such middleware to insert #{where}: #{index.inspect}" unless i
|
181
|
+
i
|
182
|
+
end
|
183
|
+
|
184
|
+
def build_middleware(klass, args, block)
|
185
|
+
Middleware.new(klass, args, block)
|
186
|
+
end
|
187
|
+
|
188
|
+
def index_of(klass)
|
189
|
+
middlewares.index do |m|
|
190
|
+
m.name == klass.name
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|