mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Merge pull request #22826 from timrogers/actiondispatch-ssl-config
Configurable redirect and secure cookies for ActionDispatch::SSL
This commit is contained in:
commit
1f85e1c9f3
4 changed files with 51 additions and 25 deletions
|
@ -4,16 +4,18 @@ module ActionDispatch
|
|||
# requests:
|
||||
#
|
||||
# 1. TLS redirect: Permanently redirects http:// requests to https://
|
||||
# with the same URL host, path, etc. This is always enabled. Set
|
||||
# `config.ssl_options` to modify the destination URL
|
||||
# (e.g. `redirect: { host: "secure.widgets.com", port: 8080 }`)
|
||||
# with the same URL host, path, etc. Enabled by default. Set `config.ssl_options`
|
||||
# to modify the destination URL
|
||||
# (e.g. `redirect: { host: "secure.widgets.com", port: 8080 }`), or set
|
||||
# `redirect: false` to disable this feature.
|
||||
#
|
||||
# 2. Secure cookies: Sets the `secure` flag on cookies to tell browsers they
|
||||
# mustn't be sent along with http:// requests. This is always enabled.
|
||||
# mustn't be sent along with http:// requests. Enabled by default. Set
|
||||
# `config.ssl_options` with `secure_cookies: false` to disable this feature.
|
||||
#
|
||||
# 3. HTTP Strict Transport Security (HSTS): Tells the browser to remember
|
||||
# this site as TLS-only and automatically redirect non-TLS requests.
|
||||
# Enabled by default. Pass `hsts: false` to disable.
|
||||
# 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
|
||||
|
@ -41,7 +43,7 @@ module ActionDispatch
|
|||
{ expires: HSTS_EXPIRES_IN, subdomains: false, preload: false }
|
||||
end
|
||||
|
||||
def initialize(app, redirect: {}, hsts: {}, **options)
|
||||
def initialize(app, redirect: {}, hsts: {}, secure_cookies: true, **options)
|
||||
@app = app
|
||||
|
||||
if options[:host] || options[:port]
|
||||
|
@ -54,6 +56,7 @@ module ActionDispatch
|
|||
@redirect = redirect
|
||||
end
|
||||
|
||||
@secure_cookies = secure_cookies
|
||||
@hsts_header = build_hsts_header(normalize_hsts_options(hsts))
|
||||
end
|
||||
|
||||
|
@ -63,10 +66,11 @@ module ActionDispatch
|
|||
if request.ssl?
|
||||
@app.call(env).tap do |status, headers, body|
|
||||
set_hsts_header! headers
|
||||
flag_cookies_as_secure! headers
|
||||
flag_cookies_as_secure! headers if @secure_cookies
|
||||
end
|
||||
else
|
||||
redirect_to_https request
|
||||
return redirect_to_https request if @redirect
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -12,25 +12,31 @@ class SSLTest < ActionDispatch::IntegrationTest
|
|||
end
|
||||
|
||||
class RedirectSSLTest < SSLTest
|
||||
def assert_not_redirected(url, headers: {})
|
||||
self.app = build_app
|
||||
|
||||
def assert_not_redirected(url, headers: {}, redirect: {}, deprecated_host: nil,
|
||||
deprecated_port: nil)
|
||||
|
||||
self.app = build_app ssl_options: { redirect: redirect,
|
||||
host: deprecated_host, port: deprecated_port
|
||||
}
|
||||
|
||||
get url, headers: headers
|
||||
assert_response :ok
|
||||
end
|
||||
|
||||
def assert_redirected(host: nil, port: nil, status: 301, body: [],
|
||||
deprecated_host: nil, deprecated_port: nil,
|
||||
def assert_redirected(redirect: {}, deprecated_host: nil, deprecated_port: nil,
|
||||
from: 'http://a/b?c=d', to: from.sub('http', 'https'))
|
||||
|
||||
self.app = build_app ssl_options: {
|
||||
redirect: { host: host, port: port, status: status, body: body },
|
||||
redirect = { status: 301, body: [] }.merge(redirect)
|
||||
|
||||
self.app = build_app ssl_options: { redirect: redirect,
|
||||
host: deprecated_host, port: deprecated_port
|
||||
}
|
||||
|
||||
get from
|
||||
assert_response status
|
||||
assert_response redirect[:status] || 301
|
||||
assert_redirected_to to
|
||||
assert_equal body.join, @response.body
|
||||
assert_equal redirect[:body].join, @response.body
|
||||
end
|
||||
|
||||
test 'https is not redirected' do
|
||||
|
@ -46,31 +52,31 @@ class RedirectSSLTest < SSLTest
|
|||
end
|
||||
|
||||
test 'redirect with non-301 status' do
|
||||
assert_redirected status: 307
|
||||
assert_redirected redirect: { status: 307 }
|
||||
end
|
||||
|
||||
test 'redirect with custom body' do
|
||||
assert_redirected body: ['foo']
|
||||
assert_redirected redirect: { body: ['foo'] }
|
||||
end
|
||||
|
||||
test 'redirect to specific host' do
|
||||
assert_redirected host: 'ssl', to: 'https://ssl/b?c=d'
|
||||
assert_redirected redirect: { host: 'ssl' }, to: 'https://ssl/b?c=d'
|
||||
end
|
||||
|
||||
test 'redirect to default port' do
|
||||
assert_redirected port: 443
|
||||
assert_redirected redirect: { port: 443 }
|
||||
end
|
||||
|
||||
test 'redirect to non-default port' do
|
||||
assert_redirected port: 8443, to: 'https://a:8443/b?c=d'
|
||||
assert_redirected redirect: { port: 8443 }, to: 'https://a:8443/b?c=d'
|
||||
end
|
||||
|
||||
test 'redirect to different host and non-default port' do
|
||||
assert_redirected host: 'ssl', port: 8443, to: 'https://ssl:8443/b?c=d'
|
||||
assert_redirected redirect: { host: 'ssl', port: 8443 }, to: 'https://ssl:8443/b?c=d'
|
||||
end
|
||||
|
||||
test 'redirect to different host including port' do
|
||||
assert_redirected host: 'ssl:443', to: 'https://ssl:443/b?c=d'
|
||||
assert_redirected redirect: { host: 'ssl:443' }, to: 'https://ssl:443/b?c=d'
|
||||
end
|
||||
|
||||
test ':host is deprecated, moved within redirect: { host: … }' do
|
||||
|
@ -84,6 +90,10 @@ class RedirectSSLTest < SSLTest
|
|||
assert_redirected deprecated_port: 1, to: 'https://a:1/b?c=d'
|
||||
end
|
||||
end
|
||||
|
||||
test 'no redirect with redirect set to false' do
|
||||
assert_not_redirected 'http://example.org', redirect: false
|
||||
end
|
||||
end
|
||||
|
||||
class StrictTransportSecurityTest < SSLTest
|
||||
|
@ -187,6 +197,11 @@ class SecureCookiesTest < SSLTest
|
|||
assert_cookies 'problem=def; path=/; Secure; HttpOnly'
|
||||
end
|
||||
|
||||
def test_cookies_as_not_secure_with_secure_cookies_disabled
|
||||
get headers: { 'Set-Cookie' => DEFAULT }, ssl_options: { secure_cookies: false }
|
||||
assert_cookies *DEFAULT.split("\n")
|
||||
end
|
||||
|
||||
def test_no_cookies
|
||||
get
|
||||
assert_nil response.headers['Set-Cookie']
|
||||
|
|
|
@ -68,7 +68,7 @@ module Rails
|
|||
middleware.use ::ActionDispatch::Cookies unless config.api_only
|
||||
|
||||
if !config.api_only && config.session_store
|
||||
if config.force_ssl && !config.session_options.key?(:secure)
|
||||
if config.force_ssl && config.ssl_options.fetch(:secure_cookies, true) && !config.session_options.key?(:secure)
|
||||
config.session_options[:secure] = true
|
||||
end
|
||||
middleware.use config.session_store, config.session_options
|
||||
|
|
|
@ -20,12 +20,19 @@ module ApplicationTests
|
|||
@app ||= Rails.application
|
||||
end
|
||||
|
||||
test "config.force_ssl sets cookie to secure only" do
|
||||
test "config.force_ssl sets cookie to secure only by default" do
|
||||
add_to_config "config.force_ssl = true"
|
||||
require "#{app_path}/config/environment"
|
||||
assert app.config.session_options[:secure], "Expected session to be marked as secure"
|
||||
end
|
||||
|
||||
test "config.force_ssl doesn't set cookie to secure only when changed from default" do
|
||||
add_to_config "config.force_ssl = true"
|
||||
add_to_config "config.ssl_options = { secure_cookies: false }"
|
||||
require "#{app_path}/config/environment"
|
||||
assert !app.config.session_options[:secure]
|
||||
end
|
||||
|
||||
test "session is not loaded if it's not used" do
|
||||
make_basic_app
|
||||
|
||||
|
|
Loading…
Reference in a new issue