Method: AttrEncryption#attr_encrypted

Defined in:
lib/attr_encryption.rb

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

:type             => The data type of the value to be encrypted/decrypted. Can be 'date', 'datetime', 'binary', 'text' or json.
                     When encrypting, all values will use their string value (value.to_s). When decrypting,
                     the type of the value will determine what is returned. For example:

                       type = 'date': Date.parse(decrypted_value)
                       type = 'time': DateTime.parse(decrypted_value)
                       type = 'binary': decrypted_value
                       type = 'text': decrypted_value.force_encoding('utf-8')
                       type = 'json': JSON.parse(decrypted_value)

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

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

:preencrypt       => The symbol identifying a method that should be run on an attribute immediately prior
                     to marshalling and encrypting. This could be used for things like stripping white-space
                     from values or other sorts of pre-processing. Defaults to nil.

: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. TODO (DJS): We'll see if we need this.

: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. TODO(DJS): We'll see if we need this.

:default_encoding => Defaults to 'm' (base64). TODO(DJS): Hmmm. See above

: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. TODO(DJS): Don't want to use this by default in our encryption since we want to be able to query...

: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. TODO(DJS): Need to changed this to indicate our encryptor

:encrypt_method   => The encrypt method name to call on the <tt>:encryptor</tt> object. Defaults to 'encrypt'. TODO(DJS): Verify this.

:decrypt_method   => The decrypt method name to call on the <tt>:encryptor</tt> object. Defaults to 'decrypt'. TODO(DJS): Verify this.

: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

TODO(DJS): Need to rework the examples.

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


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
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_encryption.rb', line 116

def attr_encrypted(*attributes)
  options = {
    :prefix           => '',
    :suffix           => '_enc',
    :if               => true,
    :unless           => false,
    :encode           => false,
    :key              => $encryption_key,
    :type             => 'text',
    :default_encoding => 'm',
    :preenrypt        => nil,
    :marshal          => false,
    :marshaler        => Marshal,
    :dump_method      => 'dump',
    :load_method      => 'load',
    :encryptor        => MySQLEncryptor.instance,
    :encrypt_method   => 'encrypt',
    :decrypt_method   => 'decrypt'
  }.merge!(attr_encrypted_options).merge!(attributes.last.is_a?(Hash) ? attributes.pop : {})

  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
      cached_value = instance_variable_get("@#{attribute}")
      if cached_value
        case options[:type]
        when 'date'
          value = cached_value.is_a?(Date) ? cached_value : nil
        when 'json'
          value = cached_value.is_a?(Hash) || cached_value.is_a?(Array) ? cached_value : nil
        else
          value = cached_value
        end
      else
        value = nil
      end
      value || instance_variable_set("@#{attribute}", decrypt(attribute, send(encrypted_attribute_name)))
    end

    define_method("#{attribute}=") do |value|
      value_to_encrypt = options[:type] == 'json' ? value.to_json : value
      
      send("#{encrypted_attribute_name}=", encrypt(attribute, value_to_encrypt))
      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