aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_dispatch/middleware/ssl.rb
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_dispatch/middleware/ssl.rb')
-rw-r--r--actionpack/lib/action_dispatch/middleware/ssl.rb51
1 files changed, 29 insertions, 22 deletions
diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb
index 735b5939dd..557721c301 100644
--- a/actionpack/lib/action_dispatch/middleware/ssl.rb
+++ b/actionpack/lib/action_dispatch/middleware/ssl.rb
@@ -18,45 +18,44 @@ module ActionDispatch
# Enabled by default. Configure `config.ssl_options` with `hsts: false` to disable.
#
# Set `config.ssl_options` with `hsts: { … }` to configure HSTS:
- # * `expires`: How long, in seconds, these settings will stick. Defaults to
- # `180.days` (recommended). The minimum required to qualify for browser
- # preload lists is `18.weeks`.
+ # * `expires`: How long, in seconds, these settings will stick. The minimum
+ # required to qualify for browser preload lists is `18.weeks`. Defaults to
+ # `180.days` (recommended).
# * `subdomains`: Set to `true` to tell the browser to apply these settings
# to all subdomains. This protects your cookies from interception by a
- # vulnerable site on a subdomain. Defaults to `false`.
+ # vulnerable site on a subdomain. Defaults to `true`.
# * `preload`: Advertise that this site may be included in browsers'
# preloaded HSTS lists. HSTS protects your site on every visit *except the
# first visit* since it hasn't seen your HSTS header yet. To close this
# gap, browser vendors include a baked-in list of HSTS-enabled sites.
# Go to https://hstspreload.appspot.com to submit your site for inclusion.
+ # Defaults to `false`.
#
# To turn off HSTS, omitting the header is not enough. Browsers will remember the
# original HSTS directive until it expires. Instead, use the header to tell browsers to
# expire HSTS immediately. Setting `hsts: false` is a shortcut for
# `hsts: { expires: 0 }`.
+ #
+ # Requests can opt-out of redirection with `exclude`:
+ #
+ # config.ssl_options = { redirect: { exclude: -> request { request.path =~ /healthcheck/ } } }
class SSL
# Default to 180 days, the low end for https://www.ssllabs.com/ssltest/
# and greater than the 18-week requirement for browser preload lists.
HSTS_EXPIRES_IN = 15552000
def self.default_hsts_options
- { expires: HSTS_EXPIRES_IN, subdomains: false, preload: false }
+ { expires: HSTS_EXPIRES_IN, subdomains: true, preload: false }
end
- def initialize(app, redirect: {}, hsts: {}, secure_cookies: true, **options)
+ def initialize(app, redirect: {}, hsts: {}, secure_cookies: true)
@app = app
- if options[:host] || options[:port]
- ActiveSupport::Deprecation.warn <<-end_warning.strip_heredoc
- The `:host` and `:port` options are moving within `:redirect`:
- `config.ssl_options = { redirect: { host: …, port: … }}`.
- end_warning
- @redirect = options.slice(:host, :port)
- else
- @redirect = redirect
- end
+ @redirect = redirect
+ @exclude = @redirect && @redirect[:exclude] || proc { !@redirect }
@secure_cookies = secure_cookies
+
@hsts_header = build_hsts_header(normalize_hsts_options(hsts))
end
@@ -69,14 +68,14 @@ module ActionDispatch
flag_cookies_as_secure! headers if @secure_cookies
end
else
- return redirect_to_https request if @redirect
+ return redirect_to_https request unless @exclude.call(request)
@app.call(env)
end
end
private
def set_hsts_header!(headers)
- headers['Strict-Transport-Security'.freeze] ||= @hsts_header
+ headers["Strict-Transport-Security".freeze] ||= @hsts_header
end
def normalize_hsts_options(options)
@@ -102,10 +101,10 @@ module ActionDispatch
end
def flag_cookies_as_secure!(headers)
- if cookies = headers['Set-Cookie'.freeze]
+ if cookies = headers["Set-Cookie".freeze]
cookies = cookies.split("\n".freeze)
- headers['Set-Cookie'.freeze] = cookies.map { |cookie|
+ headers["Set-Cookie".freeze] = cookies.map { |cookie|
if cookie !~ /;\s*secure\s*(;|$)/i
"#{cookie}; secure"
else
@@ -116,12 +115,20 @@ module ActionDispatch
end
def redirect_to_https(request)
- [ @redirect.fetch(:status, 301),
- { 'Content-Type' => 'text/html',
- 'Location' => https_location_for(request) },
+ [ @redirect.fetch(:status, redirection_status(request)),
+ { "Content-Type" => "text/html",
+ "Location" => https_location_for(request) },
@redirect.fetch(:body, []) ]
end
+ def redirection_status(request)
+ if request.get? || request.head?
+ 301 # Issue a permanent redirect via a GET request.
+ else
+ 307 # Issue a fresh request redirect to preserve the HTTP method.
+ end
+ end
+
def https_location_for(request)
host = @redirect[:host] || request.host
port = @redirect[:port] || request.port