Skip to content

Commit

Permalink
Fix Active Record encryption not picking up encryption settings with …
Browse files Browse the repository at this point in the history
…eager-loading (#48577)

This deals with a problem where, when eager-loading is enabled, Active Record fails to pick up settings affecting encryption schemes.

The solution here is to resolve encryption schemes lazily, when the attribute type is used.

Follow-up from #48530

Closes #48204 (comment)
  • Loading branch information
jorgemanrubia committed Jun 26, 2023
1 parent ba41696 commit 4f36572
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 11 deletions.
18 changes: 9 additions & 9 deletions activerecord/lib/active_record/encryption/encryptable_record.rb
Expand Up @@ -44,11 +44,9 @@ module EncryptableRecord
# encryption is used, they will be used to generate additional ciphertexts to check in the queries.
def encrypts(*names, key_provider: nil, key: nil, deterministic: false, downcase: false, ignore_case: false, previous: [], **context_properties)
self.encrypted_attributes ||= Set.new # not using :default because the instance would be shared across classes
scheme = scheme_for key_provider: key_provider, key: key, deterministic: deterministic, downcase: downcase, \
ignore_case: ignore_case, previous: previous, **context_properties

names.each do |name|
encrypt_attribute name, scheme
encrypt_attribute name, key_provider: key_provider, key: key, deterministic: deterministic, downcase: downcase, ignore_case: ignore_case, previous: previous, **context_properties
end
end

Expand All @@ -67,9 +65,9 @@ def source_attribute_from_preserved_attribute(attribute_name)
private
def scheme_for(key_provider: nil, key: nil, deterministic: false, downcase: false, ignore_case: false, previous: [], **context_properties)
ActiveRecord::Encryption::Scheme.new(key_provider: key_provider, key: key, deterministic: deterministic,
downcase: downcase, ignore_case: ignore_case, **context_properties).tap do |scheme|
downcase: downcase, ignore_case: ignore_case, **context_properties).tap do |scheme|
scheme.previous_schemes = global_previous_schemes_for(scheme) +
Array.wrap(previous).collect { |scheme_config| ActiveRecord::Encryption::Scheme.new(**scheme_config) }
Array.wrap(previous).collect { |scheme_config| ActiveRecord::Encryption::Scheme.new(**scheme_config) }
end
end

Expand All @@ -79,15 +77,17 @@ def global_previous_schemes_for(scheme)
end
end

def encrypt_attribute(name, attribute_scheme)
def encrypt_attribute(name, key_provider: nil, key: nil, deterministic: false, downcase: false, ignore_case: false, previous: [], **context_properties)
encrypted_attributes << name.to_sym

attribute name do |cast_type|
ActiveRecord::Encryption::EncryptedAttributeType.new(scheme: attribute_scheme, cast_type: cast_type,
default: columns_hash[name.to_s]&.default)
scheme = scheme_for key_provider: key_provider, key: key, deterministic: deterministic, downcase: downcase, \
ignore_case: ignore_case, previous: previous, **context_properties
ActiveRecord::Encryption::EncryptedAttributeType.new(scheme: scheme, cast_type: cast_type,
default: columns_hash[name.to_s]&.default)
end

preserve_original_encrypted(name) if attribute_scheme.ignore_case?
preserve_original_encrypted(name) if ignore_case
ActiveRecord::Encryption.encrypted_attribute_was_declared(self, name)
end

Expand Down
15 changes: 15 additions & 0 deletions activerecord/test/cases/encryption/encryptable_record_test.rb
Expand Up @@ -332,6 +332,21 @@ def name
assert_equal "Post 1", encrypted_post_class_sha_256.last.title
end

test "encryption schemes are resolved when used, not when declared" do
OtherEncryptedPost = Class.new(Post) do
self.table_name = "posts"
encrypts :title
end

ActiveRecord::Encryption.configure \
primary_key: "the primary key",
deterministic_key: "the deterministic key",
key_derivation_salt: "the salt",
support_sha1_for_non_deterministic_encryption: true

assert OtherEncryptedPost.type_for_attribute(:title).scheme.previous_schemes.one?
end

private
def build_derived_key_provider_with(hash_digest_class)
ActiveRecord::Encryption.with_encryption_context(key_generator: ActiveRecord::Encryption::KeyGenerator.new(hash_digest_class: hash_digest_class)) do
Expand Down
4 changes: 2 additions & 2 deletions activerecord/test/cases/encryption/scheme_test.rb
Expand Up @@ -4,7 +4,7 @@
require "models/book"

class ActiveRecord::Encryption::SchemeTest < ActiveRecord::EncryptionTestCase
test "validates config options when declaring encrypted attributes" do
test "validates config options when using encrypted attributes" do
assert_invalid_declaration deterministic: false, ignore_case: true
assert_invalid_declaration key: "1234", key_provider: ActiveRecord::Encryption::DerivedSecretKeyProvider.new("my secret")

Expand Down Expand Up @@ -37,6 +37,6 @@ def declare_and_use_class(**options)
def declare_encrypts_with(options)
Class.new(Book) do
encrypts :name, **options
end
end.type_for_attribute(:name)
end
end

0 comments on commit 4f36572

Please sign in to comment.