[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.
This commit is contained in:
maximerety 2024-01-05 18:46:02 +01:00
parent f5910f74d4
commit d997c554b3
No known key found for this signature in database
GPG Key ID: 0D204798EE2F514A
3 changed files with 88 additions and 10 deletions

View File

@ -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*
* Deprecate defining an `enum` with keyword arguments.
```ruby

View File

@ -378,23 +378,20 @@ To keep using the current cache store, you can turn off cache versioning entirel
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

View File

@ -31,6 +31,13 @@ class ::MySanitizerVendor < ::Rails::HTML::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 @@ module ApplicationTests
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