From 3637c916a57d4abe1bb302830e12dcc578387aa0 Mon Sep 17 00:00:00 2001 From: Austin Miller Date: Mon, 12 Apr 2021 15:05:50 -0500 Subject: [PATCH 1/4] Prefixes our encrypted_attributes with library specific attr_encrypted Prefixes our encrypted_attributes with library specific attr_encrypted so we are not clashing with the Rails 7.0 definition of encrypted_attributes --- README.md | 2 +- lib/attr_encrypted.rb | 32 ++++++++++---------- lib/attr_encrypted/adapters/active_record.rb | 12 ++++---- test/active_record_test.rb | 10 +++--- test/attr_encrypted_test.rb | 22 +++++++------- test/legacy_attr_encrypted_test.rb | 12 ++++---- 6 files changed, 45 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 87ac7219..d07d0b1f 100644 --- a/README.md +++ b/README.md @@ -425,7 +425,7 @@ It is recommended that you implement a strategy to insure that you do not mix th attr_encrypted :ssn, key: :encryption_key, v2_gcm_iv: is_decrypting?(:ssn) def is_decrypting?(attribute) - encrypted_attributes[attribute][:operation] == :decrypting + attr_encrypted_encrypted_attributes[attribute][:operation] == :decrypting end end diff --git a/lib/attr_encrypted.rb b/lib/attr_encrypted.rb index 88e5f65e..44f3781f 100644 --- a/lib/attr_encrypted.rb +++ b/lib/attr_encrypted.rb @@ -10,7 +10,7 @@ def self.extended(base) # :nodoc: base.class_eval do include InstanceMethods attr_writer :attr_encrypted_options - @attr_encrypted_options, @encrypted_attributes = {}, {} + @attr_encrypted_options, @attr_encrypted_encrypted_attributes = {}, {} end end @@ -173,7 +173,7 @@ def attr_encrypted(*attributes) value.respond_to?(:empty?) ? !value.empty? : !!value end - encrypted_attributes[attribute.to_sym] = options.merge(attribute: encrypted_attribute_name) + self.attr_encrypted_encrypted_attributes[attribute.to_sym] = options.merge(attribute: encrypted_attribute_name) end end @@ -223,7 +223,7 @@ def attr_encrypted_default_options # User.attr_encrypted?(:name) # false # User.attr_encrypted?(:email) # true def attr_encrypted?(attribute) - encrypted_attributes.has_key?(attribute.to_sym) + attr_encrypted_encrypted_attributes.has_key?(attribute.to_sym) end # Decrypts a value for the attribute specified @@ -236,7 +236,7 @@ def attr_encrypted?(attribute) # # email = User.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING') def decrypt(attribute, encrypted_value, options = {}) - options = encrypted_attributes[attribute.to_sym].merge(options) + options = attr_encrypted_encrypted_attributes[attribute.to_sym].merge(options) if options[:if] && !options[:unless] && not_empty?(encrypted_value) encrypted_value = encrypted_value.unpack(options[:encode]).first if options[:encode] value = options[:encryptor].send(options[:decrypt_method], options.merge!(value: encrypted_value)) @@ -262,7 +262,7 @@ def decrypt(attribute, encrypted_value, options = {}) # # encrypted_email = User.encrypt(:email, 'test@example.com') def encrypt(attribute, value, options = {}) - options = encrypted_attributes[attribute.to_sym].merge(options) + options = attr_encrypted_encrypted_attributes[attribute.to_sym].merge(options) if options[:if] && !options[:unless] && (options[:allow_empty_value] || not_empty?(value)) value = options[:marshal] ? options[:marshaler].send(options[:dump_method], value) : value.to_s encrypted_value = options[:encryptor].send(options[:encrypt_method], options.merge!(value: value)) @@ -286,9 +286,9 @@ def not_empty?(value) # attr_encrypted :email, key: 'my secret key' # end # - # User.encrypted_attributes # { email: { attribute: 'encrypted_email', key: 'my secret key' } } - def encrypted_attributes - @encrypted_attributes ||= superclass.encrypted_attributes.dup + # User.attr_encrypted_encrypted_attributes # { email: { attribute: 'encrypted_email', key: 'my secret key' } } + def attr_encrypted_encrypted_attributes + @attr_encrypted_encrypted_attributes ||= superclass.attr_encrypted_encrypted_attributes.dup end # Forwards calls to :encrypt_#{attribute} or :decrypt_#{attribute} to the corresponding encrypt or decrypt method @@ -326,8 +326,8 @@ module InstanceMethods # @user = User.new('some-secret-key') # @user.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING') def decrypt(attribute, encrypted_value) - encrypted_attributes[attribute.to_sym][:operation] = :decrypting - encrypted_attributes[attribute.to_sym][:value_present] = self.class.not_empty?(encrypted_value) + attr_encrypted_encrypted_attributes[attribute.to_sym][:operation] = :decrypting + attr_encrypted_encrypted_attributes[attribute.to_sym][:value_present] = self.class.not_empty?(encrypted_value) self.class.decrypt(attribute, encrypted_value, evaluated_attr_encrypted_options_for(attribute)) end @@ -347,18 +347,18 @@ def decrypt(attribute, encrypted_value) # @user = User.new('some-secret-key') # @user.encrypt(:email, 'test@example.com') def encrypt(attribute, value) - encrypted_attributes[attribute.to_sym][:operation] = :encrypting - encrypted_attributes[attribute.to_sym][:value_present] = self.class.not_empty?(value) + attr_encrypted_encrypted_attributes[attribute.to_sym][:operation] = :encrypting + attr_encrypted_encrypted_attributes[attribute.to_sym][:value_present] = self.class.not_empty?(value) self.class.encrypt(attribute, value, evaluated_attr_encrypted_options_for(attribute)) end # Copies the class level hash of encrypted attributes with virtual attribute names as keys # and their corresponding options as values to the instance # - def encrypted_attributes - @encrypted_attributes ||= begin + def attr_encrypted_encrypted_attributes + @attr_encrypted_encrypted_attributes ||= begin duplicated= {} - self.class.encrypted_attributes.map { |key, value| duplicated[key] = value.dup } + self.class.attr_encrypted_encrypted_attributes.map { |key, value| duplicated[key] = value.dup } duplicated end end @@ -368,7 +368,7 @@ def encrypted_attributes # Returns attr_encrypted options evaluated in the current object's scope for the attribute specified def evaluated_attr_encrypted_options_for(attribute) evaluated_options = Hash.new - attributes = encrypted_attributes[attribute.to_sym] + attributes = attr_encrypted_encrypted_attributes[attribute.to_sym] attribute_option_value = attributes[:attribute] [:if, :unless, :value_present, :allow_empty_value].each do |option| diff --git a/lib/attr_encrypted/adapters/active_record.rb b/lib/attr_encrypted/adapters/active_record.rb index fca9343e..21ba7b98 100644 --- a/lib/attr_encrypted/adapters/active_record.rb +++ b/lib/attr_encrypted/adapters/active_record.rb @@ -11,7 +11,7 @@ def self.extended(base) # :nodoc: alias_method :reload_without_attr_encrypted, :reload def reload(*args, &block) result = reload_without_attr_encrypted(*args, &block) - self.class.encrypted_attributes.keys.each do |attribute_name| + self.class.attr_encrypted_encrypted_attributes.keys.each do |attribute_name| instance_variable_set("@#{attribute_name}", nil) end result @@ -27,8 +27,8 @@ class << self def perform_attribute_assignment(method, new_attributes, *args) return if new_attributes.blank? - send method, new_attributes.reject { |k, _| self.class.encrypted_attributes.key?(k.to_sym) }, *args - send method, new_attributes.reject { |k, _| !self.class.encrypted_attributes.key?(k.to_sym) }, *args + send method, new_attributes.reject { |k, _| self.class.attr_encrypted_encrypted_attributes.key?(k.to_sym) }, *args + send method, new_attributes.reject { |k, _| !self.class.attr_encrypted_encrypted_attributes.key?(k.to_sym) }, *args end private :perform_attribute_assignment @@ -54,7 +54,7 @@ def attr_encrypted(*attrs) options = attrs.extract_options! attr = attrs.pop attribute attr if ::ActiveRecord::VERSION::STRING >= "5.1.0" - options.merge! encrypted_attributes[attr] + options.merge! attr_encrypted_encrypted_attributes[attr] define_method("#{attr}_was") do attribute_was(attr) @@ -122,10 +122,10 @@ def method_missing_with_attr_encrypted(method, *args, &block) if match = /^(find|scoped)_(all_by|by)_([_a-zA-Z]\w*)$/.match(method.to_s) attribute_names = match.captures.last.split('_and_') attribute_names.each_with_index do |attribute, index| - if attr_encrypted?(attribute) && encrypted_attributes[attribute.to_sym][:mode] == :single_iv_and_salt + if attr_encrypted?(attribute) && attr_encrypted_encrypted_attributes[attribute.to_sym][:mode] == :single_iv_and_salt args[index] = send("encrypt_#{attribute}", args[index]) warn "DEPRECATION WARNING: This feature will be removed in the next major release." - attribute_names[index] = encrypted_attributes[attribute.to_sym][:attribute] + attribute_names[index] = attr_encrypted_encrypted_attributes[attribute.to_sym][:attribute] end end method = "#{match.captures[0]}_#{match.captures[1]}_#{attribute_names.join('_and_')}".to_sym diff --git a/test/active_record_test.rb b/test/active_record_test.rb index 8ec31aea..3a3faff6 100644 --- a/test/active_record_test.rb +++ b/test/active_record_test.rb @@ -85,7 +85,7 @@ class Account < ActiveRecord::Base attr_encrypted :password, key: :password_encryption_key def encrypting?(attr) - encrypted_attributes[attr][:operation] == :encrypting + attr_encrypted_encrypted_attributes[attr][:operation] == :encrypting end def password_encryption_key @@ -279,14 +279,14 @@ def test_should_allow_proc_based_mode @person = PersonWithProcMode.create(email: 'test@example.com', credentials: 'password123') # Email is :per_attribute_iv_and_salt - assert_equal @person.class.encrypted_attributes[:email][:mode].class, Proc - assert_equal @person.class.encrypted_attributes[:email][:mode].call, :per_attribute_iv_and_salt + assert_equal @person.class.attr_encrypted_encrypted_attributes[:email][:mode].class, Proc + assert_equal @person.class.attr_encrypted_encrypted_attributes[:email][:mode].call, :per_attribute_iv_and_salt refute_nil @person.encrypted_email_salt refute_nil @person.encrypted_email_iv # Credentials is :single_iv_and_salt - assert_equal @person.class.encrypted_attributes[:credentials][:mode].class, Proc - assert_equal @person.class.encrypted_attributes[:credentials][:mode].call, :single_iv_and_salt + assert_equal @person.class.attr_encrypted_encrypted_attributes[:credentials][:mode].class, Proc + assert_equal @person.class.attr_encrypted_encrypted_attributes[:credentials][:mode].call, :single_iv_and_salt assert_nil @person.encrypted_credentials_salt assert_nil @person.encrypted_credentials_iv end diff --git a/test/attr_encrypted_test.rb b/test/attr_encrypted_test.rb index 84cb130a..320c8c29 100644 --- a/test/attr_encrypted_test.rb +++ b/test/attr_encrypted_test.rb @@ -83,11 +83,11 @@ def setup end def test_should_store_email_in_encrypted_attributes - assert User.encrypted_attributes.include?(:email) + assert User.attr_encrypted_encrypted_attributes.include?(:email) end def test_should_not_store_salt_in_encrypted_attributes - refute User.encrypted_attributes.include?(:salt) + refute User.attr_encrypted_encrypted_attributes.include?(:salt) end def test_attr_encrypted_should_return_true_for_email @@ -95,7 +95,7 @@ def test_attr_encrypted_should_return_true_for_email end def test_attr_encrypted_should_not_use_the_same_attribute_name_for_two_attributes_in_the_same_line - refute_equal User.encrypted_attributes[:email][:attribute], User.encrypted_attributes[:without_encoding][:attribute] + refute_equal User.attr_encrypted_encrypted_attributes[:email][:attribute], User.attr_encrypted_encrypted_attributes[:without_encoding][:attribute] end def test_attr_encrypted_should_return_false_for_salt @@ -154,7 +154,7 @@ def test_should_decrypt_email def test_should_decrypt_email_when_reading @user = User.new assert_nil @user.email - options = @user.encrypted_attributes[:email] + options = @user.attr_encrypted_encrypted_attributes[:email] iv = @user.send(:generate_iv, options[:algorithm]) encoded_iv = [iv].pack(options[:encode_iv]) salt = SecureRandom.random_bytes @@ -223,7 +223,7 @@ def test_should_use_options_found_in_the_attr_encrypted_options_attribute end def test_should_inherit_encrypted_attributes - assert_equal [User.encrypted_attributes.keys, :testing].flatten.collect { |key| key.to_s }.sort, Admin.encrypted_attributes.keys.collect { |key| key.to_s }.sort + assert_equal [User.attr_encrypted_encrypted_attributes.keys, :testing].flatten.collect { |key| key.to_s }.sort, Admin.attr_encrypted_encrypted_attributes.keys.collect { |key| key.to_s }.sort end def test_should_inherit_attr_encrypted_options @@ -233,7 +233,7 @@ def test_should_inherit_attr_encrypted_options def test_should_not_inherit_unrelated_attributes assert SomeOtherClass.attr_encrypted_options.empty? - assert SomeOtherClass.encrypted_attributes.empty? + assert SomeOtherClass.attr_encrypted_encrypted_attributes.empty? end def test_should_evaluate_a_symbol_option @@ -304,7 +304,7 @@ def test_should_encrypt_empty_with_truthy_allow_empty_value_option end def test_should_work_with_aliased_attr_encryptor - assert User.encrypted_attributes.include?(:aliased) + assert User.attr_encrypted_encrypted_attributes.include?(:aliased) end def test_should_always_reset_options @@ -385,8 +385,8 @@ def test_should_decrypt_second_record end def test_should_specify_the_default_algorithm - assert YetAnotherClass.encrypted_attributes[:email][:algorithm] - assert_equal YetAnotherClass.encrypted_attributes[:email][:algorithm], 'aes-256-gcm' + assert YetAnotherClass.attr_encrypted_encrypted_attributes[:email][:algorithm] + assert_equal YetAnotherClass.attr_encrypted_encrypted_attributes[:email][:algorithm], 'aes-256-gcm' end def test_should_not_encode_iv_when_encode_iv_is_false @@ -475,8 +475,8 @@ def test_encrypted_attributes_state_is_not_shared another_user = User.new - assert_equal :encrypting, user.encrypted_attributes[:ssn][:operation] - assert_nil another_user.encrypted_attributes[:ssn][:operation] + assert_equal :encrypting, user.attr_encrypted_encrypted_attributes[:ssn][:operation] + assert_nil another_user.attr_encrypted_encrypted_attributes[:ssn][:operation] end def test_should_not_by_default_generate_key_when_attribute_is_empty diff --git a/test/legacy_attr_encrypted_test.rb b/test/legacy_attr_encrypted_test.rb index 875086d2..49cbec9b 100644 --- a/test/legacy_attr_encrypted_test.rb +++ b/test/legacy_attr_encrypted_test.rb @@ -58,11 +58,11 @@ def self.call(object) class LegacyAttrEncryptedTest < Minitest::Test def test_should_store_email_in_encrypted_attributes - assert LegacyUser.encrypted_attributes.include?(:email) + assert LegacyUser.attr_encrypted_encrypted_attributes.include?(:email) end def test_should_not_store_salt_in_encrypted_attributes - assert !LegacyUser.encrypted_attributes.include?(:salt) + assert !LegacyUser.attr_encrypted_encrypted_attributes.include?(:salt) end def test_attr_encrypted_should_return_true_for_email @@ -70,7 +70,7 @@ def test_attr_encrypted_should_return_true_for_email end def test_attr_encrypted_should_not_use_the_same_attribute_name_for_two_attributes_in_the_same_line - refute_equal LegacyUser.encrypted_attributes[:email][:attribute], LegacyUser.encrypted_attributes[:without_encoding][:attribute] + refute_equal LegacyUser.attr_encrypted_encrypted_attributes[:email][:attribute], LegacyUser.attr_encrypted_encrypted_attributes[:without_encoding][:attribute] end def test_attr_encrypted_should_return_false_for_salt @@ -201,7 +201,7 @@ def test_should_use_options_found_in_the_attr_encrypted_options_attribute end def test_should_inherit_encrypted_attributes - assert_equal [LegacyUser.encrypted_attributes.keys, :testing].flatten.collect { |key| key.to_s }.sort, LegacyAdmin.encrypted_attributes.keys.collect { |key| key.to_s }.sort + assert_equal [LegacyUser.attr_encrypted_encrypted_attributes.keys, :testing].flatten.collect { |key| key.to_s }.sort, LegacyAdmin.attr_encrypted_encrypted_attributes.keys.collect { |key| key.to_s }.sort end def test_should_inherit_attr_encrypted_options @@ -211,7 +211,7 @@ def test_should_inherit_attr_encrypted_options def test_should_not_inherit_unrelated_attributes assert LegacySomeOtherClass.attr_encrypted_options.empty? - assert LegacySomeOtherClass.encrypted_attributes.empty? + assert LegacySomeOtherClass.attr_encrypted_encrypted_attributes.empty? end def test_should_evaluate_a_symbol_option @@ -268,7 +268,7 @@ def test_should_not_encrypt_with_true_unless end def test_should_work_with_aliased_attr_encryptor - assert LegacyUser.encrypted_attributes.include?(:aliased) + assert LegacyUser.attr_encrypted_encrypted_attributes.include?(:aliased) end def test_should_always_reset_options From 515cf7155b757caa5bbfc0334a92f726cd595f6b Mon Sep 17 00:00:00 2001 From: Austin Miller Date: Mon, 12 Apr 2021 15:32:58 -0500 Subject: [PATCH 2/4] Prefix encrypt and decrypt methods with attr_encrypted Prefix encrypt and decrypt methods with attr_encrypted so we don't clash with rails 7 --- lib/attr_encrypted.rb | 20 ++++++++++---------- test/attr_encrypted_test.rb | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/attr_encrypted.rb b/lib/attr_encrypted.rb index 44f3781f..819c29d5 100644 --- a/lib/attr_encrypted.rb +++ b/lib/attr_encrypted.rb @@ -164,7 +164,7 @@ def attr_encrypted(*attributes) end define_method("#{attribute}=") do |value| - send("#{encrypted_attribute_name}=", encrypt(attribute, value)) + send("#{encrypted_attribute_name}=", attr_encrypted_encrypt(attribute, value)) instance_variable_set("@#{attribute}", value) end @@ -234,8 +234,8 @@ def attr_encrypted?(attribute) # attr_encrypted :email # end # - # email = User.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING') - def decrypt(attribute, encrypted_value, options = {}) + # email = User.attr_encrypted_decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING') + def attr_encrypted_decrypt(attribute, encrypted_value, options = {}) options = attr_encrypted_encrypted_attributes[attribute.to_sym].merge(options) if options[:if] && !options[:unless] && not_empty?(encrypted_value) encrypted_value = encrypted_value.unpack(options[:encode]).first if options[:encode] @@ -260,8 +260,8 @@ def decrypt(attribute, encrypted_value, options = {}) # attr_encrypted :email # end # - # encrypted_email = User.encrypt(:email, 'test@example.com') - def encrypt(attribute, value, options = {}) + # encrypted_email = User.attr_encrypted_encrypt(:email, 'test@example.com') + def attr_encrypted_encrypt(attribute, value, options = {}) options = attr_encrypted_encrypted_attributes[attribute.to_sym].merge(options) if options[:if] && !options[:unless] && (options[:allow_empty_value] || not_empty?(value)) value = options[:marshal] ? options[:marshaler].send(options[:dump_method], value) : value.to_s @@ -325,10 +325,10 @@ module InstanceMethods # # @user = User.new('some-secret-key') # @user.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING') - def decrypt(attribute, encrypted_value) + def attr_encrypted_decrypt(attribute, encrypted_value) attr_encrypted_encrypted_attributes[attribute.to_sym][:operation] = :decrypting attr_encrypted_encrypted_attributes[attribute.to_sym][:value_present] = self.class.not_empty?(encrypted_value) - self.class.decrypt(attribute, encrypted_value, evaluated_attr_encrypted_options_for(attribute)) + self.class.attr_encrypted_decrypt(attribute, encrypted_value, evaluated_attr_encrypted_options_for(attribute)) end # Encrypts a value for the attribute specified using options evaluated in the current object's scope @@ -345,11 +345,11 @@ def decrypt(attribute, encrypted_value) # end # # @user = User.new('some-secret-key') - # @user.encrypt(:email, 'test@example.com') - def encrypt(attribute, value) + # @user.attr_encrypted_encrypt(:email, 'test@example.com') + def attr_encrypted_encrypt(attribute, value) attr_encrypted_encrypted_attributes[attribute.to_sym][:operation] = :encrypting attr_encrypted_encrypted_attributes[attribute.to_sym][:value_present] = self.class.not_empty?(value) - self.class.encrypt(attribute, value, evaluated_attr_encrypted_options_for(attribute)) + self.class.attr_encrypted_encrypt(attribute, value, evaluated_attr_encrypted_options_for(attribute)) end # Copies the class level hash of encrypted attributes with virtual attribute names as keys diff --git a/test/attr_encrypted_test.rb b/test/attr_encrypted_test.rb index 320c8c29..0df966a4 100644 --- a/test/attr_encrypted_test.rb +++ b/test/attr_encrypted_test.rb @@ -381,7 +381,7 @@ def test_should_decrypt_second_record @user2 = User.new @user2.email = 'test@example.com' - assert_equal 'test@example.com', @user1.decrypt(:email, @user1.encrypted_email) + assert_equal 'test@example.com', @user1.attr_encrypted_decrypt(:email, @user1.encrypted_email) end def test_should_specify_the_default_algorithm From eafd77a11f69f92c2e0de7ddb60f98630a8c9f2f Mon Sep 17 00:00:00 2001 From: Austin Miller Date: Mon, 12 Apr 2021 16:47:37 -0500 Subject: [PATCH 3/4] Adds prefix to decrypt - Missed one --- lib/attr_encrypted.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/attr_encrypted.rb b/lib/attr_encrypted.rb index 819c29d5..408e1df0 100644 --- a/lib/attr_encrypted.rb +++ b/lib/attr_encrypted.rb @@ -160,7 +160,7 @@ def attr_encrypted(*attributes) end define_method(attribute) do - instance_variable_get("@#{attribute}") || instance_variable_set("@#{attribute}", decrypt(attribute, send(encrypted_attribute_name))) + instance_variable_get("@#{attribute}") || instance_variable_set("@#{attribute}", attr_encrypted_decrypt(attribute, send(encrypted_attribute_name))) end define_method("#{attribute}=") do |value| From 1e33cab79c08fd9be1b41145c2a9cffebfdb5332 Mon Sep 17 00:00:00 2001 From: Kim Yu Ng Date: Sun, 24 Apr 2022 23:40:07 -0500 Subject: [PATCH 4/4] Fix ruby 2.7 deprecations or unblock ruby 3 --- lib/attr_encrypted/adapters/active_record.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/attr_encrypted/adapters/active_record.rb b/lib/attr_encrypted/adapters/active_record.rb index 21ba7b98..7b7dfa4e 100644 --- a/lib/attr_encrypted/adapters/active_record.rb +++ b/lib/attr_encrypted/adapters/active_record.rb @@ -62,7 +62,7 @@ def attr_encrypted(*attrs) if ::ActiveRecord::VERSION::STRING >= "4.1" define_method("#{attr}_changed?") do |options = {}| - attribute_changed?(attr, options) + attribute_changed?(attr, **options) end else define_method("#{attr}_changed?") do