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

[Rails 7] Adapt gem for migration #2

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 22 additions & 22 deletions lib/attr_encrypted.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,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, @legacy_encrypted_attributes = {}, {}
end
end

Expand Down Expand Up @@ -152,11 +152,11 @@ def attr_encrypted(*attributes)
attr_writer salt_name unless instance_methods_as_symbols.include?(:"#{salt_name}=")

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}", legacy_decrypt(attribute, send(encrypted_attribute_name)))
end

define_method("#{attribute}=") do |value|
send("#{encrypted_attribute_name}=", encrypt(attribute, value))
send("#{encrypted_attribute_name}=", legacy_encrypt(attribute, value))
instance_variable_set("@#{attribute}", value)
end

Expand All @@ -165,7 +165,7 @@ def attr_encrypted(*attributes)
value.respond_to?(:empty?) ? !value.empty? : !!value
end

encrypted_attributes[attribute.to_sym] = options.merge(attribute: encrypted_attribute_name)
legacy_encrypted_attributes[attribute.to_sym] = options.merge(attribute: encrypted_attribute_name)
end
end

Expand Down Expand Up @@ -214,7 +214,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)
legacy_encrypted_attributes.has_key?(attribute.to_sym)
end

# Decrypts a value for the attribute specified
Expand All @@ -226,8 +226,8 @@ def attr_encrypted?(attribute)
# end
#
# email = User.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
def decrypt(attribute, encrypted_value, options = {})
options = encrypted_attributes[attribute.to_sym].merge(options)
def legacy_decrypt(attribute, encrypted_value, options = {})
options = legacy_encrypted_attributes[attribute.to_sym].merge(options)
if options[:if] && !options[:unless] && !encrypted_value.nil? && !(encrypted_value.is_a?(String) && encrypted_value.empty?)
encrypted_value = encrypted_value.unpack(options[:encode]).first if options[:encode]
value = options[:encryptor].send(options[:decrypt_method], options.merge!(value: encrypted_value))
Expand All @@ -252,8 +252,8 @@ def decrypt(attribute, encrypted_value, options = {})
# end
#
# encrypted_email = User.encrypt(:email, '[email protected]')
def encrypt(attribute, value, options = {})
options = encrypted_attributes[attribute.to_sym].merge(options)
def legacy_encrypt(attribute, value, options = {})
options = legacy_encrypted_attributes[attribute.to_sym].merge(options)
if options[:if] && !options[:unless] && !value.nil? && !(value.is_a?(String) && value.empty?)
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))
Expand All @@ -274,8 +274,8 @@ def encrypt(attribute, value, options = {})
# end
#
# User.encrypted_attributes # { email: { attribute: 'encrypted_email', key: 'my secret key' } }
def encrypted_attributes
@encrypted_attributes ||= superclass.encrypted_attributes.dup
def legacy_encrypted_attributes
@legacy_encrypted_attributes ||= superclass.legacy_encrypted_attributes.dup
end

# Forwards calls to :encrypt_#{attribute} or :decrypt_#{attribute} to the corresponding encrypt or decrypt method
Expand All @@ -289,7 +289,7 @@ def encrypted_attributes
#
# User.encrypt_email('SOME_ENCRYPTED_EMAIL_STRING')
def method_missing(method, *arguments, &block)
if method.to_s =~ /^((en|de)crypt)_(.+)$/ && attr_encrypted?($3)
if method.to_s =~ /^(legacy_(en|de)crypt)_(.+)$/ && attr_encrypted?($3)
send($1, $3, *arguments)
else
super
Expand All @@ -312,9 +312,9 @@ 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
self.class.decrypt(attribute, encrypted_value, evaluated_attr_encrypted_options_for(attribute))
def legacy_decrypt(attribute, encrypted_value)
legacy_encrypted_attributes[attribute.to_sym][:operation] = :decrypting
self.class.legacy_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
Expand All @@ -332,25 +332,25 @@ def decrypt(attribute, encrypted_value)
#
# @user = User.new('some-secret-key')
# @user.encrypt(:email, '[email protected]')
def encrypt(attribute, value)
encrypted_attributes[attribute.to_sym][:operation] = :encrypting
self.class.encrypt(attribute, value, evaluated_attr_encrypted_options_for(attribute))
def legacy_encrypt(attribute, value)
legacy_encrypted_attributes[attribute.to_sym][:operation] = :encrypting
self.class.legacy_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 ||= self.class.encrypted_attributes.dup
def legacy_encrypted_attributes
@legacy_encrypted_attributes ||= self.class.legacy_encrypted_attributes.dup
end

