Module: AttrEncrypted

Defined in:
lib/attr_encrypted.rb,
lib/attr_encrypted/version.rb,
lib/attr_encrypted/adapters/sequel.rb,
lib/attr_encrypted/adapters/data_mapper.rb,
lib/attr_encrypted/adapters/active_record.rb

Overview

Adds attr_accessors that encrypt and decrypt an object’s attributes

Defined Under Namespace

Modules: Adapters, InstanceMethods, Version

Class Method Summary collapse

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *arguments, &block) ⇒ Object

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')


302
303
304
305
306
307
308
# File 'lib/attr_encrypted.rb', line 302

def method_missing(method, *arguments, &block)
  if method.to_s =~ /^((en|de)crypt)_(.+)$/ && attr_encrypted?($3)
    send($1, $3, *arguments)
  else
    super
  end
end

Class Method Details

.extended(base) ⇒ Object

:nodoc:



7
8
9
10
11
12
13
# File 'lib/attr_encrypted.rb', line 7

def self.extended(base) # :nodoc:
  base.class_eval do
    include InstanceMethods
    attr_writer :attr_encrypted_options
    @attr_encrypted_options, @encrypted_attributes = {}, {}
  end
end

Instance Method Details

#attr_encrypted(*attributes) ⇒ Object Also known as: attr_encryptor

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 = '[email protected]'
@user.email? # true
@user.encrypted_email # returns the encrypted version of '[email protected]'

@user.configuration = { time_zone: 'UTC' }
@user.encrypted_configuration # returns the encrypted version of configuration

See README for more examples


134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/attr_encrypted.rb', line 134

def attr_encrypted(*attributes)
  options = attributes.last.is_a?(Hash) ? attributes.pop : {}
  options = attr_encrypted_default_options.dup.merge!(attr_encrypted_options).merge!(options)

  options[:encode] = options[:default_encoding] if options[:encode] == true
  options[:encode_iv] = options[:default_encoding] if options[:encode_iv] == true
  options[:encode_salt] = options[:default_encoding] if options[:encode_salt] == true

  attributes.each do |attribute|
    encrypted_attribute_name = (options[:attribute] ? options[:attribute] : [options[:prefix], attribute, options[:suffix]].join).to_sym

    instance_methods_as_symbols = attribute_instance_methods_as_symbols

    if attribute_instance_methods_as_symbols_available?
      attr_reader encrypted_attribute_name unless instance_methods_as_symbols.include?(encrypted_attribute_name)
      attr_writer encrypted_attribute_name unless instance_methods_as_symbols.include?(:"#{encrypted_attribute_name}=")

      iv_name = "#{encrypted_attribute_name}_iv".to_sym
      attr_reader iv_name unless instance_methods_as_symbols.include?(iv_name)
      attr_writer iv_name unless instance_methods_as_symbols.include?(:"#{iv_name}=")

      salt_name = "#{encrypted_attribute_name}_salt".to_sym
      attr_reader salt_name unless instance_methods_as_symbols.include?(salt_name)
      attr_writer salt_name unless instance_methods_as_symbols.include?(:"#{salt_name}=")
    end

    define_method(attribute) do
      instance_variable_get("@#{attribute}") || instance_variable_set("@#{attribute}", decrypt(attribute, send(encrypted_attribute_name)))
    end

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

    define_method("#{attribute}?") do
      value = send(attribute)
      value.respond_to?(:empty?) ? !value.empty? : !!value
    end

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

#attr_encrypted?(attribute) ⇒ Boolean

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

Returns:

  • (Boolean)


223
224
225
# File 'lib/attr_encrypted.rb', line 223

def attr_encrypted?(attribute)
  encrypted_attributes.has_key?(attribute.to_sym)
end

#attr_encrypted_optionsObject

Default options to use with calls to attr_encrypted

It will inherit existing options from its superclass



183
184
185
# File 'lib/attr_encrypted.rb', line 183

def attr_encrypted_options
  @attr_encrypted_options ||= superclass.attr_encrypted_options.dup
end

#decrypt(attribute, encrypted_value, options = {}) ⇒ Object

Decrypts a value for the attribute specified

Example

class User
  attr_encrypted :email
end

email = User.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')


236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/attr_encrypted.rb', line 236

def decrypt(attribute, encrypted_value, options = {})
  options = 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))
    if options[:marshal]
      value = options[:marshaler].send(options[:load_method], value)
    elsif defined?(Encoding)
      encoding = Encoding.default_internal || Encoding.default_external
      value = value.force_encoding(encoding.name)
    end
    value
  else
    encrypted_value
  end
end

#encrypt(attribute, value, options = {}) ⇒ Object

Encrypts a value for the attribute specified

Example

class User
  attr_encrypted :email
end

encrypted_email = User.encrypt(:email, '[email protected]')


262
263
264
265
266
267
268
269
270
271
272
# File 'lib/attr_encrypted.rb', line 262

def encrypt(attribute, value, options = {})
  options = 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))
    encrypted_value = [encrypted_value].pack(options[:encode]) if options[:encode]
    encrypted_value
  else
    value
  end
end

#encrypted_attributesObject

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' } }


288
289
290
# File 'lib/attr_encrypted.rb', line 288

def encrypted_attributes
  @encrypted_attributes ||= superclass.encrypted_attributes.dup
end

#not_empty?(value) ⇒ Boolean

Returns:

  • (Boolean)


274
275
276
# File 'lib/attr_encrypted.rb', line 274

def not_empty?(value)
  !value.nil? && !(value.is_a?(String) && value.empty?)
end