From 7105a53b72b6f55a1b8da8be914b556487e497f7 Mon Sep 17 00:00:00 2001 From: maximerety Date: Fri, 5 Jan 2024 18:46:02 +0100 Subject: [PATCH] [Fix #50604] Restore compatibility of ARE configs with eager loading mode Configure ActiveRecord::Encryption (ARE) on ActiveRecord::Base (AR) loading, so that ARE configs are ready before AR models start using `encrypts` to declare encrypted attributes. This means that you can add ARE configurations in initializers, as long as you don't trigger the loading of ActiveRecord::Base or your AR models in prior initializers. --- activerecord/CHANGELOG.md | 6 ++ activerecord/lib/active_record/railtie.rb | 17 ++--- .../test/application/configuration_test.rb | 75 +++++++++++++++++++ 3 files changed, 88 insertions(+), 10 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 1c385682b69c..b60ee1850cfb 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,9 @@ +* Fix an issue where `ActiveRecord::Encryption` configurations are not ready before the loading + of Active Record models, when an application is eager loaded. As a result, encrypted attributes + could be misconfigured in some cases. + + *Maxime Réty* + * Add an option to `ActiveRecord::Encryption::Encryptor` to disable compression Allow compression to be disabled by setting `compress: false` diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 63caf32fe05d..a4309ab831d0 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -378,23 +378,20 @@ class Railtie < Rails::Railtie # :nodoc: end initializer "active_record_encryption.configuration" do |app| - auto_filtered_parameters = ActiveRecord::Encryption::AutoFilteredParameters.new(app) - - config.after_initialize do |app| + ActiveSupport.on_load(:active_record) do ActiveRecord::Encryption.configure \ primary_key: app.credentials.dig(:active_record_encryption, :primary_key), deterministic_key: app.credentials.dig(:active_record_encryption, :deterministic_key), key_derivation_salt: app.credentials.dig(:active_record_encryption, :key_derivation_salt), - **config.active_record.encryption + **app.config.active_record.encryption + auto_filtered_parameters = ActiveRecord::Encryption::AutoFilteredParameters.new(app) auto_filtered_parameters.enable if ActiveRecord::Encryption.config.add_to_filter_parameters - ActiveSupport.on_load(:active_record) do - # Support extended queries for deterministic attributes and validations - if ActiveRecord::Encryption.config.extend_queries - ActiveRecord::Encryption::ExtendedDeterministicQueries.install_support - ActiveRecord::Encryption::ExtendedDeterministicUniquenessValidator.install_support - end + # Support extended queries for deterministic attributes and validations + if ActiveRecord::Encryption.config.extend_queries + ActiveRecord::Encryption::ExtendedDeterministicQueries.install_support + ActiveRecord::Encryption::ExtendedDeterministicUniquenessValidator.install_support end end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 7a1d794ed86f..f62139db6617 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -31,6 +31,13 @@ def self.safe_list_sanitizer end end +class ::MyCustomKeyProvider + attr_reader :primary_key + def initialize(primary_key); @primary_key = primary_key; end +end + +class ::MyOldKeyProvider; end + module ApplicationTests class ConfigurationTest < ActiveSupport::TestCase include ActiveSupport::Testing::Isolation @@ -3819,6 +3826,74 @@ class Post < ActiveRecord::Base assert_not_includes ActiveRecord::Base.filter_attributes, :content end + test "ActiveRecord::Encryption.config is ready for encrypted attributes when app is lazy loaded" do + add_to_config <<-RUBY + config.enable_reloading = false + config.eager_load = false + RUBY + + app_file "config/initializers/active_record.rb", <<-RUBY + Rails.application.config.active_record.encryption.primary_key = "dummy_key" + Rails.application.config.active_record.encryption.previous = [ { key_provider: MyOldKeyProvider.new } ] + + ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") + ActiveRecord::Migration.verbose = false + ActiveRecord::Schema.define(version: 1) do + create_table :posts do |t| + t.string :content + end + end + + ActiveRecord::Base.connection.schema_cache.add("posts") + RUBY + + app_file "app/models/post.rb", <<-RUBY + class Post < ActiveRecord::Base + encrypts :content, key_provider: MyCustomKeyProvider.new(ActiveRecord::Encryption.config.primary_key) + end + RUBY + + app "development" + + assert_kind_of ::MyOldKeyProvider, Post.attribute_types["content"].previous_schemes.first.key_provider + assert_kind_of ::MyCustomKeyProvider, Post.attribute_types["content"].scheme.key_provider + assert_equal "dummy_key", Post.attribute_types["content"].scheme.key_provider.primary_key + end + + test "ActiveRecord::Encryption.config is ready for encrypted attributes when app is eager loaded" do + add_to_config <<-RUBY + config.enable_reloading = false + config.eager_load = true + RUBY + + app_file "app/models/post.rb", <<-RUBY + class Post < ActiveRecord::Base + encrypts :content, key_provider: MyCustomKeyProvider.new(ActiveRecord::Encryption.config.primary_key) + end + RUBY + + app_file "config/initializers/active_record.rb", <<-RUBY + Rails.application.config.active_record.encryption.primary_key = "dummy_key" + Rails.application.config.active_record.encryption.previous = [ { key_provider: MyOldKeyProvider.new } ] + + ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") + ActiveRecord::Migration.verbose = false + ActiveRecord::Schema.define(version: 1) do + create_table :posts do |t| + t.string :content + end + end + + ActiveRecord::Base.connection.schema_cache.add("posts") + RUBY + + app "production" + + assert_kind_of ::MyOldKeyProvider, Post.attribute_types["content"].previous_schemes.first&.key_provider + assert_kind_of ::MyCustomKeyProvider, Post.attribute_types["content"].scheme.key_provider + assert_equal "dummy_key", Post.attribute_types["content"].scheme.key_provider.primary_key + end + test "ActiveStorage.routes_prefix can be configured via config.active_storage.routes_prefix" do app_file "config/environments/development.rb", <<-RUBY Rails.application.configure do