protected

# 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
attribute_option_value = encrypted_attributes[attribute.to_sym][:attribute]
encrypted_attributes[attribute.to_sym].map do |option, value|
attribute_option_value = legacy_encrypted_attributes[attribute.to_sym][:attribute]
legacy_encrypted_attributes[attribute.to_sym].map do |option, value|
evaluated_options[option] = evaluate_attr_encrypted_option(value)
end

Expand Down
16 changes: 8 additions & 8 deletions lib/attr_encrypted/adapters/active_record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,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.legacy_encrypted_attributes.keys.each do |attribute_name|
instance_variable_set("@#{attribute_name}", nil)
end
result
Expand All @@ -25,8 +25,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.legacy_encrypted_attributes.key?(k.to_sym) }, *args
send method, new_attributes.reject { |k, _| !self.class.legacy_encrypted_attributes.key?(k.to_sym) }, *args
end
private :perform_attribute_assignment

Expand All @@ -51,15 +51,15 @@ def attr_encrypted(*attrs)
super
options = attrs.extract_options!
attr = attrs.pop
options.merge! encrypted_attributes[attr]
options.merge! legacy_encrypted_attributes[attr]

define_method("#{attr}_was") do
attribute_was(attr)
end

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
Expand Down Expand Up @@ -118,10 +118,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
args[index] = send("encrypt_#{attribute}", args[index])
if attr_encrypted?(attribute) && legacy_encrypted_attributes[attribute.to_sym][:mode] == :single_iv_and_salt
args[index] = send("legacy_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] = legacy_encrypted_attributes[attribute.to_sym][:attribute]
end
end
method = "#{match.captures[0]}_#{match.captures[1]}_#{attribute_names.join('_and_')}".to_sym
Expand Down
10 changes: 5 additions & 5 deletions test/active_record_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class Account < ActiveRecord::Base
attr_encrypted :password, key: :password_encryption_key

def encrypting?(attr)
encrypted_attributes[attr][:operation] == :encrypting
legacy_encrypted_attributes[attr][:operation] == :encrypting
end

def password_encryption_key
Expand Down Expand Up @@ -279,14 +279,14 @@ def test_should_allow_proc_based_mode
@person = PersonWithProcMode.create(email: '[email protected]', 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.legacy_encrypted_attributes[:email][:mode].class, Proc
assert_equal @person.class.legacy_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.legacy_encrypted_attributes[:credentials][:mode].class, Proc
assert_equal @person.class.legacy_encrypted_attributes[:credentials][:mode].call, :single_iv_and_salt
assert_nil @person.encrypted_credentials_salt
assert_nil @person.encrypted_credentials_iv
end
Expand Down
62 changes: 31 additions & 31 deletions test/attr_encrypted_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,19 +79,19 @@ def setup
end

def test_should_store_email_in_encrypted_attributes
assert User.encrypted_attributes.include?(:email)
assert User.legacy_encrypted_attributes.include?(:email)
end

def test_should_not_store_salt_in_encrypted_attributes
refute User.encrypted_attributes.include?(:salt)
refute User.legacy_encrypted_attributes.include?(:salt)
end

def test_attr_encrypted_should_return_true_for_email
assert User.attr_encrypted?('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.legacy_encrypted_attributes[:email][:attribute], User.legacy_encrypted_attributes[:without_encoding][:attribute]
end

def test_attr_encrypted_should_return_false_for_salt
Expand All @@ -111,16 +111,16 @@ def test_should_generate_an_encrypted_attribute_with_the_attribute_option
end

def test_should_not_encrypt_nil_value
assert_nil User.encrypt_email(nil, iv: @iv)
assert_nil User.legacy_encrypt_email(nil, iv: @iv)
end

def test_should_not_encrypt_empty_string
assert_equal '', User.encrypt_email('', iv: @iv)
assert_equal '', User.legacy_encrypt_email('', iv: @iv)
end

def test_should_encrypt_email
refute_nil User.encrypt_email('[email protected]', iv: @iv)
refute_equal '[email protected]', User.encrypt_email('[email protected]', iv: @iv)
refute_nil User.legacy_encrypt_email('[email protected]', iv: @iv)
refute_equal '[email protected]', User.legacy_encrypt_email('[email protected]', iv: @iv)
end

def test_should_encrypt_email_when_modifying_the_attr_writer
Expand All @@ -130,50 +130,50 @@ def test_should_encrypt_email_when_modifying_the_attr_writer
refute_nil @user.encrypted_email
iv = @user.encrypted_email_iv.unpack('m').first
salt = @user.encrypted_email_salt[1..-1].unpack('m').first
assert_equal User.encrypt_email('[email protected]', iv: iv, salt: salt), @user.encrypted_email
assert_equal User.legacy_encrypt_email('[email protected]', iv: iv, salt: salt), @user.encrypted_email
end

def test_should_not_decrypt_nil_value
assert_nil User.decrypt_email(nil, iv: @iv)
assert_nil User.legacy_decrypt_email(nil, iv: @iv)
end

def test_should_not_decrypt_empty_string
assert_equal '', User.decrypt_email('', iv: @iv)
assert_equal '', User.legacy_decrypt_email('', iv: @iv)
end

def test_should_decrypt_email
encrypted_email = User.encrypt_email('[email protected]', iv: @iv)
encrypted_email = User.legacy_encrypt_email('[email protected]', iv: @iv)
refute_equal '[email protected]', encrypted_email
assert_equal '[email protected]', User.decrypt_email(encrypted_email, iv: @iv)
assert_equal '[email protected]', User.legacy_decrypt_email(encrypted_email, iv: @iv)
end

def test_should_decrypt_email_when_reading
@user = User.new
assert_nil @user.email
iv = @user.encrypted_email_iv.unpack('m').first
salt = @user.encrypted_email_salt[1..-1].unpack('m').first
@user.encrypted_email = User.encrypt_email('[email protected]', iv: iv, salt: salt)
@user.encrypted_email = User.legacy_encrypt_email('[email protected]', iv: iv, salt: salt)
assert_equal '[email protected]', @user.email
end

def test_should_encrypt_with_encoding
assert_equal User.encrypt_with_encoding('test', iv: @iv), [User.encrypt_without_encoding('test', iv: @iv)].pack('m')
assert_equal User.legacy_encrypt_with_encoding('test', iv: @iv), [User.legacy_encrypt_without_encoding('test', iv: @iv)].pack('m')
end

def test_should_decrypt_with_encoding
encrypted = User.encrypt_with_encoding('test', iv: @iv)
assert_equal 'test', User.decrypt_with_encoding(encrypted, iv: @iv)
assert_equal User.decrypt_with_encoding(encrypted, iv: @iv), User.decrypt_without_encoding(encrypted.unpack('m').first, iv: @iv)
encrypted = User.legacy_encrypt_with_encoding('test', iv: @iv)
assert_equal 'test', User.legacy_decrypt_with_encoding(encrypted, iv: @iv)
assert_equal User.legacy_decrypt_with_encoding(encrypted, iv: @iv), User.legacy_decrypt_without_encoding(encrypted.unpack('m').first, iv: @iv)
end

def test_should_encrypt_with_custom_encoding
assert_equal User.encrypt_with_encoding('test', iv: @iv), [User.encrypt_without_encoding('test', iv: @iv)].pack('m')
assert_equal User.legacy_encrypt_with_encoding('test', iv: @iv), [User.legacy_encrypt_without_encoding('test', iv: @iv)].pack('m')
end

def test_should_decrypt_with_custom_encoding
encrypted = User.encrypt_with_encoding('test', iv: @iv)
assert_equal 'test', User.decrypt_with_encoding(encrypted, iv: @iv)
assert_equal User.decrypt_with_encoding(encrypted, iv: @iv), User.decrypt_without_encoding(encrypted.unpack('m').first, iv: @iv)
encrypted = User.legacy_encrypt_with_encoding('test', iv: @iv)
assert_equal 'test', User.legacy_decrypt_with_encoding(encrypted, iv: @iv)
assert_equal User.legacy_decrypt_with_encoding(encrypted, iv: @iv), User.legacy_decrypt_without_encoding(encrypted.unpack('m').first, iv: @iv)
end

def test_should_encrypt_with_marshaling
Expand All @@ -183,7 +183,7 @@ def test_should_encrypt_with_marshaling
end

def test_should_use_custom_encryptor_and_crypt_method_names_and_arguments
assert_equal SillyEncryptor.silly_encrypt(:value => 'testing', :some_arg => 'test'), User.encrypt_credit_card('testing')
assert_equal SillyEncryptor.silly_encrypt(:value => 'testing', :some_arg => 'test'), User.legacy_encrypt_credit_card('testing')
end

def test_should_evaluate_a_key_passed_as_a_symbol
Expand Down Expand Up @@ -214,7 +214,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.legacy_encrypted_attributes.keys, :testing].flatten.collect { |key| key.to_s }.sort, Admin.legacy_encrypted_attributes.keys.collect { |key| key.to_s }.sort
end

def test_should_inherit_attr_encrypted_options
Expand All @@ -224,7 +224,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.legacy_encrypted_attributes.empty?
end

def test_should_evaluate_a_symbol_option
Expand Down Expand Up @@ -283,7 +283,7 @@ def test_should_not_encrypt_with_true_unless
end

def test_should_work_with_aliased_attr_encryptor
assert User.encrypted_attributes.include?(:aliased)
assert User.legacy_encrypted_attributes.include?(:aliased)
end

def test_should_always_reset_options
Expand All @@ -298,9 +298,9 @@ def test_should_always_reset_options
end

def test_should_cast_values_as_strings_before_encrypting
string_encrypted_email = User.encrypt_email('3', iv: @iv)
assert_equal string_encrypted_email, User.encrypt_email(3, iv: @iv)
assert_equal '3', User.decrypt_email(string_encrypted_email, iv: @iv)
string_encrypted_email = User.legacy_encrypt_email('3', iv: @iv)
assert_equal string_encrypted_email, User.legacy_encrypt_email(3, iv: @iv)
assert_equal '3', User.legacy_decrypt_email(string_encrypted_email, iv: @iv)
end

def test_should_create_query_accessor
Expand Down Expand Up @@ -360,12 +360,12 @@ def test_should_decrypt_second_record
@user2 = User.new
@user2.email = '[email protected]'

assert_equal '[email protected]', @user1.decrypt(:email, @user1.encrypted_email)
assert_equal '[email protected]', @user1.legacy_decrypt(:email, @user1.encrypted_email)
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.legacy_encrypted_attributes[:email][:algorithm]
assert_equal YetAnotherClass.legacy_encrypted_attributes[:email][:algorithm], 'aes-256-gcm'
end

def test_should_not_encode_iv_when_encode_iv_is_false
Expand Down
Loading