diff options
Diffstat (limited to 'railties')
25 files changed, 212 insertions, 60 deletions
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 70c0f5c67b..0658c4b55c 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -57,7 +57,7 @@ *bogdanvlviv* -* Deprecate support of use `Rails::Application` subclass to start Rails server. +* Deprecate support for using a `Rails::Application` subclass to start Rails server. *Yuji Yaginuma* diff --git a/railties/MIT-LICENSE b/railties/MIT-LICENSE index f9e4444f07..cce00cbc3a 100644 --- a/railties/MIT-LICENSE +++ b/railties/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2017 David Heinemeier Hansson +Copyright (c) 2004-2018 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 293a736bfd..a200a1005c 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -472,7 +472,8 @@ module Rails ActiveSupport::EncryptedConfiguration.new( config_path: Rails.root.join(path), key_path: Rails.root.join(key_path), - env_key: env_key + env_key: env_key, + raise_if_missing_key: config.require_master_key ) end diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index cbc04f8a48..5d8d6740c8 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -16,7 +16,8 @@ module Rails :ssl_options, :public_file_server, :session_options, :time_zone, :reload_classes_only_on_change, :beginning_of_week, :filter_redirect, :x, :enable_dependency_loading, - :read_encrypted_secrets, :log_level, :content_security_policy_report_only + :read_encrypted_secrets, :log_level, :content_security_policy_report_only, + :require_master_key attr_reader :encoding, :api_only @@ -56,6 +57,7 @@ module Rails @read_encrypted_secrets = false @content_security_policy = nil @content_security_policy_report_only = false + @require_master_key = false end def load_defaults(target_version) diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb index 3d938be951..c4b188aeee 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -58,7 +58,7 @@ module Rails end # This needs to happen before eager load so it happens - # in exactly the same point regardless of config.cache_classes + # in exactly the same point regardless of config.eager_load initializer :run_prepare_callbacks do |app| app.reloader.prepare! end diff --git a/railties/lib/rails/commands/credentials/credentials_command.rb b/railties/lib/rails/commands/credentials/credentials_command.rb index 8085f07c2b..385d3976da 100644 --- a/railties/lib/rails/commands/credentials/credentials_command.rb +++ b/railties/lib/rails/commands/credentials/credentials_command.rb @@ -33,8 +33,7 @@ module Rails def show require_application_and_environment! - say Rails.application.credentials.read.presence || - "No credentials have been added yet. Use bin/rails credentials:edit to change that." + say Rails.application.credentials.read.presence || missing_credentials_message end private @@ -67,6 +66,14 @@ module Rails Rails::Generators::CredentialsGenerator.new end + + def missing_credentials_message + if Rails.application.credentials.key.nil? + "Missing master key to decrypt credentials. See bin/rails credentials:help" + else + "No credentials have been added yet. Use bin/rails credentials:edit to change that." + end + end end end end diff --git a/railties/lib/rails/commands/encrypted/encrypted_command.rb b/railties/lib/rails/commands/encrypted/encrypted_command.rb index 898094f1a4..912c453f09 100644 --- a/railties/lib/rails/commands/encrypted/encrypted_command.rb +++ b/railties/lib/rails/commands/encrypted/encrypted_command.rb @@ -37,9 +37,9 @@ module Rails def show(file_path) require_application_and_environment! + encrypted = Rails.application.encrypted(file_path, key_path: options[:key]) - say Rails.application.encrypted(file_path, key_path: options[:key]).read.presence || - "File '#{file_path}' does not exist. Use bin/rails encrypted:edit #{file_path} to change that." + say encrypted.read.presence || missing_encrypted_message(key: encrypted.key, key_path: options[:key], file_path: file_path) end private @@ -72,6 +72,14 @@ module Rails Rails::Generators::EncryptedFileGenerator.new end + + def missing_encrypted_message(key:, key_path:, file_path:) + if key.nil? + "Missing '#{key_path}' to decrypt data. See bin/rails encrypted:help" + else + "File '#{file_path}' does not exist. Use bin/rails encrypted:edit #{file_path} to change that." + end + end end end end diff --git a/railties/lib/rails/commands/server/server_command.rb b/railties/lib/rails/commands/server/server_command.rb index 703ec59087..e546fe3e4b 100644 --- a/railties/lib/rails/commands/server/server_command.rb +++ b/railties/lib/rails/commands/server/server_command.rb @@ -27,7 +27,7 @@ module Rails app = super if app.is_a?(Class) ActiveSupport::Deprecation.warn(<<-MSG.squish) - Use `Rails::Application` subclass to start the server is deprecated and will be removed in Rails 6.0. + Using `Rails::Application` subclass to start the server is deprecated and will be removed in Rails 6.0. Please change `run #{app}` to `run Rails.application` in config.ru. MSG end diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 59184020ae..e18aeda0d0 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -84,6 +84,9 @@ module Rails class_option :skip_system_test, type: :boolean, default: false, desc: "Skip system test files" + class_option :skip_bootsnap, type: :boolean, default: false, + desc: "Skip bootsnap gem" + class_option :dev, type: :boolean, default: false, desc: "Setup the #{name} with Gemfile pointing to your Rails checkout" @@ -435,6 +438,10 @@ module Rails !options[:skip_listen] && os_supports_listen_out_of_the_box? end + def depend_on_bootsnap? + !options[:skip_bootsnap] && !options[:dev] + end + def os_supports_listen_out_of_the_box? RbConfig::CONFIG["host_os"] =~ /darwin|linux/ end diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile.tt b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt index e3ed3e7c11..23bb89f4ce 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile.tt +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt @@ -29,9 +29,11 @@ ruby <%= "'#{RUBY_VERSION}'" -%> # Use Capistrano for deployment # gem 'capistrano-rails', group: :development +<% if depend_on_bootsnap? -%> # Reduces boot times through caching; required in config/boot.rb gem 'bootsnap', '>= 1.1.0', require: false +<%- end -%> <%- if options.api? -%> # Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible # gem 'rack-cors' diff --git a/railties/lib/rails/generators/rails/app/templates/config/boot.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/boot.rb.tt index 6246e7bf85..720d36a2a4 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/boot.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/boot.rb.tt @@ -1,7 +1,9 @@ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) require 'bundler/setup' # Set up gems listed in the Gemfile. +<% if depend_on_bootsnap? -%> require 'bootsnap/setup' # Speed up boot time by caching expensive operations. +<%- end -%> if %w[s server c console].any? { |a| ARGV.include?(a) } puts "=> Booting Rails" diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt index 961e167d24..a87649b50f 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt @@ -14,7 +14,7 @@ Rails.application.configure do # Enable/disable caching. By default caching is disabled. # Run rails dev:cache to toggle caching. - if Rails.root.join('tmp/caching-dev.txt').exist? + if Rails.root.join('tmp', 'caching-dev.txt').exist? config.action_controller.perform_caching = true config.cache_store = :memory_store diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt index f630d9985a..ae665b960a 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt @@ -10,7 +10,7 @@ # This is needed for recyclable cache keys. # Rails.application.config.active_record.cache_versioning = true -# Use AES 256 GCM authenticated encryption for encrypted cookies. +# Use AES-256-GCM authenticated encryption for encrypted cookies. # Existing cookies will be converted on read then written with the new scheme. # Rails.application.config.action_dispatch.use_authenticated_cookie_encryption = true @@ -25,6 +25,3 @@ # Store boolean values are in sqlite3 databases as 1 and 0 instead of 't' and # 'f' after migrating old data. # Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true - -# Highlight code that triggered database queries in logs. -Rails.application.config.active_record.verbose_query_logs = Rails.env.development? diff --git a/railties/lib/rails/generators/rails/credentials/credentials_generator.rb b/railties/lib/rails/generators/rails/credentials/credentials_generator.rb index 01a5b502f9..9103b1122e 100644 --- a/railties/lib/rails/generators/rails/credentials/credentials_generator.rb +++ b/railties/lib/rails/generators/rails/credentials/credentials_generator.rb @@ -36,7 +36,8 @@ module Rails ActiveSupport::EncryptedConfiguration.new( config_path: "config/credentials.yml.enc", key_path: "config/master.key", - env_key: "RAILS_MASTER_KEY" + env_key: "RAILS_MASTER_KEY", + raise_if_missing_key: true ) end diff --git a/railties/lib/rails/generators/rails/encrypted_file/encrypted_file_generator.rb b/railties/lib/rails/generators/rails/encrypted_file/encrypted_file_generator.rb index ddce5f6fe2..4ce2fc1d86 100644 --- a/railties/lib/rails/generators/rails/encrypted_file/encrypted_file_generator.rb +++ b/railties/lib/rails/generators/rails/encrypted_file/encrypted_file_generator.rb @@ -24,7 +24,7 @@ module Rails def add_encrypted_file_silently(file_path, key_path, template = encrypted_file_template) unless File.exist?(file_path) - setup = { content_path: file_path, key_path: key_path, env_key: "RAILS_MASTER_KEY" } + setup = { content_path: file_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true } ActiveSupport::EncryptedFile.new(setup).write(template) end end diff --git a/railties/lib/rails/railtie/configuration.rb b/railties/lib/rails/railtie/configuration.rb index 48853129bc..70274b948c 100644 --- a/railties/lib/rails/railtie/configuration.rb +++ b/railties/lib/rails/railtie/configuration.rb @@ -55,7 +55,7 @@ module Rails ActiveSupport.on_load(:before_configuration, yield: true, &block) end - # Third configurable block to run. Does not run if +config.cache_classes+ + # Third configurable block to run. Does not run if +config.eager_load+ # set to false. def before_eager_load(&block) ActiveSupport.on_load(:before_eager_load, yield: true, &block) diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index d28f7ffc7f..437b1ded72 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -707,6 +707,14 @@ module ApplicationTests assert_match(/Missing.*RAILS_MASTER_KEY/, error) end + test "credentials does not raise error when require_master_key is false and master key does not exist" do + remove_file "config/master.key" + add_to_config "config.require_master_key = false" + app "development" + + assert_not app.credentials.secret_key_base + end + test "protect from forgery is the default in a new app" do make_basic_app @@ -1731,9 +1739,7 @@ module ApplicationTests test "default SQLite3Adapter.represent_boolean_as_integer for 5.1 is false" do remove_from_config '.*config\.load_defaults.*\n' - add_to_top_of_config <<-RUBY - config.load_defaults 5.1 - RUBY + app_file "app/models/post.rb", <<-RUBY class Post < ActiveRecord::Base end @@ -1882,6 +1888,50 @@ module ApplicationTests assert_equal "https://example.org/", last_response.location end + test "ActiveSupport::MessageEncryptor.use_authenticated_message_encryption is true by default for new apps" do + app "development" + + assert_equal true, ActiveSupport::MessageEncryptor.use_authenticated_message_encryption + end + + test "ActiveSupport::MessageEncryptor.use_authenticated_message_encryption is false by default for upgraded apps" do + remove_from_config '.*config\.load_defaults.*\n' + + app "development" + + assert_equal false, ActiveSupport::MessageEncryptor.use_authenticated_message_encryption + end + + test "ActiveSupport::MessageEncryptor.use_authenticated_message_encryption can be configured via config.active_support.use_authenticated_message_encryption" do + remove_from_config '.*config\.load_defaults.*\n' + + app_file "config/initializers/new_framework_defaults_5_2.rb", <<-RUBY + Rails.application.config.active_support.use_authenticated_message_encryption = true + RUBY + + app "development" + + assert_equal true, ActiveSupport::MessageEncryptor.use_authenticated_message_encryption + end + + test "config.active_support.hash_digest_class is Digest::MD5 by default" do + app "development" + + assert_equal Digest::MD5, ActiveSupport::Digest.hash_digest_class + end + + test "config.active_support.hash_digest_class can be configured" do + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + config.active_support.hash_digest_class = Digest::SHA1 + end + RUBY + + app "development" + + assert_equal Digest::SHA1, ActiveSupport::Digest.hash_digest_class + end + private def force_lazy_load_hooks yield # Tasty clarifying sugar, homie! We only need to reference a constant to load it. diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index 470a5326c6..d59384e982 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -249,7 +249,7 @@ module ApplicationTests test "can't change middleware after it's built" do boot! - assert_raise RuntimeError do + assert_raise frozen_error_class do app.config.middleware.use Rack::Config end end diff --git a/railties/test/application/server_test.rb b/railties/test/application/server_test.rb index 2238f4f63a..f3a7e00a4d 100644 --- a/railties/test/application/server_test.rb +++ b/railties/test/application/server_test.rb @@ -29,12 +29,17 @@ module ApplicationTests server.app log = File.read(Rails.application.config.paths["log"].first) - assert_match(/DEPRECATION WARNING: Use `Rails::Application` subclass to start the server is deprecated/, log) + assert_match(/DEPRECATION WARNING: Using `Rails::Application` subclass to start the server is deprecated/, log) end test "restart rails server with custom pid file path" do skip "PTY unavailable" unless available_pty? + File.open("#{app_path}/config/boot.rb", "w") do |f| + f.puts "ENV['BUNDLE_GEMFILE'] = '#{Bundler.default_gemfile.to_s}'" + f.puts "require 'bundler/setup'" + end + master, slave = PTY.open pid = nil diff --git a/railties/test/commands/credentials_test.rb b/railties/test/commands/credentials_test.rb index f1bb1ef08a..7c464b3fde 100644 --- a/railties/test/commands/credentials_test.rb +++ b/railties/test/commands/credentials_test.rb @@ -26,10 +26,6 @@ class Rails::Command::CredentialsCommandTest < ActiveSupport::TestCase end end - test "show credentials" do - assert_match(/access_key_id: 123/, run_show_command) - end - test "edit command does not add master key to gitignore when already exist" do run_edit_command @@ -47,6 +43,24 @@ class Rails::Command::CredentialsCommandTest < ActiveSupport::TestCase assert_match(/api_key: abc/, run_show_command) end + test "show credentials" do + assert_match(/access_key_id: 123/, run_show_command) + end + + test "show command raise error when require_master_key is specified and key does not exist" do + remove_file "config/master.key" + add_to_config "config.require_master_key = true" + + assert_match(/Missing encryption key to decrypt file with/, run_show_command(allow_failure: true)) + end + + test "show command does not raise error when require_master_key is false and master key does not exist" do + remove_file "config/master.key" + add_to_config "config.require_master_key = false" + + assert_match(/Missing master key to decrypt credentials/, run_show_command) + end + private def run_edit_command(editor: "cat") switch_env("EDITOR", editor) do @@ -54,7 +68,7 @@ class Rails::Command::CredentialsCommandTest < ActiveSupport::TestCase end end - def run_show_command - rails "credentials:show" + def run_show_command(**options) + rails "credentials:show", **options end end diff --git a/railties/test/commands/encrypted_test.rb b/railties/test/commands/encrypted_test.rb index 0461493f2a..6647dcc902 100644 --- a/railties/test/commands/encrypted_test.rb +++ b/railties/test/commands/encrypted_test.rb @@ -52,6 +52,20 @@ class Rails::Command::EncryptedCommandTest < ActiveSupport::TestCase assert_match(/access_key_id: 123/, run_show_command("config/tokens.yml.enc", key: "config/tokens.key")) end + test "show command raise error when require_master_key is specified and key does not exist" do + add_to_config "config.require_master_key = true" + + assert_match(/Missing encryption key to decrypt file with/, + run_show_command("config/tokens.yml.enc", key: "unexist.key", allow_failure: true)) + end + + test "show command does not raise error when require_master_key is false and master key does not exist" do + remove_file "config/master.key" + add_to_config "config.require_master_key = false" + + assert_match(/Missing 'config\/master\.key' to decrypt data/, run_show_command("config/tokens.yml.enc")) + end + test "won't corrupt encrypted file when passed wrong key" do run_edit_command("config/tokens.yml.enc", key: "config/tokens.key") @@ -68,8 +82,8 @@ class Rails::Command::EncryptedCommandTest < ActiveSupport::TestCase end end - def run_show_command(file, key: nil) - rails "encrypted:show", prepare_args(file, key) + def run_show_command(file, key: nil, **options) + rails "encrypted:show", prepare_args(file, key), **options end def prepare_args(file, key) diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index f953f319af..a616624f46 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -105,6 +105,15 @@ class AppGeneratorTest < Rails::Generators::TestCase ::DEFAULT_APP_FILES end + def test_skip_bundle + assert_not_called(generator([destination_root], skip_bundle: true), :bundle_command) do + quietly { generator.invoke_all } + # skip_bundle is only about running bundle install, ensure the Gemfile is still + # generated. + assert_file "Gemfile" + end + end + def test_assets run_generator @@ -315,7 +324,9 @@ class AppGeneratorTest < Rails::Generators::TestCase end generator.stub :rails_command, command_check do - quietly { generator.invoke_all } + generator.stub :bundle_command, nil do + quietly { generator.invoke_all } + end end end @@ -753,7 +764,9 @@ class AppGeneratorTest < Rails::Generators::TestCase end generator([destination_root], webpack: "webpack").stub(:rails_command, command_check) do - quietly { generator.invoke_all } + generator.stub :bundle_command, nil do + quietly { generator.invoke_all } + end end assert_gem "webpacker" @@ -774,7 +787,9 @@ class AppGeneratorTest < Rails::Generators::TestCase end generator([destination_root], webpack: "react").stub(:rails_command, command_check) do - quietly { generator.invoke_all } + generator.stub :bundle_command, nil do + quietly { generator.invoke_all } + end end end @@ -792,6 +807,37 @@ class AppGeneratorTest < Rails::Generators::TestCase end end + def test_bootsnap + run_generator + + assert_gem "bootsnap" + assert_file "config/boot.rb" do |content| + assert_match(/require 'bootsnap\/setup'/, content) + end + end + + def test_skip_bootsnap + run_generator [destination_root, "--skip-bootsnap"] + + assert_file "Gemfile" do |content| + assert_no_match(/bootsnap/, content) + end + assert_file "config/boot.rb" do |content| + assert_no_match(/require 'bootsnap\/setup'/, content) + end + end + + def test_bootsnap_with_dev_option + run_generator [destination_root, "--dev"] + + assert_file "Gemfile" do |content| + assert_no_match(/bootsnap/, content) + end + assert_file "config/boot.rb" do |content| + assert_no_match(/require 'bootsnap\/setup'/, content) + end + end + def test_inclusion_of_ruby_version run_generator diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb index 6b746f3fed..29528825b8 100644 --- a/railties/test/generators/shared_generator_tests.rb +++ b/railties/test/generators/shared_generator_tests.rb @@ -92,7 +92,9 @@ module SharedGeneratorTests end generator([destination_root], template: path).stub(:open, check_open, template) do - quietly { assert_match(/It works!/, capture(:stdout) { generator.invoke_all }) } + generator.stub :bundle_command, nil do + quietly { assert_match(/It works!/, capture(:stdout) { generator.invoke_all }) } + end end end @@ -103,15 +105,6 @@ module SharedGeneratorTests end end - def test_skip_bundle - assert_not_called(generator([destination_root], skip_bundle: true), :bundle_command) do - quietly { generator.invoke_all } - # skip_bundle is only about running bundle install, ensure the Gemfile is still - # generated. - assert_file "Gemfile" - end - end - def test_skip_git run_generator [destination_root, "--skip-git", "--full"] assert_no_file(".gitignore") diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 96c6f21395..6568a356d6 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -82,22 +82,6 @@ module TestHelpers assert_match "charset=utf-8", resp[1]["Content-Type"] assert extract_body(resp).match(/Yay! You.*re on Rails!/) end - - def assert_success(resp) - assert_equal 202, resp[0] - end - - def assert_missing(resp) - assert_equal 404, resp[0] - end - - def assert_header(key, value, resp) - assert_equal value, resp[1][key.to_s] - end - - def assert_body(expected, resp) - assert_equal expected, extract_body(resp) - end end module Generation @@ -406,6 +390,10 @@ class ActiveSupport::TestCase include TestHelpers::Rack include TestHelpers::Generation include ActiveSupport::Testing::Stream + + def frozen_error_class + Object.const_defined?(:FrozenError) ? FrozenError : RuntimeError + end end # Create a scope and build a fixture rails app diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 4a861bff55..339a56c34f 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -1479,6 +1479,21 @@ YAML assert_equal "/fruits/2/bukkits/posts", last_response.body end + test "active_storage:install task works within engine" do + @plugin.write "Rakefile", <<-RUBY + APP_RAKEFILE = '#{app_path}/Rakefile' + load 'rails/tasks/engine.rake' + RUBY + + Dir.chdir(@plugin.path) do + output = `bundle exec rake app:active_storage:install` + assert $?.success?, output + + active_storage_migration = migrations.detect { |migration| migration.name == "CreateActiveStorageTables" } + assert active_storage_migration + end + end + private def app Rails.application |