|
module Encryptable
|
|
extend ActiveSupport::Concern
|
|
include EncryptionKey if defined?(EncryptionKey)
|
|
# Set encryption key for tests only if case it's not set
|
|
ENCRYPTION_KEY = '25d224dd383e92a7e0c82b8bf7c985e815f34cf5' if Rails.env.test?
|
|
|
|
if const_defined?(:ENCRYPTION_KEY)
|
|
|
|
included do
|
|
ENCRYPTION_PREFIX = "encrypted-"
|
|
before_save :encrypt_setters
|
|
end
|
|
|
|
def encrypt_setters
|
|
self.encryptable_fields.each do |field|
|
|
if send("#{field}_changed?")
|
|
self.send("#{field}=", encrypt_field(read_attribute(field.to_sym)))
|
|
end
|
|
end
|
|
end
|
|
|
|
def matches_prefix?(str)
|
|
ENCRYPTION_PREFIX == str.to_s[0..(ENCRYPTION_PREFIX.length - 1)]
|
|
end
|
|
|
|
def puts_and_logs(msg)
|
|
logger.info msg
|
|
puts msg if defined?(Rake) && !Rails.env.test?
|
|
end
|
|
|
|
def is_encryptable?(str)
|
|
return true if !matches_prefix?(str) && str.present?
|
|
if str.blank?
|
|
puts_and_logs "String is empty', so #{self.class.name} #{name} was not encrypted"
|
|
else
|
|
puts_and_logs "String starts with the prefix '#{ENCRYPTION_PREFIX}', so #{self.class.name} #{name} was not encrypted again"
|
|
end
|
|
false
|
|
end
|
|
|
|
def is_decryptable?(str)
|
|
return true if matches_prefix?(str)
|
|
puts_and_logs "String does not start with the prefix '#{ENCRYPTION_PREFIX}', so #{self.class.name} #{name} was not decrypted"
|
|
false
|
|
end
|
|
|
|
def encrypt_field(str)
|
|
return str.to_s unless is_encryptable?(str)
|
|
encryptor = ActiveSupport::MessageEncryptor.new(ENCRYPTION_KEY)
|
|
begin
|
|
# add prefix to encrypted string
|
|
str_encrypted = "#{ENCRYPTION_PREFIX}#{encryptor.encrypt_and_sign(str)}"
|
|
puts_and_logs "Successfully encrypted field for #{self.class.name} #{name}"
|
|
str = str_encrypted
|
|
rescue
|
|
puts_and_logs "WARNING: Encryption failed for string. Please check that the ENCRYPTION_KEY has not changed."
|
|
end
|
|
str
|
|
end
|
|
|
|
def decrypt_field(str)
|
|
return str unless is_decryptable?(str)
|
|
encryptor = ActiveSupport::MessageEncryptor.new(ENCRYPTION_KEY)
|
|
begin
|
|
# remove prefix before decrypting string
|
|
str_no_prefix = str.gsub(/^#{ENCRYPTION_PREFIX}/, "")
|
|
str_decrypted = encryptor.decrypt_and_verify(str_no_prefix)
|
|
puts_and_logs "Successfully decrypted field for #{self.class.name} #{name}"
|
|
str = str_decrypted
|
|
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
|
puts_and_logs "WARNING: Decryption failed for string. Please check that the ENCRYPTION_KEY has not changed."
|
|
end
|
|
str
|
|
end
|
|
|
|
module ClassMethods
|
|
|
|
def encrypts(*fields)
|
|
class_attribute :encryptable_fields
|
|
self.encryptable_fields = fields.map(&:to_sym)
|
|
define_getter_in_db(fields.map(&:to_sym))
|
|
define_auto_decrypt_getter(fields.map(&:to_sym))
|
|
end
|
|
|
|
def encrypts?(field)
|
|
encryptable_fields.include?(field.to_sym)
|
|
end
|
|
|
|
def define_getter_in_db(fields)
|
|
fields.each do |field|
|
|
define_method "#{field}_in_db" do
|
|
read_attribute(field.to_sym)
|
|
end
|
|
end
|
|
end
|
|
|
|
def define_auto_decrypt_getter(fields)
|
|
fields.each do |field|
|
|
define_method field do
|
|
decrypt_field(send("#{field}_in_db".to_sym))
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
else
|
|
included do
|
|
logger.info "ENCRYPTION_KEY is not defined, so encryption is turned off for #{model_name}."
|
|
end
|
|
|
|
module ClassMethods
|
|
def encrypts(*fields)
|
|
end
|
|
end
|
|
end
|
|
end
|