lib/rails/application.rb in railties-7.0.8.6 vs lib/rails/application.rb in railties-7.1.0.beta1

- old
+ new

@@ -2,11 +2,12 @@ require "yaml" require "active_support/core_ext/hash/keys" require "active_support/core_ext/object/blank" require "active_support/key_generator" -require "active_support/message_verifier" +require "active_support/message_verifiers" +require "active_support/deprecation" require "active_support/encrypted_configuration" require "active_support/hash_with_indifferent_access" require "active_support/configuration_file" require "rails/engine" require "rails/secrets" @@ -24,11 +25,11 @@ # # == \Configuration # # Besides providing the same configuration as Rails::Engine and Rails::Railtie, # the application object has several specific configurations, for example - # +cache_classes+, +consider_all_requests_local+, +filter_parameters+, + # +enable_reloading+, +consider_all_requests_local+, +filter_parameters+, # +logger+, and so forth. # # Check Rails::Application::Configuration to see them all. # # == Routes @@ -68,10 +69,12 @@ class << self def inherited(base) super Rails.app_class = base + # lib has to be added to $LOAD_PATH unconditionally, even if it's in the + # autoload paths and config.add_autoload_paths_to_load_path is false. add_lib_to_load_path!(find_root(base.called_from)) ActiveSupport.run_load_hooks(:before_configuration, base) end def instance @@ -109,11 +112,13 @@ @reloaders = [] @routes_reloader = nil @app_env_config = nil @ordered_railties = nil @railties = nil - @message_verifiers = {} + @key_generators = {} + @message_verifiers = nil + @deprecators = nil @ran_load_hooks = false @executor = Class.new(ActiveSupport::Executor) @reloader = Class.new(ActiveSupport::Reloader) @reloader.executor = @executor @@ -147,19 +152,57 @@ # Reload application routes regardless if they changed or not. def reload_routes! routes_reloader.reload! end - # Returns the application's KeyGenerator - def key_generator + # Returns a key generator (ActiveSupport::CachingKeyGenerator) for a + # specified +secret_key_base+. The return value is memoized, so additional + # calls with the same +secret_key_base+ will return the same key generator + # instance. + def key_generator(secret_key_base = self.secret_key_base) # number of iterations selected based on consultation with the google security # team. Details at https://github.com/rails/rails/pull/6952#issuecomment-7661220 - @caching_key_generator ||= ActiveSupport::CachingKeyGenerator.new( + @key_generators[secret_key_base] ||= ActiveSupport::CachingKeyGenerator.new( ActiveSupport::KeyGenerator.new(secret_key_base, iterations: 1000) ) end + # Returns a message verifier factory (ActiveSupport::MessageVerifiers). This + # factory can be used as a central point to configure and create message + # verifiers (ActiveSupport::MessageVerifier) for your application. + # + # By default, message verifiers created by this factory will generate + # messages using the default ActiveSupport::MessageVerifier options. You can + # override these options with a combination of + # ActiveSupport::MessageVerifiers#clear_rotations and + # ActiveSupport::MessageVerifiers#rotate. However, this must be done prior + # to building any message verifier instances. For example, in a + # +before_initialize+ block: + # + # # Use `url_safe: true` when generating messages + # config.before_initialize do |app| + # app.message_verifiers.clear_rotations + # app.message_verifiers.rotate(url_safe: true) + # end + # + # Message verifiers created by this factory will always use a secret derived + # from #secret_key_base when generating messages. +clear_rotations+ will not + # affect this behavior. However, older +secret_key_base+ values can be + # rotated for verifying messages: + # + # # Fall back to old `secret_key_base` when verifying messages + # config.before_initialize do |app| + # app.message_verifiers.rotate(secret_key_base: "old secret_key_base") + # end + # + def message_verifiers + @message_verifiers ||= + ActiveSupport::MessageVerifiers.new do |salt, secret_key_base: self.secret_key_base| + key_generator(secret_key_base).generate_key(salt) + end.rotate_defaults + end + # Returns a message verifier object. # # This verifier can be used to generate and verify signed messages in the application. # # It is recommended not to use the same verifier for different things, so you can get different @@ -175,17 +218,24 @@ # Rails.application.message_verifier('sensitive_data').verify(message) # # => 'my sensible data' # # See the ActiveSupport::MessageVerifier documentation for more information. def message_verifier(verifier_name) - @message_verifiers[verifier_name] ||= begin - secret = key_generator.generate_key(verifier_name.to_s) - ActiveSupport::MessageVerifier.new(secret) + message_verifiers[verifier_name] + end + + # A managed collection of deprecators (ActiveSupport::Deprecation::Deprecators). + # The collection's configuration methods affect all deprecators in the + # collection. Additionally, the collection's +silence+ method silences all + # deprecators in the collection for the duration of a given block. + def deprecators + @deprecators ||= ActiveSupport::Deprecation::Deprecators.new.tap do |deprecators| + deprecators[:railties] = Rails.deprecator end end - # Convenience for loading config/foo.yml for the current Rails env. + # Convenience for loading config/foo.yml for the current \Rails env. # # Examples: # # # config/exception_notification.yml: # production: @@ -243,20 +293,21 @@ else raise "Could not load configuration. No such file - #{yaml}" end end - # Stores some of the Rails initial environment parameters which + # Stores some of the \Rails initial environment parameters which # will be used by middlewares and engines to configure themselves. def env_config @app_env_config ||= super.merge( - "action_dispatch.parameter_filter" => config.filter_parameters, + "action_dispatch.parameter_filter" => filter_parameters, "action_dispatch.redirect_filter" => config.filter_redirect, "action_dispatch.secret_key_base" => secret_key_base, "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions, "action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local, "action_dispatch.log_rescued_responses" => config.action_dispatch.log_rescued_responses, + "action_dispatch.debug_exception_log_level" => ActiveSupport::Logger.const_get(config.action_dispatch.debug_exception_log_level.to_s.upcase), "action_dispatch.logger" => Rails.logger, "action_dispatch.backtrace_cleaner" => Rails.backtrace_cleaner, "action_dispatch.key_generator" => key_generator, "action_dispatch.http_auth_salt" => config.action_dispatch.http_auth_salt, "action_dispatch.signed_cookie_salt" => config.action_dispatch.signed_cookie_salt, @@ -335,11 +386,11 @@ # Notice this method takes into consideration the default root path. So if you # are changing config.root inside your application definition or having a custom # Rails application, you will need to add lib to $LOAD_PATH on your own in case # you need to load files in lib/ during the application configuration as well. def self.add_lib_to_load_path!(root) # :nodoc: - path = File.join root, "lib" + path = File.join(root, "lib") if File.exist?(path) && !$LOAD_PATH.include?(path) $LOAD_PATH.unshift(path) end end @@ -385,10 +436,13 @@ end attr_writer :config def secrets + Rails.deprecator.warn(<<~MSG.squish) + `Rails.application.secrets` is deprecated in favor of `Rails.application.credentials` and will be removed in Rails 7.2. + MSG @secrets ||= begin secrets = ActiveSupport::OrderedOptions.new files = config.paths["config/secrets"].existent files = files.reject { |path| path.end_with?(".enc") } unless config.read_encrypted_secrets secrets.merge! Rails::Secrets.parse(files, env: Rails.env) @@ -405,18 +459,24 @@ # The secret_key_base is used as the input secret to the application's key generator, which in turn # is used to create all ActiveSupport::MessageVerifier and ActiveSupport::MessageEncryptor instances, # including the ones that sign and encrypt cookies. # # In development and test, this is randomly generated and stored in a - # temporary file in <tt>tmp/development_secret.txt</tt>. + # temporary file in <tt>tmp/local_secret.txt</tt>. # + # You can also set <tt>ENV["SECRET_KEY_BASE_DUMMY"]</tt> to trigger the use of a randomly generated + # secret_key_base that's stored in a temporary file. This is useful when precompiling assets for + # production as part of a build step that otherwise does not need access to the production secrets. + # + # Dockerfile example: <tt>RUN SECRET_KEY_BASE_DUMMY=1 bundle exec rails assets:precompile</tt>. + # # In all other environments, we look for it first in <tt>ENV["SECRET_KEY_BASE"]</tt>, # then +credentials.secret_key_base+, and finally +secrets.secret_key_base+. For most applications, # the correct place to store it is in the encrypted credentials file. def secret_key_base - if Rails.env.development? || Rails.env.test? - secrets.secret_key_base ||= generate_development_secret + if Rails.env.local? || ENV["SECRET_KEY_BASE_DUMMY"] + config.secret_key_base ||= generate_local_secret else validate_secret_key_base( ENV["SECRET_KEY_BASE"] || credentials.secret_key_base || secrets.secret_key_base ) end @@ -486,10 +546,15 @@ # +railties_order+. def migration_railties # :nodoc: ordered_railties.flatten - [self] end + def load_generators(app = self) # :nodoc: + app.ensure_generator_templates_added + super + end + # Eager loads the application code. def eager_load! Rails.autoloaders.each(&:eager_load) end @@ -575,27 +640,41 @@ else raise ArgumentError, "Missing `secret_key_base` for '#{Rails.env}' environment, set this string with `bin/rails credentials:edit`" end end + def ensure_generator_templates_added + configured_paths = config.generators.templates + configured_paths.unshift(*(paths["lib/templates"].existent - configured_paths)) + end + private - def generate_development_secret - if secrets.secret_key_base.nil? - key_file = Rails.root.join("tmp/development_secret.txt") + def generate_local_secret + if config.secret_key_base.nil? + key_file = Rails.root.join("tmp/local_secret.txt") - if !File.exist?(key_file) + if File.exist?(key_file) + config.secret_key_base = File.binread(key_file) + elsif secrets_secret_key_base + config.secret_key_base = secrets_secret_key_base + else random_key = SecureRandom.hex(64) FileUtils.mkdir_p(key_file.dirname) File.binwrite(key_file, random_key) + config.secret_key_base = File.binread(key_file) end - - secrets.secret_key_base = File.binread(key_file) end - secrets.secret_key_base + config.secret_key_base end + def secrets_secret_key_base + Rails.deprecator.silence do + secrets.secret_key_base + end + end + def build_request(env) req = super env["ORIGINAL_FULLPATH"] = req.fullpath env["ORIGINAL_SCRIPT_NAME"] = req.script_name req @@ -605,8 +684,17 @@ config.app_middleware + super end def coerce_same_site_protection(protection) protection.respond_to?(:call) ? protection : proc { protection } + end + + def filter_parameters + if config.precompile_filter_parameters + config.filter_parameters.replace( + ActiveSupport::ParameterFilter.precompile_filters(config.filter_parameters) + ) + end + config.filter_parameters end end end