diff options
Diffstat (limited to 'actionpack/test/dispatch/ssl_test.rb')
-rw-r--r-- | actionpack/test/dispatch/ssl_test.rb | 311 |
1 files changed, 153 insertions, 158 deletions
diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb index 7ced41bc2e..e29ffa750c 100644 --- a/actionpack/test/dispatch/ssl_test.rb +++ b/actionpack/test/dispatch/ssl_test.rb @@ -1,219 +1,214 @@ -require 'abstract_unit' +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.reverse_merge(hsts: { subdomains: true }) end +end - def app - @app ||= ActionDispatch::SSL.new(default_app) +class RedirectSSLTest < SSLTest + def assert_not_redirected(url, headers: {}, redirect: {}) + self.app = build_app ssl_options: { redirect: redirect } + get url, headers: headers + assert_response :ok end - attr_writer :app - def test_allows_https_url - get "https://example.org/path?key=value" - assert_response :success + def assert_redirected(redirect: {}, from: "http://a/b?c=d", to: from.sub("http", "https")) + redirect = { status: 301, body: [] }.merge(redirect) + + self.app = build_app ssl_options: { redirect: redirect } + + get from + assert_response redirect[:status] || 301 + assert_redirected_to to + assert_equal redirect[:body].join, @response.body end - def test_allows_https_proxy_header_url - get "http://example.org/", headers: { 'HTTP_X_FORWARDED_PROTO' => "https" } - assert_response :success + def assert_post_redirected(redirect: {}, from: "http://a/b?c=d", + to: from.sub("http", "https")) + + self.app = build_app ssl_options: { redirect: redirect } + + post from + assert_response redirect[:status] || 307 + assert_redirected_to to 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 "exclude can avoid redirect" do + excluding = { exclude: -> request { request.path =~ /healthcheck/ } } + + assert_not_redirected "http://example.org/healthcheck", redirect: excluding + assert_redirected from: "http://example.org/", redirect: excluding end - def test_hsts_header_by_default - get "https://example.org/" - assert_equal "max-age=31536000", - response.headers['Strict-Transport-Security'] + test "https is not redirected" do + assert_not_redirected "https://example.org" end - def test_no_hsts_with_insecure_connection - get "http://example.org/" - assert_not response.headers['Strict-Transport-Security'] + test "proxied https is not redirected" do + assert_not_redirected "http://example.org", headers: { "HTTP_X_FORWARDED_PROTO" => "https" } 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 "http is redirected to https" do + assert_redirected 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 "http POST is redirected to https with status 307" do + assert_post_redirected 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 with non-301 status" do + assert_redirected redirect: { status: 307 } 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 with custom body" do + assert_redirected redirect: { body: ["foo"] } 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 "redirect to specific host" do + assert_redirected redirect: { host: "ssl" }, to: "https://ssl/b?c=d" 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 "redirect to default port" do + assert_redirected redirect: { port: 443 } 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"]] - }) + test "redirect to non-default port" do + assert_redirected redirect: { port: 8443 }, to: "https://a:8443/b?c=d" + end - get "https://example.org/" - assert_equal ["problem=def; path=/; HttpOnly; secure"], - response.headers['Set-Cookie'].split("\n") + test "redirect to different host and non-default port" do + assert_redirected redirect: { host: "ssl", port: 8443 }, to: "https://ssl:8443/b?c=d" 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 "redirect to different host including port" do + assert_redirected redirect: { host: "ssl:443" }, to: "https://ssl:443/b?c=d" + end - get "https://example.org/" - assert_equal ["problem=def; path=/; HttpOnly; secure"], - response.headers['Set-Cookie'].split("\n") + test "no redirect with redirect set to false" do + assert_not_redirected "http://example.org", redirect: false end +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"]] - }) +class StrictTransportSecurityTest < SSLTest + EXPECTED = "max-age=15552000" + EXPECTED_WITH_SUBDOMAINS = "max-age=15552000; includeSubDomains" - get "https://example.org/" - assert_equal ["problem=def; path=/; secure; HttpOnly"], - response.headers['Set-Cookie'].split("\n") + def assert_hsts(expected, url: "https://example.org", hsts: { subdomains: true }, headers: {}) + self.app = build_app ssl_options: { hsts: hsts }, headers: headers + get url + assert_equal expected, response.headers["Strict-Transport-Security"] end + test "enabled by default" do + assert_hsts EXPECTED_WITH_SUBDOMAINS + 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 "not sent with http:// responses" do + assert_hsts nil, url: "http://example.org" + end - get "https://example.org/" - assert_equal ["problem=def; path=/;secure; HttpOnly"], - response.headers['Set-Cookie'].split("\n") + test "defers to app-provided header" do + assert_hsts "app-provided", headers: { "Strict-Transport-Security" => "app-provided" } 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 "hsts: true enables default settings" do + assert_hsts EXPECTED_WITH_SUBDOMAINS, hsts: true + end - get "https://example.org/" - assert_equal ["problem=def; path=/; secure;HttpOnly"], - response.headers['Set-Cookie'].split("\n") + test "hsts: false sets max-age to zero, clearing browser HSTS settings" do + assert_hsts "max-age=0; includeSubDomains", hsts: 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 ":expires sets max-age" do + assert_hsts "max-age=500; includeSubDomains", 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; includeSubDomains", hsts: { expires: 1.year } 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'] + test "include subdomains" do + assert_hsts "#{EXPECTED}; includeSubDomains", hsts: { subdomains: true } 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'] + test "exclude subdomains" do + assert_hsts EXPECTED, hsts: { subdomains: false } 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'] + test "opt in to browser preload lists" do + assert_hsts "#{EXPECTED_WITH_SUBDOMAINS}; preload", hsts: { preload: true } 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'] + test "opt out of browser preload lists" do + assert_hsts EXPECTED_WITH_SUBDOMAINS, hsts: { preload: false } end +end + +class SecureCookiesTest < SSLTest + DEFAULT = %(id=1; path=/\ntoken=abc; path=/; secure; HttpOnly) - 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 get(**options) + self.app = build_app(**options) + super "https://example.org" 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 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_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_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_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_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_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_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"] 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_keeps_original_headers_behavior + get headers: { "Connection" => %w[close] } + assert_equal "close", response.headers["Connection"] end end |