Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix queries for deterministically encrypted attributed for data migrated from 7.0 #48676

Merged
merged 1 commit into from Jul 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -72,8 +72,8 @@ def scheme_for(key_provider: nil, key: nil, deterministic: false, downcase: fals
end

def global_previous_schemes_for(scheme)
ActiveRecord::Encryption.config.previous_schemes.collect do |previous_scheme|
scheme.merge(previous_scheme)
ActiveRecord::Encryption.config.previous_schemes.filter_map do |previous_scheme|
scheme.merge(previous_scheme) if scheme.compatible_with?(previous_scheme)
end
end

Expand Down
6 changes: 5 additions & 1 deletion activerecord/lib/active_record/encryption/scheme.rb
Expand Up @@ -36,7 +36,7 @@ def downcase?
end

def deterministic?
@deterministic
!!@deterministic
end

def fixed?
Expand Down Expand Up @@ -65,6 +65,10 @@ def with_context(&block)
end
end

def compatible_with?(other_scheme)
deterministic? == other_scheme.deterministic?
end

private
def validate_config!
raise Errors::Configuration, "ignore_case: can only be used with deterministic encryption" if @ignore_case && !@deterministic
Expand Down
12 changes: 6 additions & 6 deletions activerecord/lib/active_record/railtie.rb
Expand Up @@ -367,13 +367,13 @@ class Railtie < Rails::Railtie # :nodoc:
**config.active_record.encryption

auto_filtered_parameters.enable if ActiveRecord::Encryption.config.add_to_filter_parameters
end

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
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
end
end

Expand Down
51 changes: 49 additions & 2 deletions activerecord/test/cases/encryption/encryption_schemes_test.rb
Expand Up @@ -101,7 +101,7 @@ class ActiveRecord::Encryption::EncryptionSchemesTest < ActiveRecord::Encryption
test "deterministic encryption is fixed by default: it will always use the oldest scheme to encrypt data" do
ActiveRecord::Encryption.config.support_unencrypted_data = false
ActiveRecord::Encryption.config.deterministic_key = "12345"
ActiveRecord::Encryption.config.previous = [{ downcase: true }, { downcase: false }]
ActiveRecord::Encryption.config.previous = [{ downcase: true, deterministic: true }, { downcase: false, deterministic: true }]

encrypted_author_class = Class.new(Author) do
self.table_name = "authors"
Expand All @@ -113,10 +113,25 @@ class ActiveRecord::Encryption::EncryptionSchemesTest < ActiveRecord::Encryption
assert_equal "stephen king", author.name
end

test "don't use global previous schemes with a different deterministic nature" do
ActiveRecord::Encryption.config.support_unencrypted_data = false
ActiveRecord::Encryption.config.deterministic_key = "12345"
ActiveRecord::Encryption.config.previous = [{ downcase: true, deterministic: false }, { downcase: false, deterministic: true }]

encrypted_author_class = Class.new(Author) do
self.table_name = "authors"

encrypts :name, deterministic: true, downcase: false
end

author = encrypted_author_class.create!(name: "STEPHEN KING")
assert_equal "STEPHEN KING", author.name
end

test "deterministic encryption will use the newest encryption scheme to encrypt data when setting it to { fixed: false }" do
ActiveRecord::Encryption.config.support_unencrypted_data = false
ActiveRecord::Encryption.config.deterministic_key = "12345"
ActiveRecord::Encryption.config.previous = [{ downcase: true }, { downcase: false }]
ActiveRecord::Encryption.config.previous = [{ downcase: true, deterministic: true }, { downcase: false, deterministic: true }]

encrypted_author_class = Class.new(Author) do
self.table_name = "authors"
Expand All @@ -128,6 +143,38 @@ class ActiveRecord::Encryption::EncryptionSchemesTest < ActiveRecord::Encryption
assert_equal "STEPHEN KING", author.name
end

test "use global previous schemes when performing queries" do
ActiveRecord::Encryption.config.support_unencrypted_data = false
ActiveRecord::Encryption.config.deterministic_key = "12345"
ActiveRecord::Encryption.config.previous = [{ downcase: true, deterministic: true }, { downcase: false, deterministic: true }]

encrypted_author_class = Class.new(Author) do
self.table_name = "authors"

encrypts :name, deterministic: true, downcase: false
end

author = encrypted_author_class.create!(name: "STEPHEN KING")
assert_equal author, encrypted_author_class.find_by_name("STEPHEN KING")
assert_equal author, encrypted_author_class.find_by_name("stephen king")
end

test "don't use global previous schemes with a different deterministic nature when performing queries" do
ActiveRecord::Encryption.config.support_unencrypted_data = false
ActiveRecord::Encryption.config.deterministic_key = "12345"
ActiveRecord::Encryption.config.previous = [{ downcase: true, deterministic: false }, { downcase: false, deterministic: true }]

encrypted_author_class = Class.new(Author) do
self.table_name = "authors"

encrypts :name, deterministic: true, downcase: false
end

author = encrypted_author_class.create!(name: "STEPHEN KING")
assert_equal author, encrypted_author_class.find_by_name("STEPHEN KING")
assert_nil encrypted_author_class.find_by_name("stephen king")
end

private
class TestEncryptor
def initialize(ciphertexts_by_clear_value)
Expand Down
Expand Up @@ -23,7 +23,7 @@ class ActiveRecord::Encryption::UniquenessValidationsTest < ActiveRecord::Encryp
end

test "uniqueness validations work when using old encryption schemes" do
ActiveRecord::Encryption.config.previous = [ { downcase: true } ]
ActiveRecord::Encryption.config.previous = [ { downcase: true, deterministic: true } ]

OldEncryptionBook = Class.new(UnencryptedBook) do
self.table_name = "encrypted_books"
Expand Down