diff options
Diffstat (limited to 'railties/test/application/middleware')
-rw-r--r-- | railties/test/application/middleware/cache_test.rb | 41 | ||||
-rw-r--r-- | railties/test/application/middleware/cookies_test.rb | 168 | ||||
-rw-r--r-- | railties/test/application/middleware/exceptions_test.rb | 43 | ||||
-rw-r--r-- | railties/test/application/middleware/remote_ip_test.rb | 20 | ||||
-rw-r--r-- | railties/test/application/middleware/sendfile_test.rb | 17 | ||||
-rw-r--r-- | railties/test/application/middleware/session_test.rb | 299 | ||||
-rw-r--r-- | railties/test/application/middleware/static_test.rb | 45 |
7 files changed, 471 insertions, 162 deletions
diff --git a/railties/test/application/middleware/cache_test.rb b/railties/test/application/middleware/cache_test.rb index c951dabd6c..3768d8ce2d 100644 --- a/railties/test/application/middleware/cache_test.rb +++ b/railties/test/application/middleware/cache_test.rb @@ -1,4 +1,6 @@ -require 'isolation/abstract_unit' +# frozen_string_literal: true + +require "isolation/abstract_unit" module ApplicationTests class CacheTest < ActiveSupport::TestCase @@ -6,8 +8,7 @@ module ApplicationTests def setup build_app - boot_rails - require 'rack/test' + require "rack/test" extend Rack::Test::Methods end @@ -20,7 +21,7 @@ module ApplicationTests class ExpiresController < ApplicationController def expires_header expires_in 10, public: !params[:private] - render text: SecureRandom.hex(16) + render plain: SecureRandom.hex(16) end def expires_etag @@ -33,18 +34,18 @@ module ApplicationTests end def keeps_if_modified_since - render :text => request.headers['If-Modified-Since'] + render plain: request.headers['If-Modified-Since'] end private def render_conditionally(headers) if stale?(headers.merge(public: !params[:private])) - render text: SecureRandom.hex(16) + render plain: SecureRandom.hex(16) end end end RUBY - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get ':controller(/:action)' end @@ -55,7 +56,7 @@ module ApplicationTests simple_controller expected = "Wed, 30 May 1984 19:43:31 GMT" - get "/expires/keeps_if_modified_since", {}, "HTTP_IF_MODIFIED_SINCE" => expected + get "/expires/keeps_if_modified_since", {}, { "HTTP_IF_MODIFIED_SINCE" => expected } assert_equal 200, last_response.status assert_equal expected, last_response.body, "cache should have kept If-Modified-Since" @@ -66,12 +67,12 @@ module ApplicationTests app("development") get "/expires/expires_header" - assert_nil last_response.headers['X-Rack-Cache'] + assert_nil last_response.headers["X-Rack-Cache"] body = last_response.body get "/expires/expires_header" - assert_nil last_response.headers['X-Rack-Cache'] + assert_nil last_response.headers["X-Rack-Cache"] assert_not_equal body, last_response.body end @@ -118,12 +119,12 @@ module ApplicationTests assert_equal "miss, store", last_response.headers["X-Rack-Cache"] assert_equal "public", last_response.headers["Cache-Control"] - body = last_response.body etag = last_response.headers["ETag"] - get "/expires/expires_etag", {}, "If-None-Match" => etag + get "/expires/expires_etag", {}, { "HTTP_IF_NONE_MATCH" => etag } assert_equal "stale, valid, store", last_response.headers["X-Rack-Cache"] - assert_equal body, last_response.body + assert_equal 304, last_response.status + assert_equal "", last_response.body end def test_cache_works_with_etags_private @@ -138,8 +139,8 @@ module ApplicationTests body = last_response.body etag = last_response.headers["ETag"] - get "/expires/expires_etag", {private: true}, "If-None-Match" => etag - assert_equal "miss", last_response.headers["X-Rack-Cache"] + get "/expires/expires_etag", { private: true }, { "HTTP_IF_NONE_MATCH" => etag } + assert_equal "miss", last_response.headers["X-Rack-Cache"] assert_not_equal body, last_response.body end @@ -152,12 +153,12 @@ module ApplicationTests assert_equal "miss, store", last_response.headers["X-Rack-Cache"] assert_equal "public", last_response.headers["Cache-Control"] - body = last_response.body last = last_response.headers["Last-Modified"] - get "/expires/expires_last_modified", {}, "If-Modified-Since" => last + get "/expires/expires_last_modified", {}, { "HTTP_IF_MODIFIED_SINCE" => last } assert_equal "stale, valid, store", last_response.headers["X-Rack-Cache"] - assert_equal body, last_response.body + assert_equal 304, last_response.status + assert_equal "", last_response.body end def test_cache_works_with_last_modified_private @@ -172,8 +173,8 @@ module ApplicationTests body = last_response.body last = last_response.headers["Last-Modified"] - get "/expires/expires_last_modified", {private: true}, "If-Modified-Since" => last - assert_equal "miss", last_response.headers["X-Rack-Cache"] + get "/expires/expires_last_modified", { private: true }, { "HTTP_IF_MODIFIED_SINCE" => last } + assert_equal "miss", last_response.headers["X-Rack-Cache"] assert_not_equal body, last_response.body end end diff --git a/railties/test/application/middleware/cookies_test.rb b/railties/test/application/middleware/cookies_test.rb index bbb7627be9..fe48ef3f03 100644 --- a/railties/test/application/middleware/cookies_test.rb +++ b/railties/test/application/middleware/cookies_test.rb @@ -1,8 +1,12 @@ -require 'isolation/abstract_unit' +# frozen_string_literal: true + +require "isolation/abstract_unit" +require "rack/test" module ApplicationTests class CookiesTest < ActiveSupport::TestCase include ActiveSupport::Testing::Isolation + include Rack::Test::Methods def new_app File.expand_path("#{app_path}/../new_app") @@ -10,38 +14,180 @@ module ApplicationTests def setup build_app - boot_rails FileUtils.rm_rf("#{app_path}/config/environments") end + def app + Rails.application + end + def teardown teardown_app FileUtils.rm_rf(new_app) if File.directory?(new_app) end - test 'always_write_cookie is true by default in development' do - require 'rails' - Rails.env = 'development' + test "always_write_cookie is true by default in development" do + require "rails" + Rails.env = "development" require "#{app_path}/config/environment" assert_equal true, ActionDispatch::Cookies::CookieJar.always_write_cookie end - test 'always_write_cookie is false by default in production' do - require 'rails' - Rails.env = 'production' + test "always_write_cookie is false by default in production" do + require "rails" + Rails.env = "production" require "#{app_path}/config/environment" assert_equal false, ActionDispatch::Cookies::CookieJar.always_write_cookie end - test 'always_write_cookie can be overridden' do + test "always_write_cookie can be overridden" do add_to_config <<-RUBY config.action_dispatch.always_write_cookie = false RUBY - require 'rails' - Rails.env = 'development' + require "rails" + Rails.env = "development" require "#{app_path}/config/environment" assert_equal false, ActionDispatch::Cookies::CookieJar.always_write_cookie end + + test "signed cookies with SHA512 digest and rotated out SHA256 and SHA1 digests" do + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get ':controller(/:action)' + post ':controller(/:action)' + end + RUBY + + controller :foo, <<-RUBY + class FooController < ActionController::Base + protect_from_forgery with: :null_session + + def write_raw_cookie_sha1 + cookies[:signed_cookie] = TestVerifiers.sha1.generate("signed cookie") + head :ok + end + + def write_raw_cookie_sha256 + cookies[:signed_cookie] = TestVerifiers.sha256.generate("signed cookie") + head :ok + end + + def read_signed + render plain: cookies.signed[:signed_cookie].inspect + end + + def read_raw_cookie + render plain: cookies[:signed_cookie] + end + end + RUBY + + add_to_config <<-RUBY + sha1_secret = Rails.application.key_generator.generate_key("sha1") + sha256_secret = Rails.application.key_generator.generate_key("sha256") + + ::TestVerifiers = Class.new do + class_attribute :sha1, default: ActiveSupport::MessageVerifier.new(sha1_secret, digest: "SHA1") + class_attribute :sha256, default: ActiveSupport::MessageVerifier.new(sha256_secret, digest: "SHA256") + end + + config.action_dispatch.signed_cookie_digest = "SHA512" + config.action_dispatch.signed_cookie_salt = "sha512 salt" + + config.action_dispatch.cookies_rotations.tap do |cookies| + cookies.rotate :signed, sha1_secret, digest: "SHA1" + cookies.rotate :signed, sha256_secret, digest: "SHA256" + end + RUBY + + require "#{app_path}/config/environment" + + verifier_sha512 = ActiveSupport::MessageVerifier.new(app.key_generator.generate_key("sha512 salt"), digest: :SHA512) + + get "/foo/write_raw_cookie_sha1" + get "/foo/read_signed" + assert_equal "signed cookie".inspect, last_response.body + + get "/foo/read_raw_cookie" + assert_equal "signed cookie", verifier_sha512.verify(last_response.body, purpose: "cookie.signed_cookie") + + get "/foo/write_raw_cookie_sha256" + get "/foo/read_signed" + assert_equal "signed cookie".inspect, last_response.body + + get "/foo/read_raw_cookie" + assert_equal "signed cookie", verifier_sha512.verify(last_response.body, purpose: "cookie.signed_cookie") + end + + test "encrypted cookies rotating multiple encryption keys" do + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get ':controller(/:action)' + post ':controller(/:action)' + end + RUBY + + controller :foo, <<-RUBY + class FooController < ActionController::Base + protect_from_forgery with: :null_session + + def write_raw_cookie_one + cookies[:encrypted_cookie] = TestEncryptors.first_gcm.encrypt_and_sign("encrypted cookie") + head :ok + end + + def write_raw_cookie_two + cookies[:encrypted_cookie] = TestEncryptors.second_gcm.encrypt_and_sign("encrypted cookie") + head :ok + end + + def read_encrypted + render plain: cookies.encrypted[:encrypted_cookie].inspect + end + + def read_raw_cookie + render plain: cookies[:encrypted_cookie] + end + end + RUBY + + add_to_config <<-RUBY + first_secret = Rails.application.key_generator.generate_key("first", 32) + second_secret = Rails.application.key_generator.generate_key("second", 32) + + ::TestEncryptors = Class.new do + class_attribute :first_gcm, default: ActiveSupport::MessageEncryptor.new(first_secret, cipher: "aes-256-gcm") + class_attribute :second_gcm, default: ActiveSupport::MessageEncryptor.new(second_secret, cipher: "aes-256-gcm") + end + + config.action_dispatch.use_authenticated_cookie_encryption = true + config.action_dispatch.encrypted_cookie_cipher = "aes-256-gcm" + config.action_dispatch.authenticated_encrypted_cookie_salt = "salt" + + config.action_dispatch.cookies_rotations.tap do |cookies| + cookies.rotate :encrypted, first_secret + cookies.rotate :encrypted, second_secret + end + RUBY + + require "#{app_path}/config/environment" + + encryptor = ActiveSupport::MessageEncryptor.new(app.key_generator.generate_key("salt", 32), cipher: "aes-256-gcm") + + get "/foo/write_raw_cookie_one" + get "/foo/read_encrypted" + assert_equal "encrypted cookie".inspect, last_response.body + + get "/foo/read_raw_cookie" + assert_equal "encrypted cookie", encryptor.decrypt_and_verify(last_response.body, purpose: "cookie.encrypted_cookie") + + get "/foo/write_raw_cookie_two" + get "/foo/read_encrypted" + assert_equal "encrypted cookie".inspect, last_response.body + + get "/foo/read_raw_cookie" + assert_equal "encrypted cookie", encryptor.decrypt_and_verify(last_response.body, purpose: "cookie.encrypted_cookie") + end end end diff --git a/railties/test/application/middleware/exceptions_test.rb b/railties/test/application/middleware/exceptions_test.rb index 4906f9a1e8..2d659ade8d 100644 --- a/railties/test/application/middleware/exceptions_test.rb +++ b/railties/test/application/middleware/exceptions_test.rb @@ -1,5 +1,7 @@ -require 'isolation/abstract_unit' -require 'rack/test' +# frozen_string_literal: true + +require "isolation/abstract_unit" +require "rack/test" module ApplicationTests class MiddlewareExceptionsTest < ActiveSupport::TestCase @@ -8,7 +10,6 @@ module ApplicationTests def setup build_app - boot_rails end def teardown @@ -48,7 +49,7 @@ module ApplicationTests test "uses custom exceptions app" do add_to_config <<-RUBY config.exceptions_app = lambda do |env| - [404, { "Content-Type" => "text/plain" }, ["YOU FAILED BRO"]] + [404, { "Content-Type" => "text/plain" }, ["YOU FAILED"]] end RUBY @@ -56,7 +57,7 @@ module ApplicationTests get "/foo" assert_equal 404, last_response.status - assert_equal "YOU FAILED BRO", last_response.body + assert_equal "YOU FAILED", last_response.body end test "url generation error when action_dispatch.show_exceptions is set raises an exception" do @@ -67,10 +68,10 @@ module ApplicationTests end end RUBY - + app.config.action_dispatch.show_exceptions = true - get '/foo' + get "/foo" assert_equal 500, last_response.status end @@ -78,15 +79,15 @@ module ApplicationTests app.config.action_dispatch.show_exceptions = false assert_raise(ActionController::RoutingError) do - get '/foo' + get "/foo" end end test "unspecified route when action_dispatch.show_exceptions is set shows 404" do app.config.action_dispatch.show_exceptions = true - assert_nothing_raised(ActionController::RoutingError) do - get '/foo' + assert_nothing_raised do + get "/foo" assert_match "The page you were looking for doesn't exist.", last_response.body end end @@ -95,12 +96,26 @@ module ApplicationTests app.config.action_dispatch.show_exceptions = true app.config.consider_all_requests_local = true - assert_nothing_raised(ActionController::RoutingError) do - get '/foo' + assert_nothing_raised do + get "/foo" assert_match "No route matches", last_response.body end end + test "routing to a nonexistent controller when action_dispatch.show_exceptions and consider_all_requests_local are set shows diagnostics" do + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + resources :articles + end + RUBY + + app.config.action_dispatch.show_exceptions = true + app.config.consider_all_requests_local = true + + get "/articles" + assert_match "<title>Action Controller: Exception caught</title>", last_response.body + end + test "displays diagnostics message when exception raised in template that contains UTF-8" do controller :foo, <<-RUBY class FooController < ActionController::Base @@ -112,12 +127,12 @@ module ApplicationTests app.config.action_dispatch.show_exceptions = true app.config.consider_all_requests_local = true - app_file 'app/views/foo/index.html.erb', <<-ERB + app_file "app/views/foo/index.html.erb", <<-ERB <% raise 'boooom' %> ✓測試テスト시험 ERB - get '/foo', :utf8 => '✓' + get "/foo", utf8: "✓" assert_match(/boooom/, last_response.body) assert_match(/測試テスト시험/, last_response.body) end diff --git a/railties/test/application/middleware/remote_ip_test.rb b/railties/test/application/middleware/remote_ip_test.rb index 97d5b5c698..83cf8a27f7 100644 --- a/railties/test/application/middleware/remote_ip_test.rb +++ b/railties/test/application/middleware/remote_ip_test.rb @@ -1,6 +1,8 @@ -require 'ipaddr' -require 'isolation/abstract_unit' -require 'active_support/key_generator' +# frozen_string_literal: true + +require "ipaddr" +require "isolation/abstract_unit" +require "active_support/key_generator" module ApplicationTests class RemoteIpTest < ActiveSupport::TestCase @@ -9,8 +11,8 @@ module ApplicationTests def remote_ip(env = {}) remote_ip = nil env = Rack::MockRequest.env_for("/").merge(env).merge!( - 'action_dispatch.show_exceptions' => false, - 'action_dispatch.key_generator' => ActiveSupport::LegacyKeyGenerator.new('b3c631c314c0bbca50c1b2843150fe33') + "action_dispatch.show_exceptions" => false, + "action_dispatch.key_generator" => ActiveSupport::LegacyKeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33") ) endpoint = Proc.new do |e| @@ -36,10 +38,10 @@ module ApplicationTests test "works with both headers individually" do make_basic_app - assert_nothing_raised(ActionDispatch::RemoteIp::IpSpoofAttackError) do + assert_nothing_raised do assert_equal "1.1.1.1", remote_ip("HTTP_X_FORWARDED_FOR" => "1.1.1.1") end - assert_nothing_raised(ActionDispatch::RemoteIp::IpSpoofAttackError) do + assert_nothing_raised do assert_equal "1.1.1.2", remote_ip("HTTP_CLIENT_IP" => "1.1.1.2") end end @@ -49,7 +51,7 @@ module ApplicationTests app.config.action_dispatch.ip_spoofing_check = false end - assert_nothing_raised(ActionDispatch::RemoteIp::IpSpoofAttackError) do + assert_nothing_raised do assert_equal "1.1.1.1", remote_ip("HTTP_X_FORWARDED_FOR" => "1.1.1.1", "HTTP_CLIENT_IP" => "1.1.1.2") end end @@ -69,7 +71,7 @@ module ApplicationTests test "the user can set trusted proxies with an IPAddr argument" do make_basic_app do |app| - app.config.action_dispatch.trusted_proxies = IPAddr.new('4.2.42.0/24') + app.config.action_dispatch.trusted_proxies = IPAddr.new("4.2.42.0/24") end assert_equal "1.1.1.1", remote_ip("REMOTE_ADDR" => "1.1.1.1", "HTTP_X_FORWARDED_FOR" => "10.0.0.0,4.2.42.42") diff --git a/railties/test/application/middleware/sendfile_test.rb b/railties/test/application/middleware/sendfile_test.rb index dc96480d6d..818ad61c64 100644 --- a/railties/test/application/middleware/sendfile_test.rb +++ b/railties/test/application/middleware/sendfile_test.rb @@ -1,4 +1,6 @@ -require 'isolation/abstract_unit' +# frozen_string_literal: true + +require "isolation/abstract_unit" module ApplicationTests class SendfileTest < ActiveSupport::TestCase @@ -6,7 +8,6 @@ module ApplicationTests def setup build_app - boot_rails FileUtils.rm_rf "#{app_path}/config/environments" end @@ -14,10 +15,6 @@ module ApplicationTests teardown_app end - def app - @app ||= Rails.application - end - define_method :simple_controller do class ::OmgController < ActionController::Base def index @@ -32,7 +29,7 @@ module ApplicationTests simple_controller get "/" - assert !last_response.headers["X-Sendfile"] + assert_not last_response.headers["X-Sendfile"] assert_equal File.read(__FILE__), last_response.body end @@ -49,7 +46,7 @@ module ApplicationTests test "config.action_dispatch.x_sendfile_header is sent to Rack::Sendfile" do make_basic_app do |app| - app.config.action_dispatch.x_sendfile_header = 'X-Lighttpd-Send-File' + app.config.action_dispatch.x_sendfile_header = "X-Lighttpd-Send-File" end simple_controller @@ -60,8 +57,8 @@ module ApplicationTests test "files handled by ActionDispatch::Static are handled by Rack::Sendfile" do make_basic_app do |app| - app.config.action_dispatch.x_sendfile_header = 'X-Sendfile' - app.config.serve_static_files = true + app.config.action_dispatch.x_sendfile_header = "X-Sendfile" + app.config.public_file_server.enabled = true app.paths["public"] = File.join(rails_root, "public") end diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb index 25eadfc387..b25e56b625 100644 --- a/railties/test/application/middleware/session_test.rb +++ b/railties/test/application/middleware/session_test.rb @@ -1,5 +1,7 @@ -require 'isolation/abstract_unit' -require 'rack/test' +# frozen_string_literal: true + +require "isolation/abstract_unit" +require "rack/test" module ApplicationTests class MiddlewareSessionTest < ActiveSupport::TestCase @@ -8,7 +10,6 @@ module ApplicationTests def setup build_app - boot_rails FileUtils.rm_rf "#{app_path}/config/environments" end @@ -20,12 +21,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_not app.config.session_options[:secure] + end + test "session is not loaded if it's not used" do make_basic_app @@ -43,11 +51,11 @@ module ApplicationTests get "/" assert last_request.env["HTTP_COOKIE"] - assert !last_response.headers["Set-Cookie"] + assert_not last_response.headers["Set-Cookie"] end test "session is empty and isn't saved on unverified request when using :null_session protect method" do - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get ':controller(/:action)' post ':controller(/:action)' @@ -64,7 +72,7 @@ module ApplicationTests end def read_session - render text: session[:foo].inspect + render plain: session[:foo].inspect end end RUBY @@ -75,20 +83,20 @@ module ApplicationTests require "#{app_path}/config/environment" - get '/foo/write_session' - get '/foo/read_session' - assert_equal '1', last_response.body + get "/foo/write_session" + get "/foo/read_session" + assert_equal "1", last_response.body - post '/foo/read_session' # Read session using POST request without CSRF token - assert_equal 'nil', last_response.body # Stored value shouldn't be accessible + post "/foo/read_session" # Read session using POST request without CSRF token + assert_equal "nil", last_response.body # Stored value shouldn't be accessible - post '/foo/write_session' # Write session using POST request without CSRF token - get '/foo/read_session' # Session shouldn't be changed - assert_equal '1', last_response.body + post "/foo/write_session" # Write session using POST request without CSRF token + get "/foo/read_session" # Session shouldn't be changed + assert_equal "1", last_response.body end test "cookie jar is empty and isn't saved on unverified request when using :null_session protect method" do - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get ':controller(/:action)' post ':controller(/:action)' @@ -105,7 +113,7 @@ module ApplicationTests end def read_cookie - render text: cookies[:foo].inspect + render plain: cookies[:foo].inspect end end RUBY @@ -116,20 +124,20 @@ module ApplicationTests require "#{app_path}/config/environment" - get '/foo/write_cookie' - get '/foo/read_cookie' + get "/foo/write_cookie" + get "/foo/read_cookie" assert_equal '"1"', last_response.body - post '/foo/read_cookie' # Read cookie using POST request without CSRF token - assert_equal 'nil', last_response.body # Stored value shouldn't be accessible + post "/foo/read_cookie" # Read cookie using POST request without CSRF token + assert_equal "nil", last_response.body # Stored value shouldn't be accessible - post '/foo/write_cookie' # Write cookie using POST request without CSRF token - get '/foo/read_cookie' # Cookie shouldn't be changed + post "/foo/write_cookie" # Write cookie using POST request without CSRF token + get "/foo/read_cookie" # Cookie shouldn't be changed assert_equal '"1"', last_response.body end test "session using encrypted cookie store" do - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get ':controller(/:action)' end @@ -143,38 +151,43 @@ module ApplicationTests end def read_session - render text: session[:foo] + render plain: session[:foo] end def read_encrypted_cookie - render text: cookies.encrypted[:_myapp_session]['foo'] + render plain: cookies.encrypted[:_myapp_session]['foo'] end def read_raw_cookie - render text: cookies[:_myapp_session] + render plain: cookies[:_myapp_session] end end RUBY + add_to_config <<-RUBY + # Enable AEAD cookies + config.action_dispatch.use_authenticated_cookie_encryption = true + RUBY + require "#{app_path}/config/environment" - get '/foo/write_session' - get '/foo/read_session' - assert_equal '1', last_response.body + get "/foo/write_session" + get "/foo/read_session" + assert_equal "1", last_response.body - get '/foo/read_encrypted_cookie' - assert_equal '1', last_response.body + get "/foo/read_encrypted_cookie" + assert_equal "1", last_response.body - secret = app.key_generator.generate_key('encrypted cookie') - sign_secret = app.key_generator.generate_key('signed encrypted cookie') - encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret) + cipher = "aes-256-gcm" + secret = app.key_generator.generate_key("authenticated encrypted cookie") + encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher) - get '/foo/read_raw_cookie' - assert_equal 1, encryptor.decrypt_and_verify(last_response.body)['foo'] + get "/foo/read_raw_cookie" + assert_equal 1, encryptor.decrypt_and_verify(last_response.body, purpose: "cookie._myapp_session")["foo"] end test "session upgrading signature to encryption cookie store works the same way as encrypted cookie store" do - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get ':controller(/:action)' end @@ -188,42 +201,45 @@ module ApplicationTests end def read_session - render text: session[:foo] + render plain: session[:foo] end def read_encrypted_cookie - render text: cookies.encrypted[:_myapp_session]['foo'] + render plain: cookies.encrypted[:_myapp_session]['foo'] end def read_raw_cookie - render text: cookies[:_myapp_session] + render plain: cookies[:_myapp_session] end end RUBY add_to_config <<-RUBY secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4" + + # Enable AEAD cookies + config.action_dispatch.use_authenticated_cookie_encryption = true RUBY require "#{app_path}/config/environment" - get '/foo/write_session' - get '/foo/read_session' - assert_equal '1', last_response.body + get "/foo/write_session" + get "/foo/read_session" + assert_equal "1", last_response.body - get '/foo/read_encrypted_cookie' - assert_equal '1', last_response.body + get "/foo/read_encrypted_cookie" + assert_equal "1", last_response.body - secret = app.key_generator.generate_key('encrypted cookie') - sign_secret = app.key_generator.generate_key('signed encrypted cookie') - encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret) + cipher = "aes-256-gcm" + secret = app.key_generator.generate_key("authenticated encrypted cookie") + encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher) - get '/foo/read_raw_cookie' - assert_equal 1, encryptor.decrypt_and_verify(last_response.body)['foo'] + get "/foo/read_raw_cookie" + assert_equal 1, encryptor.decrypt_and_verify(last_response.body, purpose: "cookie._myapp_session")["foo"] end test "session upgrading signature to encryption cookie store upgrades session to encrypted mode" do - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get ':controller(/:action)' end @@ -243,46 +259,119 @@ module ApplicationTests end def read_session - render text: session[:foo] + render plain: session[:foo] end def read_encrypted_cookie - render text: cookies.encrypted[:_myapp_session]['foo'] + render plain: cookies.encrypted[:_myapp_session]['foo'] end def read_raw_cookie - render text: cookies[:_myapp_session] + render plain: cookies[:_myapp_session] end end RUBY add_to_config <<-RUBY secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4" + + # Enable AEAD cookies + config.action_dispatch.use_authenticated_cookie_encryption = true RUBY require "#{app_path}/config/environment" - get '/foo/write_raw_session' - get '/foo/read_session' - assert_equal '1', last_response.body + get "/foo/write_raw_session" + get "/foo/read_session" + assert_equal "1", last_response.body + + get "/foo/write_session" + get "/foo/read_session" + assert_equal "2", last_response.body + + get "/foo/read_encrypted_cookie" + assert_equal "2", last_response.body + + cipher = "aes-256-gcm" + secret = app.key_generator.generate_key("authenticated encrypted cookie") + encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher) + + get "/foo/read_raw_cookie" + assert_equal 2, encryptor.decrypt_and_verify(last_response.body, purpose: "cookie._myapp_session")["foo"] + end + + test "session upgrading from AES-CBC-HMAC encryption to AES-GCM encryption" do + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get ':controller(/:action)' + end + RUBY + + controller :foo, <<-RUBY + class FooController < ActionController::Base + def write_raw_session + # AES-256-CBC with SHA1 HMAC + # {"session_id"=>"1965d95720fffc123941bdfb7d2e6870", "foo"=>1} + cookies[:_myapp_session] = "TlgrdS85aUpDd1R2cDlPWlR6K0FJeGExckwySjZ2Z0pkR3d2QnRObGxZT25aalJWYWVvbFVLcHF4d0VQVDdSaFF2QjFPbG9MVjJzeWp3YjcyRUlKUUU2ZlR4bXlSNG9ZUkJPRUtld0E3dVU9LS0xNDZXbGpRZ3NjdW43N2haUEZJSUNRPT0=--3639b5ce54c09495cfeaae928cd5634e0c4b2e96" + head :ok + end + + def write_session + session[:foo] = session[:foo] + 1 + head :ok + end + + def read_session + render plain: session[:foo] + end + + def read_encrypted_cookie + render plain: cookies.encrypted[:_myapp_session]['foo'] + end + + def read_raw_cookie + render plain: cookies[:_myapp_session] + end + end + RUBY + + add_to_config <<-RUBY + # Use a static key + Rails.application.credentials.secret_key_base = "known key base" - get '/foo/write_session' - get '/foo/read_session' - assert_equal '2', last_response.body + # Enable AEAD cookies + config.action_dispatch.use_authenticated_cookie_encryption = true + RUBY + + begin + old_rails_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "production" + + require "#{app_path}/config/environment" + + get "/foo/write_raw_session" + get "/foo/read_session" + assert_equal "1", last_response.body + + get "/foo/write_session" + get "/foo/read_session" + assert_equal "2", last_response.body - get '/foo/read_encrypted_cookie' - assert_equal '2', last_response.body + get "/foo/read_encrypted_cookie" + assert_equal "2", last_response.body - secret = app.key_generator.generate_key('encrypted cookie') - sign_secret = app.key_generator.generate_key('signed encrypted cookie') - encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret) + cipher = "aes-256-gcm" + secret = app.key_generator.generate_key("authenticated encrypted cookie") + encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher) - get '/foo/read_raw_cookie' - assert_equal 2, encryptor.decrypt_and_verify(last_response.body)['foo'] + get "/foo/read_raw_cookie" + assert_equal 2, encryptor.decrypt_and_verify(last_response.body, purpose: "cookie._myapp_session")["foo"] + ensure + ENV["RAILS_ENV"] = old_rails_env + end end test "session upgrading legacy signed cookies to new signed cookies" do - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get ':controller(/:action)' end @@ -302,41 +391,81 @@ module ApplicationTests end def read_session - render text: session[:foo] + render plain: session[:foo] end def read_signed_cookie - render text: cookies.signed[:_myapp_session]['foo'] + render plain: cookies.signed[:_myapp_session]['foo'] end def read_raw_cookie - render text: cookies[:_myapp_session] + render plain: cookies[:_myapp_session] end end RUBY add_to_config <<-RUBY secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4" - secrets.secret_key_base = nil + Rails.application.credentials.secret_key_base = nil RUBY - require "#{app_path}/config/environment" + begin + old_rails_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "production" + + require "#{app_path}/config/environment" + + get "/foo/write_raw_session" + get "/foo/read_session" + assert_equal "1", last_response.body - get '/foo/write_raw_session' - get '/foo/read_session' - assert_equal '1', last_response.body + get "/foo/write_session" + get "/foo/read_session" + assert_equal "2", last_response.body - get '/foo/write_session' - get '/foo/read_session' - assert_equal '2', last_response.body + get "/foo/read_signed_cookie" + assert_equal "2", last_response.body - get '/foo/read_signed_cookie' - assert_equal '2', last_response.body + verifier = ActiveSupport::MessageVerifier.new(app.secrets.secret_token) - verifier = ActiveSupport::MessageVerifier.new(app.secrets.secret_token) + get "/foo/read_raw_cookie" + assert_equal 2, verifier.verify(last_response.body, purpose: "cookie._myapp_session")["foo"] + ensure + ENV["RAILS_ENV"] = old_rails_env + end + end + + test "calling reset_session on request does not trigger an error for API apps" do + add_to_config "config.api_only = true" + + controller :test, <<-RUBY + class TestController < ApplicationController + def dump_flash + request.reset_session + render plain: 'It worked!' + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/dump_flash' => "test#dump_flash" + end + RUBY + + require "#{app_path}/config/environment" + + get "/dump_flash" - get '/foo/read_raw_cookie' - assert_equal 2, verifier.verify(last_response.body)['foo'] + assert_equal 200, last_response.status + assert_equal "It worked!", last_response.body + + assert_not_includes Rails.application.middleware, ActionDispatch::Flash + end + + test "cookie_only is set to true even if user tries to overwrite it" do + add_to_config "config.session_store :cookie_store, key: '_myapp_session', cookie_only: false" + require "#{app_path}/config/environment" + assert app.config.session_options[:cookie_only], "Expected cookie_only to be set to true" end end end diff --git a/railties/test/application/middleware/static_test.rb b/railties/test/application/middleware/static_test.rb index 1a46cd3568..0977042cfe 100644 --- a/railties/test/application/middleware/static_test.rb +++ b/railties/test/application/middleware/static_test.rb @@ -1,5 +1,7 @@ -require 'isolation/abstract_unit' -require 'rack/test' +# frozen_string_literal: true + +require "isolation/abstract_unit" +require "rack/test" module ApplicationTests class MiddlewareStaticTest < ActiveSupport::TestCase @@ -18,32 +20,49 @@ module ApplicationTests # Regression test to #8907 # See https://github.com/rails/rails/commit/9cc82b77196d21a5c7021f6dca59ab9b2b158a45#commitcomment-2416514 test "doesn't set Cache-Control header when it is nil" do - app_file "public/foo.html", 'static' + app_file "public/foo.html", "static" + + require "#{app_path}/config/environment" + + get "foo" + + assert_not last_response.headers.has_key?("Cache-Control"), "Cache-Control should not be set" + end + + test "headers for static files are configurable" do + app_file "public/about.html", "static" + add_to_config <<-CONFIG + config.public_file_server.headers = { + "Access-Control-Allow-Origin" => "http://rubyonrails.org", + "Cache-Control" => "public, max-age=60" + } + CONFIG require "#{app_path}/config/environment" - get 'foo' + get "/about.html" - assert_not last_response.headers.has_key?('Cache-Control'), "Cache-Control should not be set" + assert_equal "http://rubyonrails.org", last_response.headers["Access-Control-Allow-Origin"] + assert_equal "public, max-age=60", last_response.headers["Cache-Control"] end - test "static_index defaults to 'index'" do + test "public_file_server.index_name defaults to 'index'" do app_file "public/index.html", "/index.html" - + require "#{app_path}/config/environment" - get '/' - + get "/" + assert_equal "/index.html\n", last_response.body end - test "static_index configurable" do + test "public_file_server.index_name configurable" do app_file "public/other-index.html", "/other-index.html" - add_to_config "config.static_index = 'other-index'" - + add_to_config "config.public_file_server.index_name = 'other-index'" + require "#{app_path}/config/environment" - get '/' + get "/" assert_equal "/other-index.html\n", last_response.body end |