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

Comparing another known ruby 3/rails 7 fork #2

Closed
wants to merge 5 commits into from
Closed
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
54 changes: 27 additions & 27 deletions lib/attr_encrypted.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -160,11 +160,11 @@ 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|
send("#{encrypted_attribute_name}=", encrypt(attribute, value))
send("#{encrypted_attribute_name}=", attr_encrypted_encrypt(attribute, value))
Comment on lines +163 to +167
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling attr_encrypted(:secure_field) is supposed to create two methods, #secure_field and #secure_field=. This ensures that those methods use the attr_encrypted implementations for decrypt/encrypt.

Otherwise, in rails 7 this would call the rails-versions of #encrypt/#decrypt?

instance_variable_set("@#{attribute}", value)
end

Expand All @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -234,9 +234,9 @@ def attr_encrypted?(attribute)
# attr_encrypted :email
# end
#
# email = User.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
def decrypt(attribute, encrypted_value, options = {})
options = encrypted_attributes[attribute.to_sym].merge(options)
# email = User.attr_encrypted_decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
def attr_encrypted_decrypt(attribute, encrypted_value, options = {})
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renames the .decrypt method (I don't think this or the next one impact Rails 7)

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))
Expand All @@ -260,9 +260,9 @@ def decrypt(attribute, encrypted_value, options = {})
# attr_encrypted :email
# end
#
# encrypted_email = User.encrypt(:email, '[email protected]')
def encrypt(attribute, value, options = {})
options = encrypted_attributes[attribute.to_sym].merge(options)
# encrypted_email = User.attr_encrypted_encrypt(:email, '[email protected]')
def attr_encrypted_encrypt(attribute, value, options = {})
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renames the .encrypt method

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))
Expand All @@ -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
Expand Down Expand Up @@ -325,10 +325,10 @@ 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)
self.class.decrypt(attribute, encrypted_value, evaluated_attr_encrypted_options_for(attribute))
def attr_encrypted_decrypt(attribute, encrypted_value)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename the instance method to not collide with AR 7 #decrypt

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.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
Expand All @@ -345,20 +345,20 @@ def decrypt(attribute, encrypted_value)
# end
#
# @user = User.new('some-secret-key')
# @user.encrypt(:email, '[email protected]')
def encrypt(attribute, value)
encrypted_attributes[attribute.to_sym][:operation] = :encrypting
encrypted_attributes[attribute.to_sym][:value_present] = self.class.not_empty?(value)
self.class.encrypt(attribute, value, evaluated_attr_encrypted_options_for(attribute))
# @user.attr_encrypted_encrypt(:email, '[email protected]')
def attr_encrypted_encrypt(attribute, value)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename the instance method to not collide with AR 7 #encrypt

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.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
# 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
Expand All @@ -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|
Expand Down
14 changes: 7 additions & 7 deletions lib/attr_encrypted/adapters/active_record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -54,15 +54,15 @@ 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)
end

if ::ActiveRecord::VERSION::STRING >= "4.1"
define_method("#{attr}_changed?") do |options = {}|
attribute_changed?(attr, options)
attribute_changed?(attr, **options)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ruby 3 syntax

end
else
define_method("#{attr}_changed?") do
Expand Down Expand Up @@ -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
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
attr_encrypted_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.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
Expand Down
24 changes: 12 additions & 12 deletions test/attr_encrypted_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,19 +83,19 @@ 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
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.attr_encrypted_encrypted_attributes[:email][:attribute], User.attr_encrypted_encrypted_attributes[:without_encoding][:attribute]
end

def test_attr_encrypted_should_return_false_for_salt
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -381,12 +381,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.attr_encrypted_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.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
Expand Down Expand Up @@ -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
Expand Down
12 changes: 6 additions & 6 deletions test/legacy_attr_encrypted_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,19 @@ 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
assert LegacyUser.attr_encrypted?('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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down