aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/test/dispatch/ssl_test.rb
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/test/dispatch/ssl_test.rb')
-rw-r--r--actionpack/test/dispatch/ssl_test.rb311
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