diff options
Diffstat (limited to 'railties')
47 files changed, 810 insertions, 190 deletions
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index d086248278..70c0f5c67b 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,10 @@ +## Rails 5.2.0.beta2 (November 28, 2017) ## + +* No changes. + + +## Rails 5.2.0.beta1 (November 27, 2017) ## + * Deprecate `after_bundle` callback in Rails plugin templates. *Yuji Yaginuma* diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index df266fbfce..a200a1005c 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -174,8 +174,9 @@ module Rails # team. Details at https://github.com/rails/rails/pull/6952#issuecomment-7661220 @caching_key_generator ||= if secret_key_base - ActiveSupport::CachingKeyGenerator.new \ + ActiveSupport::CachingKeyGenerator.new( ActiveSupport::KeyGenerator.new(secret_key_base, iterations: 1000) + ) else ActiveSupport::LegacyKeyGenerator.new(secrets.secret_token) end @@ -265,7 +266,9 @@ module Rails "action_dispatch.signed_cookie_digest" => config.action_dispatch.signed_cookie_digest, "action_dispatch.cookies_serializer" => config.action_dispatch.cookies_serializer, "action_dispatch.cookies_digest" => config.action_dispatch.cookies_digest, - "action_dispatch.cookies_rotations" => config.action_dispatch.cookies_rotations + "action_dispatch.cookies_rotations" => config.action_dispatch.cookies_rotations, + "action_dispatch.content_security_policy" => config.content_security_policy, + "action_dispatch.content_security_policy_report_only" => config.content_security_policy_report_only ) end end @@ -400,8 +403,9 @@ module Rails secrets.secret_token ||= config.secret_token if secrets.secret_token.present? - ActiveSupport::Deprecation.warn \ + ActiveSupport::Deprecation.warn( "`secrets.secret_token` is deprecated in favor of `secret_key_base` and will be removed in Rails 6.0." + ) end secrets @@ -424,28 +428,29 @@ module Rails if Rails.env.test? || Rails.env.development? Digest::MD5.hexdigest self.class.name else - validate_secret_key_base \ + validate_secret_key_base( ENV["SECRET_KEY_BASE"] || credentials.secret_key_base || secrets.secret_key_base + ) end end - # Decrypts the credentials hash as kept in `config/credentials.yml.enc`. This file is encrypted with - # the Rails master key, which is either taken from ENV["RAILS_MASTER_KEY"] or from loading - # `config/master.key`. + # Decrypts the credentials hash as kept in +config/credentials.yml.enc+. This file is encrypted with + # the Rails master key, which is either taken from <tt>ENV["RAILS_MASTER_KEY"]</tt> or from loading + # +config/master.key+. def credentials @credentials ||= encrypted("config/credentials.yml.enc") end # Shorthand to decrypt any encrypted configurations or files. # - # For any file added with `bin/rails encrypted:edit` call `read` to decrypt + # For any file added with <tt>bin/rails encrypted:edit</tt> call +read+ to decrypt # the file with the master key. - # The master key is either stored in `config/master.key` or `ENV["RAILS_MASTER_KEY"]`. + # The master key is either stored in +config/master.key+ or <tt>ENV["RAILS_MASTER_KEY"]</tt>. # # Rails.application.encrypted("config/mystery_man.txt.enc").read # # => "We've met before, haven't we?" # - # It's also possible to interpret encrypted YAML files with `config`. + # It's also possible to interpret encrypted YAML files with +config+. # # Rails.application.encrypted("config/credentials.yml.enc").config # # => { next_guys_line: "I don't think so. Where was it you think we met?" } @@ -456,18 +461,20 @@ module Rails # # => "I don't think so. Where was it you think we met?" # # The files or configs can also be encrypted with a custom key. To decrypt with - # a key in the `ENV`, use: + # a key in the +ENV+, use: # # Rails.application.encrypted("config/special_tokens.yml.enc", env_key: "SPECIAL_TOKENS") # - # Or to decrypt with a file, that should be version control ignored, relative to `Rails.root`: + # Or to decrypt with a file, that should be version control ignored, relative to +Rails.root+: # # Rails.application.encrypted("config/special_tokens.yml.enc", key_path: "config/special_tokens.key") def encrypted(path, key_path: "config/master.key", env_key: "RAILS_MASTER_KEY") - ActiveSupport::EncryptedConfiguration.new \ + 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 def to_app #:nodoc: diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 290ec13878..5d8d6740c8 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -16,44 +16,48 @@ 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 + :read_encrypted_secrets, :log_level, :content_security_policy_report_only, + :require_master_key attr_reader :encoding, :api_only def initialize(*) super - self.encoding = Encoding::UTF_8 - @allow_concurrency = nil - @consider_all_requests_local = false - @filter_parameters = [] - @filter_redirect = [] - @helpers_paths = [] - @public_file_server = ActiveSupport::OrderedOptions.new - @public_file_server.enabled = true - @public_file_server.index_name = "index" - @force_ssl = false - @ssl_options = {} - @session_store = nil - @time_zone = "UTC" - @beginning_of_week = :monday - @log_level = :debug - @generators = app_generators - @cache_store = [ :file_store, "#{root}/tmp/cache/" ] - @railties_order = [:all] - @relative_url_root = ENV["RAILS_RELATIVE_URL_ROOT"] - @reload_classes_only_on_change = true - @file_watcher = ActiveSupport::FileUpdateChecker - @exceptions_app = nil - @autoflush_log = true - @log_formatter = ActiveSupport::Logger::SimpleFormatter.new - @eager_load = nil - @secret_token = nil - @secret_key_base = nil - @api_only = false - @debug_exception_response_format = nil - @x = Custom.new - @enable_dependency_loading = false - @read_encrypted_secrets = false + self.encoding = Encoding::UTF_8 + @allow_concurrency = nil + @consider_all_requests_local = false + @filter_parameters = [] + @filter_redirect = [] + @helpers_paths = [] + @public_file_server = ActiveSupport::OrderedOptions.new + @public_file_server.enabled = true + @public_file_server.index_name = "index" + @force_ssl = false + @ssl_options = {} + @session_store = nil + @time_zone = "UTC" + @beginning_of_week = :monday + @log_level = :debug + @generators = app_generators + @cache_store = [ :file_store, "#{root}/tmp/cache/" ] + @railties_order = [:all] + @relative_url_root = ENV["RAILS_RELATIVE_URL_ROOT"] + @reload_classes_only_on_change = true + @file_watcher = ActiveSupport::FileUpdateChecker + @exceptions_app = nil + @autoflush_log = true + @log_formatter = ActiveSupport::Logger::SimpleFormatter.new + @eager_load = nil + @secret_token = nil + @secret_key_base = nil + @api_only = false + @debug_exception_response_format = nil + @x = Custom.new + @enable_dependency_loading = false + @read_encrypted_secrets = false + @content_security_policy = nil + @content_security_policy_report_only = false + @require_master_key = false end def load_defaults(target_version) @@ -71,7 +75,6 @@ module Rails end self.ssl_options = { hsts: { subdomains: true } } - when "5.1" load_defaults "5.0" @@ -82,7 +85,6 @@ module Rails if respond_to?(:action_view) action_view.form_with_generates_remote_forms = true end - when "5.2" load_defaults "5.1" @@ -106,6 +108,9 @@ module Rails action_controller.default_protect_from_forgery = true end + if respond_to?(:action_view) + action_view.form_with_generates_ids = true + end else raise "Unknown version #{target_version.to_s.inspect}" end @@ -228,6 +233,10 @@ module Rails SourceAnnotationExtractor::Annotation end + def content_security_policy(&block) + @content_security_policy ||= ActionDispatch::ContentSecurityPolicy.new(&block) + end + class Custom #:nodoc: def initialize @configurations = Hash.new diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb index ea2273c1f2..0e79ba7da0 100644 --- a/railties/lib/rails/application/default_middleware_stack.rb +++ b/railties/lib/rails/application/default_middleware_stack.rb @@ -63,6 +63,10 @@ module Rails middleware.use ::ActionDispatch::Flash end + unless config.api_only + middleware.use ::ActionDispatch::ContentSecurityPolicy::Middleware + end + middleware.use ::Rack::Head middleware.use ::Rack::ConditionalGet middleware.use ::Rack::ETag, "no-cache" 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/command/helpers/editor.rb b/railties/lib/rails/command/helpers/editor.rb index 5e9ecc05e7..6191d97672 100644 --- a/railties/lib/rails/command/helpers/editor.rb +++ b/railties/lib/rails/command/helpers/editor.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/encrypted_file" module Rails 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/secrets/secrets_command.rb b/railties/lib/rails/commands/secrets/secrets_command.rb index 73a88767e2..a36ccf314c 100644 --- a/railties/lib/rails/commands/secrets/secrets_command.rb +++ b/railties/lib/rails/commands/secrets/secrets_command.rb @@ -56,7 +56,7 @@ module Rails private def deprecate_in_favor_of_credentials_and_exit say "Encrypted secrets is deprecated in favor of credentials. Run:" - say "bin/rails credentials --help" + say "bin/rails credentials:help" exit 1 end diff --git a/railties/lib/rails/gem_version.rb b/railties/lib/rails/gem_version.rb index 92b5e0392a..2cc861a1bd 100644 --- a/railties/lib/rails/gem_version.rb +++ b/railties/lib/rails/gem_version.rb @@ -10,7 +10,7 @@ module Rails MAJOR = 5 MINOR = 2 TINY = 0 - PRE = "alpha" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 5592e8d78e..6c9c109f17 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -274,8 +274,9 @@ module Rails else options = sorted_groups.flat_map(&:last) suggestions = options.sort_by { |suggested| levenshtein_distance(namespace.to_s, suggested) }.first(3) + suggestions.map! { |s| "'#{s}'" } msg = "Could not find generator '#{namespace}'. ".dup - msg << "Maybe you meant #{ suggestions.map { |s| "'#{s}'" }.to_sentence(last_word_connector: " or ", locale: :en) }\n" + msg << "Maybe you meant #{ suggestions[0...-1].join(', ')} or #{suggestions[-1]}\n" msg << "Run `rails generate --help` for more options." puts msg end diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 73256bec61..400f954dcd 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" @@ -192,7 +195,7 @@ module Rails def webserver_gemfile_entry # :doc: return [] if options[:skip_puma] comment = "Use Puma as the app server" - GemfileEntry.new("puma", "~> 3.7", comment) + GemfileEntry.new("puma", "~> 3.11", comment) end def include_all_railties? # :doc: @@ -297,7 +300,7 @@ module Rails def gem_for_database # %w( mysql postgresql sqlite3 oracle frontbase ibm_db sqlserver jdbcmysql jdbcsqlite3 jdbcpostgresql ) case options[:database] - when "mysql" then ["mysql2", [">= 0.3.18", "< 0.5"]] + when "mysql" then ["mysql2", ["~> 0.4.4"]] when "postgresql" then ["pg", ["~> 0.18"]] when "oracle" then ["activerecord-oracle_enhanced-adapter", nil] when "frontbase" then ["ruby-frontbase", nil] @@ -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/erb/scaffold/templates/_form.html.erb.tt b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb.tt index 0eb9d82bbb..518cb1121e 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb.tt +++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb.tt @@ -15,15 +15,15 @@ <div class="field"> <% if attribute.password_digest? -%> <%%= form.label :password %> - <%%= form.password_field :password, id: :<%= field_id(:password) %> %> + <%%= form.password_field :password %> </div> <div class="field"> <%%= form.label :password_confirmation %> - <%%= form.password_field :password_confirmation, id: :<%= field_id(:password_confirmation) %> %> + <%%= form.password_field :password_confirmation %> <% else -%> <%%= form.label :<%= attribute.column_name %> %> - <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %>, id: :<%= field_id(attribute.column_name) %> %> + <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %> %> <% end -%> </div> diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb index 99165168fd..98fcc95964 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -114,10 +114,6 @@ module Rails "new_#{singular_route_name}_url" end - def field_id(attribute_name) - [singular_table_name, attribute_name].join("_") - end - def singular_table_name # :doc: @singular_table_name ||= (pluralize_table_names? ? table_name.singularize : table_name) end diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 1fdfc3ca52..bf4570db90 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -128,6 +128,7 @@ module Rails active_storage_config_exist = File.exist?("config/storage.yml") rack_cors_config_exist = File.exist?("config/initializers/cors.rb") assets_config_exist = File.exist?("config/initializers/assets.rb") + csp_config_exist = File.exist?("config/initializers/content_security_policy.rb") config @@ -155,6 +156,10 @@ module Rails unless assets_config_exist remove_file "config/initializers/assets.rb" end + + unless csp_config_exist + remove_file "config/initializers/content_security_policy.rb" + end end end @@ -343,6 +348,14 @@ module Rails build(:public_directory) end + def create_tmp_files + build(:tmp) + end + + def create_vendor_files + build(:vendor) + end + def create_test_files build(:test) unless options[:skip_test] end @@ -355,14 +368,6 @@ module Rails build(:storage) unless skip_active_storage? end - def create_tmp_files - build(:tmp) - end - - def create_vendor_files - build(:vendor) - end - def delete_app_assets_if_api_option if options[:api] remove_dir "app/assets" @@ -432,6 +437,7 @@ module Rails def delete_non_api_initializers_if_api_option if options[:api] remove_file "config/initializers/cookies_serializer.rb" + remove_file "config/initializers/content_security_policy.rb" end end diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile.tt b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt index 61026f5182..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' @@ -41,13 +43,6 @@ gem 'bootsnap', '>= 1.1.0', require: false group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] - <%- if depends_on_system_test? -%> - # Adds support for Capybara system testing and selenium driver - gem 'capybara', '~> 2.15' - gem 'selenium-webdriver' - # Easy installation and use of chromedriver to run system tests with Chrome - gem 'chromedriver-helper' - <%- end -%> end group :development do @@ -70,6 +65,16 @@ group :development do <% end -%> <% end -%> end + +<%- if depends_on_system_test? -%> +group :test do + # Adds support for Capybara system testing and selenium driver + gem 'capybara', '~> 2.15' + gem 'selenium-webdriver' + # Easy installation and use of chromedriver to run system tests with Chrome + gem 'chromedriver-helper' +end +<%- end -%> <% end -%> # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 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 b9e460cef3..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,4 +1,10 @@ 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" +end 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 b383228dc0..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 @@ -46,6 +46,9 @@ Rails.application.configure do # Raise an error on page load if there are pending migrations. config.active_record.migration_error = :page_load + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + <%- end -%> <%- unless options.skip_sprockets? -%> # Debug mode disables concatenation and preprocessing of assets. diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/content_security_policy.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/content_security_policy.rb.tt new file mode 100644 index 0000000000..656ded4069 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/content_security_policy.rb.tt @@ -0,0 +1,20 @@ +# Define an application-wide content security policy +# For further information see the following documentation +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy + +Rails.application.config.content_security_policy do |p| + p.default_src :self, :https + p.font_src :self, :https, :data + p.img_src :self, :https, :data + p.object_src :none + p.script_src :self, :https + p.style_src :self, :https, :unsafe_inline + + # Specify URI for violation reports + # p.report_uri "/csp-violation-report-endpoint" +end + +# Report CSP violations to a specified URI +# For further information see the following documentation: +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only +# Rails.application.config.content_security_policy_report_only = true 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 25dcddb27a..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 diff --git a/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt index 1e19380dcb..a5eccf816b 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt @@ -26,31 +26,9 @@ environment ENV.fetch("RAILS_ENV") { "development" } # Use the `preload_app!` method when specifying a `workers` number. # This directive tells Puma to first boot the application and load code # before forking the application. This takes advantage of Copy On Write -# process behavior so workers use less memory. If you use this option -# you need to make sure to reconnect any threads in the `on_worker_boot` -# block. +# process behavior so workers use less memory. # # preload_app! -# If you are preloading your application and using Active Record, it's -# recommended that you close any connections to the database before workers -# are forked to prevent connection leakage. -# -# before_fork do -# ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord) -# end - -# The code in the `on_worker_boot` will be called if you are using -# clustered mode by specifying a number of `workers`. After each worker -# process is booted, this block will be run. If you are using the `preload_app!` -# option, you will want to use this block to reconnect to any threads -# or connections that may have been created at application boot, as Ruby -# cannot share connections between processes. -# -# on_worker_boot do -# ActiveRecord::Base.establish_connection if defined?(ActiveRecord) -# end -# - # Allow puma to be restarted by `rails restart` command. plugin :tmp_restart diff --git a/railties/lib/rails/generators/rails/app/templates/config/storage.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/storage.yml.tt index 9bada4b66d..1c0cde0b09 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/storage.yml.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/storage.yml.tt @@ -18,7 +18,7 @@ local: # google: # service: GCS # project: your_project -# keyfile: <%%= Rails.root.join("path/to/gcs.keyfile") %> +# credentials: <%%= Rails.root.join("path/to/gcs.keyfile") %> # bucket: your_own_bucket # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) diff --git a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt index 6ad1f11781..52d68cc77c 100644 --- a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt @@ -1,3 +1,4 @@ +ENV['RAILS_ENV'] ||= 'test' require_relative '../config/environment' require 'rails/test_help' diff --git a/railties/lib/rails/generators/rails/credentials/credentials_generator.rb b/railties/lib/rails/generators/rails/credentials/credentials_generator.rb index ab15da5423..9103b1122e 100644 --- a/railties/lib/rails/generators/rails/credentials/credentials_generator.rb +++ b/railties/lib/rails/generators/rails/credentials/credentials_generator.rb @@ -8,7 +8,7 @@ module Rails module Generators class CredentialsGenerator < Base def add_credentials_file - unless credentials.exist? + unless credentials.content_path.exist? template = credentials_template say "Adding #{credentials.content_path} to store encrypted credentials." @@ -26,15 +26,19 @@ module Rails end def add_credentials_file_silently(template = nil) - credentials.write(credentials_template) + unless credentials.content_path.exist? + credentials.write(credentials_template) + end end private def credentials - ActiveSupport::EncryptedConfiguration.new \ + 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 def credentials_template 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/generators/rails/plugin/templates/test/test_helper.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb.tt index 7fa9973931..755d19ef5d 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb.tt +++ b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb.tt @@ -1,3 +1,6 @@ +# Configure Rails Environment +ENV["RAILS_ENV"] = "test" + require_relative "<%= File.join('..', options[:dummy_path], 'config/environment') -%>" <% unless options[:skip_active_record] -%> ActiveRecord::Migrator.migrations_paths = [File.expand_path("../<%= options[:dummy_path] -%>/db/migrate", __dir__)] 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/lib/rails/tasks/engine.rake b/railties/lib/rails/tasks/engine.rake index 9db9d78ec4..8d77904210 100644 --- a/railties/lib/rails/tasks/engine.rake +++ b/railties/lib/rails/tasks/engine.rake @@ -53,7 +53,7 @@ namespace :db do desc "Rolls the schema back to the previous version (specify steps w/ STEP=n)." app_task "rollback" - desc "Create a db/schema.rb file that can be portably used against any DB supported by Active Record" + desc "Create a db/schema.rb file that can be portably used against any database supported by Active Record" app_task "schema:dump" desc "Load a schema.rb file into the database" @@ -62,7 +62,7 @@ namespace :db do desc "Load the seed data from db/seeds.rb" app_task "seed" - desc "Create the database, load the schema, and initialize with the seed data (use db:reset to also drop the db first)" + desc "Create the database, load the schema, and initialize with the seed data (use db:reset to also drop the database first)" app_task "setup" desc "Dump the database structure to an SQL file" diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb index 732c5c1e1f..76c28ac85e 100644 --- a/railties/lib/rails/test_help.rb +++ b/railties/lib/rails/test_help.rb @@ -29,10 +29,6 @@ if defined?(ActiveRecord::Base) end ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path - - def create_fixtures(*fixture_set_names, &block) - FixtureSet.create_fixtures(ActiveSupport::TestCase.fixture_path, fixture_set_names, {}, &block) - end end # :enddoc: diff --git a/railties/lib/rails/test_unit/runner.rb b/railties/lib/rails/test_unit/runner.rb index 5c2f6451e2..de5744c662 100644 --- a/railties/lib/rails/test_unit/runner.rb +++ b/railties/lib/rails/test_unit/runner.rb @@ -13,7 +13,7 @@ module Rails class << self def attach_before_load_options(opts) opts.on("--warnings", "-w", "Run with Ruby warnings enabled") {} - opts.on("--environment", "-e", "Run tests in the ENV environment") {} + opts.on("-e", "--environment ENV", "Run tests in the ENV environment") {} end def parse_options(argv) diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index bb8cc0876c..907eb4fa58 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 @@ -757,6 +765,68 @@ module ApplicationTests assert_match(/label/, last_response.body) end + test "form_with can be configured with form_with_generates_ids" do + app_file "config/initializers/form_builder.rb", <<-RUBY + Rails.configuration.action_view.form_with_generates_ids = false + RUBY + + app_file "app/models/post.rb", <<-RUBY + class Post + include ActiveModel::Model + attr_accessor :name + end + RUBY + + app_file "app/controllers/posts_controller.rb", <<-RUBY + class PostsController < ApplicationController + def index + render inline: "<%= begin; form_with(model: Post.new) {|f| f.text_field(:name)}; rescue => e; e.to_s; end %>" + end + end + RUBY + + add_to_config <<-RUBY + routes.prepend do + resources :posts + end + RUBY + + app "development" + + get "/posts" + + assert_no_match(/id=('|")post_name('|")/, last_response.body) + end + + test "form_with outputs ids by default" do + app_file "app/models/post.rb", <<-RUBY + class Post + include ActiveModel::Model + attr_accessor :name + end + RUBY + + app_file "app/controllers/posts_controller.rb", <<-RUBY + class PostsController < ApplicationController + def index + render inline: "<%= begin; form_with(model: Post.new) {|f| f.text_field(:name)}; rescue => e; e.to_s; end %>" + end + end + RUBY + + add_to_config <<-RUBY + routes.prepend do + resources :posts + end + RUBY + + app "development" + + get "/posts" + + assert_match(/id=('|")post_name('|")/, last_response.body) + end + test "form_with can be configured with form_with_generates_remote_forms" do app_file "config/initializers/form_builder.rb", <<-RUBY Rails.configuration.action_view.form_with_generates_remote_forms = false @@ -1255,7 +1325,7 @@ module ApplicationTests assert_equal 200, last_response.status end - test "config.action_controller.action_on_unpermitted_parameters is :log by default on development" do + test "config.action_controller.action_on_unpermitted_parameters is :log by default in development" do app "development" force_lazy_load_hooks { ActionController::Base } @@ -1264,7 +1334,7 @@ module ApplicationTests assert_equal :log, ActionController::Parameters.action_on_unpermitted_parameters end - test "config.action_controller.action_on_unpermitted_parameters is :log by default on test" do + test "config.action_controller.action_on_unpermitted_parameters is :log by default in test" do app "test" force_lazy_load_hooks { ActionController::Base } @@ -1273,7 +1343,7 @@ module ApplicationTests assert_equal :log, ActionController::Parameters.action_on_unpermitted_parameters end - test "config.action_controller.action_on_unpermitted_parameters is false by default on production" do + test "config.action_controller.action_on_unpermitted_parameters is false by default in production" do app "production" force_lazy_load_hooks { ActionController::Base } @@ -1420,12 +1490,18 @@ module ApplicationTests assert_not ActiveRecord::Base.dump_schema_after_migration end - test "config.active_record.dump_schema_after_migration is true by default on development" do + test "config.active_record.dump_schema_after_migration is true by default in development" do app "development" assert ActiveRecord::Base.dump_schema_after_migration end + test "config.active_record.verbose_query_logs is false by default in development" do + app "development" + + assert_not ActiveRecord::Base.verbose_query_logs + end + test "config.annotations wrapping SourceAnnotationExtractor::Annotation class" do make_basic_app do |application| application.config.annotations.register_extensions("coffee") do |tag| @@ -1814,6 +1890,24 @@ module ApplicationTests assert_equal "https://example.org/", last_response.location 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/content_security_policy_test.rb b/railties/test/application/content_security_policy_test.rb new file mode 100644 index 0000000000..97f2957c33 --- /dev/null +++ b/railties/test/application/content_security_policy_test.rb @@ -0,0 +1,197 @@ +# frozen_string_literal: true + +require "isolation/abstract_unit" +require "rack/test" + +module ApplicationTests + class ContentSecurityPolicyTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include Rack::Test::Methods + + def setup + build_app + end + + def teardown + teardown_app + end + + test "default content security policy is empty" do + controller :pages, <<-RUBY + class PagesController < ApplicationController + def index + render html: "<h1>Welcome to Rails!</h1>" + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + root to: "pages#index" + end + RUBY + + app("development") + + get "/" + assert_equal ";", last_response.headers["Content-Security-Policy"] + end + + test "global content security policy in an initializer" do + controller :pages, <<-RUBY + class PagesController < ApplicationController + def index + render html: "<h1>Welcome to Rails!</h1>" + end + end + RUBY + + app_file "config/initializers/content_security_policy.rb", <<-RUBY + Rails.application.config.content_security_policy do |p| + p.default_src :self, :https + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + root to: "pages#index" + end + RUBY + + app("development") + + get "/" + assert_policy "default-src 'self' https:;" + end + + test "global report only content security policy in an initializer" do + controller :pages, <<-RUBY + class PagesController < ApplicationController + def index + render html: "<h1>Welcome to Rails!</h1>" + end + end + RUBY + + app_file "config/initializers/content_security_policy.rb", <<-RUBY + Rails.application.config.content_security_policy do |p| + p.default_src :self, :https + end + + Rails.application.config.content_security_policy_report_only = true + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + root to: "pages#index" + end + RUBY + + app("development") + + get "/" + assert_policy "default-src 'self' https:;", report_only: true + end + + test "override content security policy in a controller" do + controller :pages, <<-RUBY + class PagesController < ApplicationController + content_security_policy do |p| + p.default_src "https://example.com" + end + + def index + render html: "<h1>Welcome to Rails!</h1>" + end + end + RUBY + + app_file "config/initializers/content_security_policy.rb", <<-RUBY + Rails.application.config.content_security_policy do |p| + p.default_src :self, :https + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + root to: "pages#index" + end + RUBY + + app("development") + + get "/" + assert_policy "default-src https://example.com;" + end + + test "override content security policy to report only in a controller" do + controller :pages, <<-RUBY + class PagesController < ApplicationController + content_security_policy_report_only + + def index + render html: "<h1>Welcome to Rails!</h1>" + end + end + RUBY + + app_file "config/initializers/content_security_policy.rb", <<-RUBY + Rails.application.config.content_security_policy do |p| + p.default_src :self, :https + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + root to: "pages#index" + end + RUBY + + app("development") + + get "/" + assert_policy "default-src 'self' https:;", report_only: true + end + + test "global content security policy added to rack app" do + app_file "config/initializers/content_security_policy.rb", <<-RUBY + Rails.application.config.content_security_policy do |p| + p.default_src :self, :https + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + + app = ->(env) { + [200, { "Content-Type" => "text/html" }, ["<p>Hello, World!</p>"]] + } + + root to: app + end + RUBY + + app("development") + + get "/" + assert_policy "default-src 'self' https:;" + end + + private + + def assert_policy(expected, report_only: false) + assert_equal 200, last_response.status + + if report_only + expected_header = "Content-Security-Policy-Report-Only" + unexpected_header = "Content-Security-Policy" + else + expected_header = "Content-Security-Policy" + unexpected_header = "Content-Security-Policy-Report-Only" + end + + assert_nil last_response.headers[unexpected_header] + assert_equal expected, last_response.headers[expected_header] + end + end +end diff --git a/railties/test/application/initializers/notifications_test.rb b/railties/test/application/initializers/notifications_test.rb index 23b20d578c..c65c955734 100644 --- a/railties/test/application/initializers/notifications_test.rb +++ b/railties/test/application/initializers/notifications_test.rb @@ -32,6 +32,7 @@ module ApplicationTests logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new ActiveRecord::Base.logger = logger + ActiveRecord::Base.verbose_query_logs = false # Mimic Active Record notifications instrument "sql.active_record", name: "SQL", sql: "SHOW tables" diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index 0a5a524692..d59384e982 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -42,6 +42,7 @@ module ApplicationTests "ActionDispatch::Cookies", "ActionDispatch::Session::CookieStore", "ActionDispatch::Flash", + "ActionDispatch::ContentSecurityPolicy::Middleware", "Rack::Head", "Rack::ConditionalGet", "Rack::ETag" @@ -248,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/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb index 0235210fdd..5b4c42c189 100644 --- a/railties/test/application/rake/dbs_test.rb +++ b/railties/test/application/rake/dbs_test.rb @@ -98,6 +98,20 @@ module ApplicationTests end end + test "db:create works when schema cache exists and database does not exist" do + use_postgresql + + begin + rails %w(db:create db:migrate db:schema:cache:dump) + + rails "db:drop" + rails "db:create" + assert_equal 0, $?.exitstatus + ensure + rails "db:drop" rescue nil + end + end + test "db:drop failure because database does not exist" do output = rails("db:drop:_unsafe", "--trace") assert_match(/does not exist/, output) @@ -189,7 +203,7 @@ module ApplicationTests rails "environment", "db:drop", "db:structure:load" assert_match expected_database, ActiveRecord::Base.connection_config[:database] require "#{app_path}/app/models/book" - #if structure is not loaded correctly, exception would be raised + # if structure is not loaded correctly, exception would be raised assert_equal 0, Book.count end end @@ -284,7 +298,7 @@ module ApplicationTests ActiveRecord::Base.configurations = Rails.application.config.database_configuration ActiveRecord::Base.establish_connection :test require "#{app_path}/app/models/book" - #if structure is not loaded correctly, exception would be raised + # if structure is not loaded correctly, exception would be raised assert_equal 0, Book.count assert_match ActiveRecord::Base.configurations["test"]["database"], ActiveRecord::Base.connection_config[:database] diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index bf89098645..5a6404bd0a 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -132,8 +132,14 @@ module ApplicationTests output = rails("routes") assert_equal <<-MESSAGE.strip_heredoc, output - Prefix Verb URI Pattern Controller#Action - cart GET /cart(.:format) cart#show + Prefix Verb URI Pattern Controller#Action + cart GET /cart(.:format) cart#show + rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show + rails_blob_variation GET /rails/active_storage/variants/:signed_blob_id/:variation_key/*filename(.:format) active_storage/variants#show + rails_blob_preview GET /rails/active_storage/previews/:signed_blob_id/:variation_key/*filename(.:format) active_storage/previews#show + rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show + update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update + rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create MESSAGE end @@ -168,14 +174,19 @@ module ApplicationTests output = rails("routes", "-g", "show") assert_equal <<-MESSAGE.strip_heredoc, output - Prefix Verb URI Pattern Controller#Action - cart GET /cart(.:format) cart#show + Prefix Verb URI Pattern Controller#Action + cart GET /cart(.:format) cart#show + rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show + rails_blob_variation GET /rails/active_storage/variants/:signed_blob_id/:variation_key/*filename(.:format) active_storage/variants#show + rails_blob_preview GET /rails/active_storage/previews/:signed_blob_id/:variation_key/*filename(.:format) active_storage/previews#show + rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show MESSAGE output = rails("routes", "-g", "POST") assert_equal <<-MESSAGE.strip_heredoc, output - Prefix Verb URI Pattern Controller#Action - POST /cart(.:format) cart#create + Prefix Verb URI Pattern Controller#Action + POST /cart(.:format) cart#create + rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create MESSAGE output = rails("routes", "-g", "basketballs") @@ -232,11 +243,13 @@ module ApplicationTests RUBY assert_equal <<-MESSAGE.strip_heredoc, rails("routes") - You don't have any routes defined! - - Please add some routes in config/routes.rb. - - For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html. + Prefix Verb URI Pattern Controller#Action + rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show + rails_blob_variation GET /rails/active_storage/variants/:signed_blob_id/:variation_key/*filename(.:format) active_storage/variants#show + rails_blob_preview GET /rails/active_storage/previews/:signed_blob_id/:variation_key/*filename(.:format) active_storage/previews#show + rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show + update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update + rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create MESSAGE end @@ -250,8 +263,14 @@ module ApplicationTests output = Dir.chdir(app_path) { `bin/rake --rakefile Rakefile routes` } assert_equal <<-MESSAGE.strip_heredoc, output - Prefix Verb URI Pattern Controller#Action - cart GET /cart(.:format) cart#show + Prefix Verb URI Pattern Controller#Action + cart GET /cart(.:format) cart#show + rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show + rails_blob_variation GET /rails/active_storage/variants/:signed_blob_id/:variation_key/*filename(.:format) active_storage/variants#show + rails_blob_preview GET /rails/active_storage/previews/:signed_blob_id/:variation_key/*filename(.:format) active_storage/previews#show + rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show + update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update + rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create MESSAGE end diff --git a/railties/test/application/server_test.rb b/railties/test/application/server_test.rb index 2238f4f63a..a6093b5733 100644 --- a/railties/test/application/server_test.rb +++ b/railties/test/application/server_test.rb @@ -35,6 +35,11 @@ module ApplicationTests 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/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb index e92a0466dd..a01325fdb8 100644 --- a/railties/test/application/test_runner_test.rb +++ b/railties/test/application/test_runner_test.rb @@ -569,6 +569,40 @@ module ApplicationTests assert_match "AccountTest#test_truth", output, "passing TEST= should run selected test" end + def test_running_with_ruby_gets_test_env_by_default + # Subshells inherit `ENV`, so we need to ensure `RAILS_ENV` is set to + # nil before we run the tests in the test app. + re, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], nil + + file = create_test_for_env("test") + results = Dir.chdir(app_path) { + `ruby -Ilib:test #{file}`.each_line.map { |line| JSON.parse line } + } + assert_equal 1, results.length + failures = results.first["failures"] + flunk(failures.first) unless failures.empty? + + ensure + ENV["RAILS_ENV"] = re + end + + def test_running_with_ruby_can_set_env_via_cmdline + # Subshells inherit `ENV`, so we need to ensure `RAILS_ENV` is set to + # nil before we run the tests in the test app. + re, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], nil + + file = create_test_for_env("development") + results = Dir.chdir(app_path) { + `RAILS_ENV=development ruby -Ilib:test #{file}`.each_line.map { |line| JSON.parse line } + } + assert_equal 1, results.length + failures = results.first["failures"] + flunk(failures.first) unless failures.empty? + + ensure + ENV["RAILS_ENV"] = re + end + def test_rake_passes_multiple_TESTOPTS_to_minitest create_test_file :models, "account" output = Dir.chdir(app_path) { `bin/rake test TESTOPTS='-v --seed=1234'` } @@ -727,6 +761,45 @@ module ApplicationTests app_file "db/schema.rb", "" end + def create_test_for_env(env) + app_file "test/models/environment_test.rb", <<-RUBY + require 'test_helper' + class JSONReporter < Minitest::AbstractReporter + def record(result) + puts JSON.dump(klass: result.class.name, + name: result.name, + failures: result.failures, + assertions: result.assertions, + time: result.time) + end + end + + def Minitest.plugin_json_reporter_init(opts) + Minitest.reporter.reporters.clear + Minitest.reporter.reporters << JSONReporter.new + end + + Minitest.extensions << "rails" + Minitest.extensions << "json_reporter" + + # Minitest uses RubyGems to find plugins, and since RubyGems + # doesn't know about the Rails installation we're pointing at, + # Minitest won't require the Rails minitest plugin when we run + # these integration tests. So we have to manually require the + # Minitest plugin here. + require 'minitest/rails_plugin' + + class EnvironmentTest < ActiveSupport::TestCase + def test_environment + test_db = ActiveRecord::Base.configurations[#{env.dump}]["database"] + db_file = ActiveRecord::Base.connection_config[:database] + assert_match(test_db, db_file) + assert_equal #{env.dump}, ENV["RAILS_ENV"] + end + end + RUBY + end + def create_test_file(path = :unit, name = "test", pass: true) app_file "test/#{path}/#{name}_test.rb", <<-RUBY require 'test_helper' diff --git a/railties/test/commands/credentials_test.rb b/railties/test/commands/credentials_test.rb index 4ef827fcf1..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 @@ -39,6 +35,32 @@ class Rails::Command::CredentialsCommandTest < ActiveSupport::TestCase end end + test "edit command does not overwrite by default if credentials already exists" do + run_edit_command(editor: "eval echo api_key: abc >") + assert_match(/api_key: abc/, run_show_command) + + run_edit_command + 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 @@ -46,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/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb index 7791d472d8..4815cf6362 100644 --- a/railties/test/generators/api_app_generator_test.rb +++ b/railties/test/generators/api_app_generator_test.rb @@ -72,6 +72,7 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase assert_no_file "config/initializers/cookies_serializer.rb" assert_no_file "config/initializers/assets.rb" + assert_no_file "config/initializers/content_security_policy.rb" end def test_app_update_does_not_generate_unnecessary_bin_files @@ -149,6 +150,7 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase bin/yarn config/initializers/assets.rb config/initializers/cookies_serializer.rb + config/initializers/content_security_policy.rb lib/assets test/helpers tmp/cache/assets diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index fddfab172e..fcb515c606 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -56,6 +56,7 @@ DEFAULT_APP_FILES = %w( config/initializers/assets.rb config/initializers/backtrace_silencers.rb config/initializers/cookies_serializer.rb + config/initializers/content_security_policy.rb config/initializers/filter_parameter_logging.rb config/initializers/inflections.rb config/initializers/mime_types.rb @@ -104,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 @@ -309,12 +319,14 @@ class AppGeneratorTest < Rails::Generators::TestCase case command when "active_storage:install" @binstub_called += 1 - assert_equal 1, @binstub_called, "active_storage:install expected to be called once, but was called #{@install_called} times." + assert_equal 1, @binstub_called, "active_storage:install expected to be called once, but was called #{@binstub_called} times" end 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 @@ -403,7 +415,7 @@ class AppGeneratorTest < Rails::Generators::TestCase if defined?(JRUBY_VERSION) assert_gem "activerecord-jdbcmysql-adapter" else - assert_gem "mysql2", "'>= 0.3.18', '< 0.5'" + assert_gem "mysql2", "'~> 0.4.4'" end end @@ -457,7 +469,7 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_generator_defaults_to_puma_version run_generator [destination_root] - assert_gem "puma", "'~> 3.7'" + assert_gem "puma", "'~> 3.11'" end def test_generator_if_skip_puma_is_given @@ -742,6 +754,45 @@ class AppGeneratorTest < Rails::Generators::TestCase end end + def test_webpack_option + command_check = -> command, *_ do + @called ||= 0 + if command == "webpacker:install" + @called += 1 + assert_equal 1, @called, "webpacker:install expected to be called once, but was called #{@called} times." + end + end + + generator([destination_root], webpack: "webpack").stub(:rails_command, command_check) do + generator.stub :bundle_command, nil do + quietly { generator.invoke_all } + end + end + + assert_gem "webpacker" + end + + def test_webpack_option_with_js_framework + command_check = -> command, *_ do + case command + when "webpacker:install" + @webpacker ||= 0 + @webpacker += 1 + assert_equal 1, @webpacker, "webpacker:install expected to be called once, but was called #{@webpacker} times." + when "webpacker:install:react" + @react ||= 0 + @react += 1 + assert_equal 1, @react, "webpacker:install:react expected to be called once, but was called #{@react} times." + end + end + + generator([destination_root], webpack: "react").stub(:rails_command, command_check) do + generator.stub :bundle_command, nil do + quietly { generator.invoke_all } + end + end + end + def test_generator_if_skip_turbolinks_is_given run_generator [destination_root, "--skip-turbolinks"] @@ -756,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/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index b6294c3b94..29426cd99f 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -471,8 +471,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase end assert_file "app/views/accounts/_form.html.erb" do |content| - assert_match(/^\W{4}<%= form\.text_field :name, id: :account_name %>/, content) - assert_match(/^\W{4}<%= form\.text_field :currency_id, id: :account_currency_id %>/, content) + assert_match(/^\W{4}<%= form\.text_field :name %>/, content) + assert_match(/^\W{4}<%= form\.text_field :currency_id %>/, content) end end @@ -495,8 +495,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase end assert_file "app/views/users/_form.html.erb" do |content| - assert_match(/<%= form\.password_field :password, id: :user_password %>/, content) - assert_match(/<%= form\.password_field :password_confirmation, id: :user_password_confirmation %>/, content) + assert_match(/<%= form\.password_field :password %>/, content) + assert_match(/<%= form\.password_field :password_confirmation %>/, content) end assert_file "app/views/users/index.html.erb" do |content| 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/generators_test.rb b/railties/test/generators_test.rb index 28e7617d7f..1735804664 100644 --- a/railties/test/generators_test.rb +++ b/railties/test/generators_test.rb @@ -36,6 +36,19 @@ class GeneratorsTest < Rails::Generators::TestCase assert_match "Maybe you meant 'migration'", output end + def test_generator_suggestions_except_en_locale + orig_available_locales = I18n.available_locales + orig_default_locale = I18n.default_locale + I18n.available_locales = :ja + I18n.default_locale = :ja + name = :tas + output = capture(:stdout) { Rails::Generators.invoke name } + assert_match "Maybe you meant 'task', 'job' or", output + ensure + I18n.available_locales = orig_available_locales + I18n.default_locale = orig_default_locale + end + def test_generator_multiple_suggestions name = :tas output = capture(:stdout) { Rails::Generators.invoke name } diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 7522237a38..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 @@ -358,10 +342,12 @@ module TestHelpers end def app_file(path, contents, mode = "w") - FileUtils.mkdir_p File.dirname("#{app_path}/#{path}") - File.open("#{app_path}/#{path}", mode) do |f| + file_name = "#{app_path}/#{path}" + FileUtils.mkdir_p File.dirname(file_name) + File.open(file_name, mode) do |f| f.puts contents end + file_name end def remove_file(path) @@ -381,6 +367,21 @@ module TestHelpers $:.reject! { |path| path =~ %r'/(#{to_remove.join('|')})/' } end + + def use_postgresql + File.open("#{app_path}/config/database.yml", "w") do |f| + f.puts <<-YAML + default: &default + adapter: postgresql + pool: 5 + database: railties_test + development: + <<: *default + test: + <<: *default + YAML + end + end end end @@ -389,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 fc710feb63..339a56c34f 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -980,14 +980,14 @@ YAML boot_rails app_generators = Rails.application.config.generators.options[:rails] - assert_equal :mongoid , app_generators[:orm] - assert_equal :liquid , app_generators[:template_engine] + assert_equal :mongoid, app_generators[:orm] + assert_equal :liquid, app_generators[:template_engine] assert_equal :test_unit, app_generators[:test_framework] generators = Bukkits::Engine.config.generators.options[:rails] assert_equal :data_mapper, generators[:orm] - assert_equal :haml , generators[:template_engine] - assert_equal :rspec , generators[:test_framework] + assert_equal :haml, generators[:template_engine] + assert_equal :rspec, generators[:test_framework] end test "engine should get default generators with ability to overwrite them" do @@ -1003,10 +1003,10 @@ YAML generators = Bukkits::Engine.config.generators.options[:rails] assert_equal :active_record, generators[:orm] - assert_equal :rspec , generators[:test_framework] + assert_equal :rspec, generators[:test_framework] app_generators = Rails.application.config.generators.options[:rails] - assert_equal :test_unit , app_generators[:test_framework] + assert_equal :test_unit, app_generators[:test_framework] end test "do not create table_name_prefix method if it already exists" do @@ -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 |