module AttrEncrypted
Adds attr_accessors that encrypt and decrypt an object's attributes
Public Instance Methods
attr_encrypted(*attributes)
click to toggle source
encode_salt: Defaults to true. default_encoding: Defaults to 'm' (base64). marshal: If set to true, attributes will be marshaled as well as encrypted. This is useful if you're planning on encrypting something other than a string. Defaults to false. marshaler: The object to use for marshaling. Defaults to Marshal. dump_method: The dump method name to call on the <tt>:marshaler</tt> object to. Defaults to 'dump'. load_method: The load method name to call on the <tt>:marshaler</tt> object. Defaults to 'load'. encryptor: The object to use for encrypting. Defaults to Encryptor. encrypt_method: The encrypt method name to call on the <tt>:encryptor</tt> object. Defaults to 'encrypt'. decrypt_method: The decrypt method name to call on the <tt>:encryptor</tt> object. Defaults to 'decrypt'. if: Attributes are only encrypted if this option evaluates to true. If you pass a symbol representing an instance method then the result of the method will be evaluated. Any objects that respond to <tt>:call</tt> are evaluated as well. Defaults to true. unless: Attributes are only encrypted if this option evaluates to false. If you pass a symbol representing an instance method then the result of the method will be evaluated. Any objects that respond to <tt>:call</tt> are evaluated as well. Defaults to false. mode: Selects encryption mode for attribute: choose <tt>:single_iv_and_salt</tt> for compatibility with the old attr_encrypted API: the IV is derived from the encryption key by the underlying Encryptor class; salt is not used. The <tt>:per_attribute_iv_and_salt</tt> mode uses a per-attribute IV and salt. The salt is used to derive a unique key per attribute. A <tt>:per_attribute_iv</default> mode derives a unique IV per attribute; salt is not used. Defaults to <tt>:per_attribute_iv</tt>. allow_empty_value: Attributes which have nil or empty string values will not be encrypted unless this option has a truthy value.
You can specify your own default options
class User # Now all attributes will be encoded and marshaled by default attr_encrypted_options.merge!(encode: true, marshal: true, some_other_option: true) attr_encrypted :configuration, key: 'my secret key' end
Example
class User attr_encrypted :email, key: 'some secret key' attr_encrypted :configuration, key: 'some other secret key', marshal: true end @user = User.new @user.encrypted_email # nil @user.email? # false @user.email = 'test@example.com' @user.email? # true @user.encrypted_email # returns the encrypted version of 'test@example.com' @user.configuration = { time_zone: 'UTC' } @user.encrypted_configuration # returns the encrypted version of configuration See README for more examples
# File lib/attr_encrypted.rb 134 def attr_encrypted(*attributes) 135 options = attributes.last.is_a?(Hash) ? attributes.pop : {} 136 options = attr_encrypted_default_options.dup.merge!(attr_encrypted_options).merge!(options) 137 138 options[:encode] = options[:default_encoding] if options[:encode] == true 139 options[:encode_iv] = options[:default_encoding] if options[:encode_iv] == true 140 options[:encode_salt] = options[:default_encoding] if options[:encode_salt] == true 141 142 attributes.each do |attribute| 143 encrypted_attribute_name = (options[:attribute] ? options[:attribute] : [options[:prefix], attribute, options[:suffix]].join).to_sym 144 145 instance_methods_as_symbols = attribute_instance_methods_as_symbols 146 147 if attribute_instance_methods_as_symbols_available? 148 attr_reader encrypted_attribute_name unless instance_methods_as_symbols.include?(encrypted_attribute_name) 149 attr_writer encrypted_attribute_name unless instance_methods_as_symbols.include?(:"#{encrypted_attribute_name}=") 150 151 iv_name = "#{encrypted_attribute_name}_iv".to_sym 152 attr_reader iv_name unless instance_methods_as_symbols.include?(iv_name) 153 attr_writer iv_name unless instance_methods_as_symbols.include?(:"#{iv_name}=") 154 155 salt_name = "#{encrypted_attribute_name}_salt".to_sym 156 attr_reader salt_name unless instance_methods_as_symbols.include?(salt_name) 157 attr_writer salt_name unless instance_methods_as_symbols.include?(:"#{salt_name}=") 158 end 159 160 define_method(attribute) do 161 instance_variable_get("@#{attribute}") || instance_variable_set("@#{attribute}", decrypt(attribute, send(encrypted_attribute_name))) 162 end 163 164 define_method("#{attribute}=") do |value| 165 send("#{encrypted_attribute_name}=", encrypt(attribute, value)) 166 instance_variable_set("@#{attribute}", value) 167 end 168 169 define_method("#{attribute}?") do 170 value = send(attribute) 171 value.respond_to?(:empty?) ? !value.empty? : !!value 172 end 173 174 encrypted_attributes[attribute.to_sym] = options.merge(attribute: encrypted_attribute_name) 175 end 176 end
Also aliased as: attr_encryptor
attr_encrypted?(attribute)
click to toggle source
Checks if an attribute is configured with attr_encrypted
Example
class User attr_accessor :name attr_encrypted :email end User.attr_encrypted?(:name) # false User.attr_encrypted?(:email) # true
# File lib/attr_encrypted.rb 223 def attr_encrypted?(attribute) 224 encrypted_attributes.has_key?(attribute.to_sym) 225 end
attr_encrypted_options()
click to toggle source
Default options to use with calls to attr_encrypted
It will inherit existing options from its superclass
# File lib/attr_encrypted.rb 183 def attr_encrypted_options 184 @attr_encrypted_options ||= superclass.attr_encrypted_options.dup 185 end
decrypt(attribute, encrypted_value, options = {})
click to toggle source
Decrypts a value for the attribute specified
Example
class User attr_encrypted :email end email = User.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
# File lib/attr_encrypted.rb 236 def decrypt(attribute, encrypted_value, options = {}) 237 options = encrypted_attributes[attribute.to_sym].merge(options) 238 if options[:if] && !options[:unless] && not_empty?(encrypted_value) 239 encrypted_value = encrypted_value.unpack(options[:encode]).first if options[:encode] 240 value = options[:encryptor].send(options[:decrypt_method], options.merge!(value: encrypted_value)) 241 if options[:marshal] 242 value = options[:marshaler].send(options[:load_method], value) 243 elsif defined?(Encoding) 244 encoding = Encoding.default_internal || Encoding.default_external 245 value = value.force_encoding(encoding.name) 246 end 247 value 248 else 249 encrypted_value 250 end 251 end
encrypt(attribute, value, options = {})
click to toggle source
Encrypts a value for the attribute specified
Example
class User attr_encrypted :email end encrypted_email = User.encrypt(:email, 'test@example.com')
# File lib/attr_encrypted.rb 262 def encrypt(attribute, value, options = {}) 263 options = encrypted_attributes[attribute.to_sym].merge(options) 264 if options[:if] && !options[:unless] && (options[:allow_empty_value] || not_empty?(value)) 265 value = options[:marshal] ? options[:marshaler].send(options[:dump_method], value) : value.to_s 266 encrypted_value = options[:encryptor].send(options[:encrypt_method], options.merge!(value: value)) 267 encrypted_value = [encrypted_value].pack(options[:encode]) if options[:encode] 268 encrypted_value 269 else 270 value 271 end 272 end
encrypted_attributes()
click to toggle source
Contains a hash of encrypted attributes with virtual attribute names as keys and their corresponding options as values
Example
class User attr_encrypted :email, key: 'my secret key' end User.encrypted_attributes # { email: { attribute: 'encrypted_email', key: 'my secret key' } }
# File lib/attr_encrypted.rb 288 def encrypted_attributes 289 @encrypted_attributes ||= superclass.encrypted_attributes.dup 290 end
method_missing(method, *arguments, &block)
click to toggle source
Forwards calls to :encrypt_#{attribute} or :decrypt_#{attribute} to the corresponding encrypt or decrypt method if attribute was configured with attr_encrypted
Example
class User attr_encrypted :email, key: 'my secret key' end User.encrypt_email('SOME_ENCRYPTED_EMAIL_STRING')
Calls superclass method
# File lib/attr_encrypted.rb 302 def method_missing(method, *arguments, &block) 303 if method.to_s =~ /^((en|de)crypt)_(.+)$/ && attr_encrypted?($3) 304 send($1, $3, *arguments) 305 else 306 super 307 end 308 end
not_empty?(value)
click to toggle source
# File lib/attr_encrypted.rb 274 def not_empty?(value) 275 !value.nil? && !(value.is_a?(String) && value.empty?) 276 end
Protected Instance Methods
attribute_instance_methods_as_symbols()
click to toggle source
# File lib/attr_encrypted.rb 450 def attribute_instance_methods_as_symbols 451 instance_methods.collect { |method| method.to_sym } 452 end
attribute_instance_methods_as_symbols_available?()
click to toggle source
# File lib/attr_encrypted.rb 454 def attribute_instance_methods_as_symbols_available? 455 true 456 end
Private Instance Methods
attr_encrypted_default_options()
click to toggle source
# File lib/attr_encrypted.rb 187 def attr_encrypted_default_options 188 { 189 prefix: 'encrypted_', 190 suffix: '', 191 if: true, 192 unless: false, 193 encode: false, 194 encode_iv: true, 195 encode_salt: true, 196 default_encoding: 'm', 197 marshal: false, 198 marshaler: Marshal, 199 dump_method: 'dump', 200 load_method: 'load', 201 encryptor: Encryptor, 202 encrypt_method: 'encrypt', 203 decrypt_method: 'decrypt', 204 mode: :per_attribute_iv, 205 algorithm: 'aes-256-gcm', 206 allow_empty_value: false, 207 } 208 end