diff options
author | Jeremy Daer (Kemper) <jeremydaer@gmail.com> | 2015-09-07 18:08:54 -0700 |
---|---|---|
committer | Jeremy Daer (Kemper) <jeremydaer@gmail.com> | 2015-09-07 18:08:54 -0700 |
commit | a11571cec3213753d63ac3e6b4bb3b97fe2594a6 (patch) | |
tree | e3132cf05e6ad49c921bb9606fc88e95b2b752f4 /actionpack/test | |
parent | 81dd3872587159ad3514fe70593b208cb5f892ec (diff) | |
parent | f674922462a15b6498e915fc0669550258410c02 (diff) | |
download | rails-a11571cec3213753d63ac3e6b4bb3b97fe2594a6.tar.gz rails-a11571cec3213753d63ac3e6b4bb3b97fe2594a6.tar.bz2 rails-a11571cec3213753d63ac3e6b4bb3b97fe2594a6.zip |
Merge pull request #21520 from jeremy/friendlier-force-ssl
Make `config.force_ssl` less dangerous to try and easier to disable
Diffstat (limited to 'actionpack/test')
-rw-r--r-- | actionpack/test/dispatch/ssl_test.rb | 297 |
1 files changed, 133 insertions, 164 deletions
diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb index 017e9ba2dd..7a5b8393dc 100644 --- a/actionpack/test/dispatch/ssl_test.rb +++ b/actionpack/test/dispatch/ssl_test.rb @@ -1,230 +1,199 @@ require 'abstract_unit' class SSLTest < ActionDispatch::IntegrationTest - def default_app - lambda { |env| - headers = {'Content-Type' => "text/html"} - headers['Set-Cookie'] = "id=1; path=/\ntoken=abc; path=/; secure; HttpOnly" - [200, headers, ["OK"]] + HEADERS = Rack::Utils::HeaderHash.new 'Content-Type' => 'text/html' + + attr_accessor :app + + def build_app(headers: {}, ssl_options: {}) + headers = HEADERS.merge(headers) + ActionDispatch::SSL.new lambda { |env| [200, headers, []] }, ssl_options + end +end + +class RedirectSSLTest < SSLTest + def assert_not_redirected(url, headers: {}) + self.app = build_app + get url, headers: headers + assert_response :ok + end + + def assert_redirected(host: nil, port: nil, status: 301, body: [], + 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 }, + host: deprecated_host, port: deprecated_port } + + get from + assert_response status + assert_redirected_to to + assert_equal body.join, @response.body end - def app - @app ||= ActionDispatch::SSL.new(default_app) + test 'https is not redirected' do + assert_not_redirected 'https://example.org' end - attr_writer :app - def test_allows_https_url - get "https://example.org/path?key=value" - assert_response :success + test 'proxied https is not redirected' do + assert_not_redirected 'http://example.org', headers: { 'HTTP_X_FORWARDED_PROTO' => 'https' } end - def test_allows_https_proxy_header_url - get "http://example.org/", headers: { 'HTTP_X_FORWARDED_PROTO' => "https" } - assert_response :success + test 'http is redirected to https' do + assert_redirected end - def test_redirects_http_to_https - get "http://example.org/path?key=value" - assert_response :redirect - assert_equal "https://example.org/path?key=value", - response.headers['Location'] + test 'redirect with non-301 status' do + assert_redirected status: 307 end - def test_hsts_header_by_default - get "https://example.org/" - assert_equal "max-age=31536000", - response.headers['Strict-Transport-Security'] + test 'redirect with custom body' do + assert_redirected body: ['foo'] end - def test_no_hsts_with_insecure_connection - get "http://example.org/" - assert_not response.headers['Strict-Transport-Security'] + test 'redirect to specific host' do + assert_redirected host: 'ssl', to: 'https://ssl/b?c=d' end - def test_hsts_header - self.app = ActionDispatch::SSL.new(default_app, :hsts => true) - get "https://example.org/" - assert_equal "max-age=31536000", - response.headers['Strict-Transport-Security'] + test 'redirect to default port' do + assert_redirected port: 443 end - def test_disable_hsts_header - self.app = ActionDispatch::SSL.new(default_app, :hsts => false) - get "https://example.org/" - assert_not response.headers['Strict-Transport-Security'] + test 'redirect to non-default port' do + assert_redirected port: 8443, to: 'https://a:8443/b?c=d' end - def test_hsts_expires - self.app = ActionDispatch::SSL.new(default_app, :hsts => { :expires => 500 }) - get "https://example.org/" - assert_equal "max-age=500", - response.headers['Strict-Transport-Security'] + test 'redirect to different host and non-default port' do + assert_redirected host: 'ssl', port: 8443, to: 'https://ssl:8443/b?c=d' end - def test_hsts_expires_with_duration - self.app = ActionDispatch::SSL.new(default_app, :hsts => { :expires => 1.year }) - get "https://example.org/" - assert_equal "max-age=31557600", - response.headers['Strict-Transport-Security'] + test 'redirect to different host including port' do + assert_redirected host: 'ssl:443', to: 'https://ssl:443/b?c=d' end - def test_hsts_include_subdomains - self.app = ActionDispatch::SSL.new(default_app, :hsts => { :subdomains => true }) - get "https://example.org/" - assert_equal "max-age=31536000; includeSubDomains", - response.headers['Strict-Transport-Security'] + test ':host is deprecated, moved within redirect: { host: … }' do + assert_deprecated do + assert_redirected deprecated_host: 'foo', to: 'https://foo/b?c=d' + end end - def test_flag_cookies_as_secure - get "https://example.org/" - assert_equal ["id=1; path=/; secure", "token=abc; path=/; secure; HttpOnly" ], - response.headers['Set-Cookie'].split("\n") + test ':port is deprecated, moved within redirect: { port: … }' do + assert_deprecated do + assert_redirected deprecated_port: 1, to: 'https://a:1/b?c=d' + end end +end - def test_flag_cookies_as_secure_at_end_of_line - self.app = ActionDispatch::SSL.new(lambda { |env| - headers = { - 'Content-Type' => "text/html", - 'Set-Cookie' => "problem=def; path=/; HttpOnly; secure" - } - [200, headers, ["OK"]] - }) +class StrictTransportSecurityTest < SSLTest + EXPECTED = 'max-age=15552000' - get "https://example.org/" - assert_equal ["problem=def; path=/; HttpOnly; secure"], - response.headers['Set-Cookie'].split("\n") + def assert_hsts(expected, url: 'https://example.org', hsts: {}, headers: {}) + self.app = build_app ssl_options: { hsts: hsts }, headers: headers + get url + assert_equal expected, response.headers['Strict-Transport-Security'] end - def test_flag_cookies_as_secure_with_more_spaces_before - self.app = ActionDispatch::SSL.new(lambda { |env| - headers = { - 'Content-Type' => "text/html", - 'Set-Cookie' => "problem=def; path=/; HttpOnly; secure" - } - [200, headers, ["OK"]] - }) + test 'enabled by default' do + assert_hsts EXPECTED + end - get "https://example.org/" - assert_equal ["problem=def; path=/; HttpOnly; secure"], - response.headers['Set-Cookie'].split("\n") + test 'not sent with http:// responses' do + assert_hsts nil, url: 'http://example.org' end - def test_flag_cookies_as_secure_with_more_spaces_after - self.app = ActionDispatch::SSL.new(lambda { |env| - headers = { - 'Content-Type' => "text/html", - 'Set-Cookie' => "problem=def; path=/; secure; HttpOnly" - } - [200, headers, ["OK"]] - }) + test 'defers to app-provided header' do + assert_hsts 'app-provided', headers: { 'Strict-Transport-Security' => 'app-provided' } + end - get "https://example.org/" - assert_equal ["problem=def; path=/; secure; HttpOnly"], - response.headers['Set-Cookie'].split("\n") + test 'hsts: true enables default settings' do + assert_hsts EXPECTED, hsts: true end + test 'hsts: false sets max-age to zero, clearing browser HSTS settings' do + assert_hsts 'max-age=0', hsts: false + end - def test_flag_cookies_as_secure_with_has_not_spaces_before - self.app = ActionDispatch::SSL.new(lambda { |env| - headers = { - 'Content-Type' => "text/html", - 'Set-Cookie' => "problem=def; path=/;secure; HttpOnly" - } - [200, headers, ["OK"]] - }) + test ':expires sets max-age' do + assert_hsts 'max-age=500', hsts: { expires: 500 } + end - get "https://example.org/" - assert_equal ["problem=def; path=/;secure; HttpOnly"], - response.headers['Set-Cookie'].split("\n") + test ':expires supports AS::Duration arguments' do + assert_hsts 'max-age=31557600', hsts: { expires: 1.year } end - def test_flag_cookies_as_secure_with_has_not_spaces_after - self.app = ActionDispatch::SSL.new(lambda { |env| - headers = { - 'Content-Type' => "text/html", - 'Set-Cookie' => "problem=def; path=/; secure;HttpOnly" - } - [200, headers, ["OK"]] - }) + test 'include subdomains' do + assert_hsts "#{EXPECTED}; includeSubDomains", hsts: { subdomains: true } + end - get "https://example.org/" - assert_equal ["problem=def; path=/; secure;HttpOnly"], - response.headers['Set-Cookie'].split("\n") + test 'exclude subdomains' do + assert_hsts EXPECTED, hsts: { subdomains: false } end - def test_flag_cookies_as_secure_with_ignore_case - self.app = ActionDispatch::SSL.new(lambda { |env| - headers = { - 'Content-Type' => "text/html", - 'Set-Cookie' => "problem=def; path=/; Secure; HttpOnly" - } - [200, headers, ["OK"]] - }) + test 'opt in to browser preload lists' do + assert_hsts "#{EXPECTED}; preload", hsts: { preload: true } + end - get "https://example.org/" - assert_equal ["problem=def; path=/; Secure; HttpOnly"], - response.headers['Set-Cookie'].split("\n") + test 'opt out of browser preload lists' do + assert_hsts EXPECTED, hsts: { preload: false } end +end - def test_no_cookies - self.app = ActionDispatch::SSL.new(lambda { |env| - [200, {'Content-Type' => "text/html"}, ["OK"]] - }) - get "https://example.org/" - assert !response.headers['Set-Cookie'] +class SecureCookiesTest < SSLTest + DEFAULT = %(id=1; path=/\ntoken=abc; path=/; secure; HttpOnly) + + def get(**options) + self.app = build_app(**options) + super 'https://example.org' + end + + def assert_cookies(*expected) + assert_equal expected, response.headers['Set-Cookie'].split("\n") + end + + def test_flag_cookies_as_secure + get headers: { 'Set-Cookie' => DEFAULT } + assert_cookies 'id=1; path=/; secure', 'token=abc; path=/; secure; HttpOnly' end - def test_redirect_to_host - self.app = ActionDispatch::SSL.new(default_app, :host => "ssl.example.org") - get "http://example.org/path?key=value" - assert_equal "https://ssl.example.org/path?key=value", - response.headers['Location'] + def test_flag_cookies_as_secure_at_end_of_line + get headers: { 'Set-Cookie' => 'problem=def; path=/; HttpOnly; secure' } + assert_cookies 'problem=def; path=/; HttpOnly; secure' + end + + def test_flag_cookies_as_secure_with_more_spaces_before + get headers: { 'Set-Cookie' => 'problem=def; path=/; HttpOnly; secure' } + assert_cookies 'problem=def; path=/; HttpOnly; secure' end - def test_redirect_to_port - self.app = ActionDispatch::SSL.new(default_app, :port => 8443) - get "http://example.org/path?key=value" - assert_equal "https://example.org:8443/path?key=value", - response.headers['Location'] + def test_flag_cookies_as_secure_with_more_spaces_after + get headers: { 'Set-Cookie' => 'problem=def; path=/; secure; HttpOnly' } + assert_cookies 'problem=def; path=/; secure; HttpOnly' end - def test_redirect_to_host_and_port - self.app = ActionDispatch::SSL.new(default_app, :host => "ssl.example.org", :port => 8443) - get "http://example.org/path?key=value" - assert_equal "https://ssl.example.org:8443/path?key=value", - response.headers['Location'] + def test_flag_cookies_as_secure_with_has_not_spaces_before + get headers: { 'Set-Cookie' => 'problem=def; path=/;secure; HttpOnly' } + assert_cookies 'problem=def; path=/;secure; HttpOnly' end - def test_redirect_to_host_with_port - self.app = ActionDispatch::SSL.new(default_app, :host => "ssl.example.org:443") - get "http://example.org/path?key=value" - assert_equal "https://ssl.example.org:443/path?key=value", - response.headers['Location'] + def test_flag_cookies_as_secure_with_has_not_spaces_after + get headers: { 'Set-Cookie' => 'problem=def; path=/; secure;HttpOnly' } + assert_cookies 'problem=def; path=/; secure;HttpOnly' end - def test_redirect_to_secure_host_when_on_subdomain - self.app = ActionDispatch::SSL.new(default_app, :host => "ssl.example.org") - get "http://ssl.example.org/path?key=value" - assert_equal "https://ssl.example.org/path?key=value", - response.headers['Location'] + def test_flag_cookies_as_secure_with_ignore_case + get headers: { 'Set-Cookie' => 'problem=def; path=/; Secure; HttpOnly' } + assert_cookies 'problem=def; path=/; Secure; HttpOnly' end - def test_redirect_to_secure_subdomain_when_on_deep_subdomain - self.app = ActionDispatch::SSL.new(default_app, :host => "example.co.uk") - get "http://double.rainbow.what.does.it.mean.example.co.uk/path?key=value" - assert_equal "https://example.co.uk/path?key=value", - response.headers['Location'] + def test_no_cookies + get + assert_nil response.headers['Set-Cookie'] end def test_keeps_original_headers_behavior - headers = Rack::Utils::HeaderHash.new( - "Content-Type" => "text/html", - "Connection" => ["close"] - ) - self.app = ActionDispatch::SSL.new(lambda { |env| [200, headers, ["OK"]] }) - - get "https://example.org/" - assert_equal "close", response.headers["Connection"] + get headers: { 'Connection' => %w[close] } + assert_equal 'close', response.headers['Connection'] end end |