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


253
254
255
256
257
258
259
# File 'lib/attr_encrypted.rb', line 253

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

Generates attr_accessors that encrypt and decrypt attributes transparently

Options (any other options you specify are passed to the encryptor’s encrypt and decrypt methods)

:attribute        => The name of the referenced encrypted attribute. For example
                     <tt>attr_accessor :email, :attribute => :ee</tt> would generate an
                     attribute named 'ee' to store the encrypted email. This is useful when defining
                     one attribute to encrypt at a time or when the :prefix and :suffix options
                     aren't enough. Defaults to nil.

:prefix           => A prefix used to generate the name of the referenced encrypted attributes.
                     For example <tt>attr_accessor :email, :password, :prefix => 'crypted_'</tt> would
                     generate attributes named 'crypted_email' and 'crypted_password' to store the
                     encrypted email and password. Defaults to 'encrypted_'.

:suffix           => A suffix used to generate the name of the referenced encrypted attributes.
                     For example <tt>attr_accessor :email, :password, :prefix => '', :suffix => '_encrypted'</tt>
                     would generate attributes named 'email_encrypted' and 'password_encrypted' to store the
                     encrypted email. Defaults to ''.

:key              => The encryption key. This option may not be required if you're using a custom encryptor. If you pass
                     a symbol representing an instance method then the :key option will be replaced with the result of the
                     method before being passed to the encryptor. Objects that respond to :call are evaluated as well (including procs).
                     Any other key types will be passed directly to the encryptor.

:encode           => If set to true, attributes will be encoded as well as encrypted. This is useful if you're
                     planning on storing the encrypted attributes in a database. The default encoding is 'm' (base64),
                     however this can be overwritten by setting the :encode option to some other encoding string instead of
                     just 'true'. See http://www.ruby-doc.org/core/classes/Array.html#M002245 for more encoding directives.
                     Defaults to false unless you're using it with ActiveRecord, DataMapper, or Sequel.

: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 unless you're using it with ActiveRecord
                     or DataMapper.

: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.

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, :credit_card, :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


99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/attr_encrypted.rb', line 99

def attr_encrypted(*attributes)
  options = {
    :prefix           => 'encrypted_',
    :suffix           => '',
    :if               => true,
    :unless           => false,
    :encode           => false,
    :default_encoding => 'm',
    :marshal          => false,
    :marshaler        => Marshal,
    :dump_method      => 'dump',
    :load_method      => 'load',
    :encryptor        => Encryptor,
    :encrypt_method   => 'encrypt',
    :decrypt_method   => 'decrypt',
    :key_identifier   => '',
    :keys             => {}
  }
  options.merge!(attr_encrypted_options)
  options.merge!(attributes.extract_options!)

  if options[:key].nil?
    if options[:keys] && options[:keys][options[:key_identifier]]
      options[:key] = options[:keys][options[:key_identifier]]
    end
  end

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

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

    instance_methods_as_symbols = instance_methods.collect { |method| method.to_sym }
    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}=")

    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)


172
173
174
# File 'lib/attr_encrypted.rb', line 172

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



157
158
159
# File 'lib/attr_encrypted.rb', line 157

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


185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/attr_encrypted.rb', line 185

def decrypt(attribute, encrypted_value, options = {})
  options = encrypted_attributes[attribute.to_sym].merge(options)
  if options[:if] && !options[:unless] && !encrypted_value.nil? && !(encrypted_value.is_a?(String) && encrypted_value.empty?)
    if encrypted_value =~ /::/
      identifier, encrypted_value = encrypted_value.split("::", 2)
      key = options[:keys][identifier]
    else
      key = options[:key]
    end
    encrypted_value = encrypted_value.unpack(options[:encode]).first if options[:encode]
    value = options[:encryptor].send(options[:decrypt_method], options.merge!(:value => encrypted_value, :key => key))
    value = options[:marshaler].send(options[:load_method], value) if options[:marshal]
    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]')


212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/attr_encrypted.rb', line 212

def encrypt(attribute, value, options = {})
  options = encrypted_attributes[attribute.to_sym].merge(options)
  if options[:if] && !options[:unless] && !value.nil? && !(value.is_a?(String) && value.empty?)
    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]

    if options[:key_identifier].present?
      encrypted_value.insert(0, options[:key_identifier] + '::')
    end
    
    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' } }


239
240
241
# File 'lib/attr_encrypted.rb', line 239

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