Add support for uniqueness validations

This commit is contained in:
Jorge Manrubia 2021-03-22 18:13:59 +01:00
parent f78a480818
commit a61692cf41
7 changed files with 78 additions and 1 deletions

@ -23,6 +23,7 @@ module Encryption
autoload :EnvelopeEncryptionKeyProvider autoload :EnvelopeEncryptionKeyProvider
autoload :Errors autoload :Errors
autoload :ExtendedDeterministicQueries autoload :ExtendedDeterministicQueries
autoload :ExtendedDeterministicUniquenessValidator
autoload :Key autoload :Key
autoload :KeyGenerator autoload :KeyGenerator
autoload :KeyProvider autoload :KeyProvider

@ -0,0 +1,29 @@
# frozen_string_literal: true
module ActiveRecord
module Encryption
module ExtendedDeterministicUniquenessValidator
def self.install_support
ActiveRecord::Validations::UniquenessValidator.prepend(EncryptedUniquenessValidator)
end
module EncryptedUniquenessValidator
def validate_each(record, attribute, value)
super(record, attribute, value)
klass = record.class
if klass.deterministic_encrypted_attributes&.each do |attribute_name|
encrypted_type = klass.type_for_attribute(attribute_name)
[ encrypted_type, *encrypted_type.previous_encrypted_types ].each do |type|
encrypted_value = type.serialize(value)
ActiveRecord::Encryption.without_encryption do
super(record, attribute, encrypted_value)
end
end
end
end
end
end
end
end
end

@ -291,9 +291,10 @@ class Railtie < Rails::Railtie # :nodoc:
ActiveRecord::Fixture.prepend ActiveRecord::Encryption::EncryptedFixtures ActiveRecord::Fixture.prepend ActiveRecord::Encryption::EncryptedFixtures
end end
# Support extended queries for deterministic attributes # Support extended queries for deterministic attributes and validations
if ActiveRecord::Encryption.config.extend_queries if ActiveRecord::Encryption.config.extend_queries
ActiveRecord::Encryption::ExtendedDeterministicQueries.install_support ActiveRecord::Encryption::ExtendedDeterministicQueries.install_support
ActiveRecord::Encryption::ExtendedDeterministicUniquenessValidator.install_support
end end
end end

@ -0,0 +1,43 @@
# frozen_string_literal: true
require "cases/encryption/helper"
require "models/book_encrypted"
require "models/author_encrypted"
class ActiveRecord::Encryption::UniquenessValidationsTest < ActiveRecord::EncryptionTestCase
fixtures :books
test "uniqueness validations work" do
EncryptedBookWithDowncaseName.create!(name: "dune")
assert_raises ActiveRecord::RecordInvalid do
EncryptedBookWithDowncaseName.create!(name: "dune")
end
end
test "uniqueness validations work when mixing encrypted an unencrypted data" do
ActiveRecord::Encryption.config.support_unencrypted_data = true
ActiveRecord::Encryption.without_encryption { EncryptedBookWithDowncaseName.create! name: "dune" }
assert_raises ActiveRecord::RecordInvalid do
EncryptedBookWithDowncaseName.create!(name: "dune")
end
end
test "uniqueness validations work when using old encryption schemes" do
ActiveRecord::Encryption.config.previous = [ { downcase: true } ]
OldEncryptionBook = Class.new(Book) do
self.table_name = "books"
validates :name, uniqueness: true
encrypts :name, deterministic: true, downcase: false
end
OldEncryptionBook.create! name: "dune"
assert_raises ActiveRecord::RecordInvalid do
OldEncryptionBook.create! name: "DUNE"
end
end
end

@ -231,3 +231,4 @@ def in_time_zone(zone)
key_derivation_salt: "testing key derivation salt" key_derivation_salt: "testing key derivation salt"
ActiveRecord::Encryption::ExtendedDeterministicQueries.install_support ActiveRecord::Encryption::ExtendedDeterministicQueries.install_support
ActiveRecord::Encryption::ExtendedDeterministicUniquenessValidator.install_support

@ -5,6 +5,7 @@
class EncryptedAuthor < Author class EncryptedAuthor < Author
self.table_name = "authors" self.table_name = "authors"
validates :name, uniqueness: true
encrypts :name, previous: { deterministic: true } encrypts :name, previous: { deterministic: true }
end end

@ -11,6 +11,7 @@ class EncryptedBook < ActiveRecord::Base
class EncryptedBookWithDowncaseName < ActiveRecord::Base class EncryptedBookWithDowncaseName < ActiveRecord::Base
self.table_name = "books" self.table_name = "books"
validates :name, uniqueness: true
encrypts :name, deterministic: true, downcase: true encrypts :name, deterministic: true, downcase: true
end end