Skip to content
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
16 changes: 16 additions & 0 deletions .github/workflows/publish-on-push.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Publish Ruby Gem (Pushed)

on:
push

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build and publish gem
uses: jstastny/publish-gem-to-github@v2.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
owner: acima-credit

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.bundle
.idea
.DS_Store
.ruby-version
pkg
Expand Down
9 changes: 8 additions & 1 deletion attr_encrypted.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ require 'date'

Gem::Specification.new do |s|
s.name = 'attr_encrypted'
s.version = AttrEncrypted::Version.string
current_branch = `git branch --remote --contains | sed "s|[[:space:]]*origin/||"`.strip
branch_commit = `git rev-parse HEAD`.strip[0..6]

if current_branch == 'master'
s.version = AttrEncrypted::Version.string
else
s.version = "#{AttrEncrypted::Version.string}-#{branch_commit}"
end
s.date = Date.today

s.summary = 'Encrypt and decrypt attributes'
Expand Down
48 changes: 24 additions & 24 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_decrypt__(attribute, send(encrypted_attribute_name)))
end

define_method("#{attribute}=") do |value|
send("#{encrypted_attribute_name}=", encrypt(attribute, value))
send("#{encrypted_attribute_name}=", __attr_encrypt__(attribute, value))
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 @@ -235,8 +235,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 __attr_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]
value = options[:encryptor].send(options[:decrypt_method], options.merge!(value: encrypted_value))
Expand All @@ -261,8 +261,8 @@ def decrypt(attribute, encrypted_value, options = {})
# end
#
# encrypted_email = User.encrypt(:email, 'test@example.com')
def encrypt(attribute, value, options = {})
options = encrypted_attributes[attribute.to_sym].merge(options)
def __attr_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
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_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.__attr_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 @@ -346,19 +346,19 @@ 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)
self.class.encrypt(attribute, value, evaluated_attr_encrypted_options_for(attribute))
def __attr_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.__attr_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
49 changes: 32 additions & 17 deletions lib/attr_encrypted/adapters/active_record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
module AttrEncrypted
module Adapters
module ActiveRecord
RAILS_VERSION = Gem::Version.new(::ActiveRecord::VERSION::STRING).freeze

def self.extended(base) # :nodoc:
base.class_eval do

# https://github.com/attr-encrypted/attr_encrypted/issues/68
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,12 +29,12 @@ 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

if ::ActiveRecord::VERSION::STRING > "3.1"
if Gem::Requirement.new('> 3.1').satisfied_by?(RAILS_VERSION)
alias_method :assign_attributes_without_attr_encrypted, :assign_attributes
def assign_attributes(*args)
perform_attribute_assignment :assign_attributes_without_attr_encrypted, *args
Expand All @@ -53,29 +55,42 @@ def attr_encrypted(*attrs)
super
options = attrs.extract_options!
attr = attrs.pop
attribute attr if ::ActiveRecord::VERSION::STRING >= "5.1.0"
options.merge! encrypted_attributes[attr]
attribute 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)
end
else
define_method("#{attr}_changed?") do
attribute_changed?(attr)
end
define_method("#{attr}_changed?") do |options = {}|
attribute_changed?(attr, **options)
end

define_method("#{attr}_change") do
attribute_change(attr)
end

define_method("#{attr}_with_dirtiness=") do |value|
attribute_will_change!(attr) if value != __send__(attr)
current_value = __send__(attr)
value_changed = current_value != value

## Source: https://github.com/priyankatapar/attr_encrypted/commit/7e8702bd5418c927a39d8dd72c0adbea522d5663
# In ActiveRecord 5.2+, due to changes to the way virtual
# attributes are handled, @attributes[attr].value is nil which
# breaks attribute_was. Setting it here returns us to the expected
# behavior.
if RAILS_VERSION >= Gem::Version.new(5.2)
if value_changed
# This is needed support attribute_was before a record has
# been saved
set_attribute_was(attr, current_value) if value_changed && RAILS_VERSION <= Gem::Version.new(6.0)

# This is needed to support attribute_was after a record has
# been saved
@attributes.write_from_user(attr.to_s, value)
end
end
attribute_will_change!(attr) if value != current_value
__send__("#{attr}_without_dirtiness=", value)
end

Expand Down Expand Up @@ -122,10 +137,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
Loading