omg-actionpack 8.0.0.alpha1
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.
- 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
|