attr_encrypted 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,90 +16,93 @@ module AttrEncrypted
16
16
  #
17
17
  # Options (any other options you specify are passed to the Encryptor's encrypt and decrypt methods)
18
18
  #
19
- # attribute: The name of the referenced encrypted attribute. For example
20
- # <tt>attr_accessor :email, attribute: :ee</tt> would generate an
21
- # attribute named 'ee' to store the encrypted email. This is useful when defining
22
- # one attribute to encrypt at a time or when the :prefix and :suffix options
23
- # aren't enough.
24
- # Defaults to nil.
25
- #
26
- # prefix: A prefix used to generate the name of the referenced encrypted attributes.
27
- # For example <tt>attr_accessor :email, prefix: 'crypted_'</tt> would
28
- # generate attributes named 'crypted_email' to store the encrypted
29
- # email and password.
30
- # Defaults to 'encrypted_'.
31
- #
32
- # suffix: A suffix used to generate the name of the referenced encrypted attributes.
33
- # For example <tt>attr_accessor :email, prefix: '', suffix: '_encrypted'</tt>
34
- # would generate attributes named 'email_encrypted' to store the
35
- # encrypted email.
36
- # Defaults to ''.
37
- #
38
- # key: The encryption key. This option may not be required if
39
- # you're using a custom encryptor. If you pass a symbol
40
- # representing an instance method then the :key option
41
- # will be replaced with the result of the method before
42
- # being passed to the encryptor. Objects that respond
43
- # to :call are evaluated as well (including procs).
44
- # Any other key types will be passed directly to the encryptor.
45
- # Defaults to nil.
46
- #
47
- # encode: If set to true, attributes will be encoded as well as
48
- # encrypted. This is useful if you're planning on storing
49
- # the encrypted attributes in a database. The default
50
- # encoding is 'm' (base64), however this can be overwritten
51
- # by setting the :encode option to some other encoding
52
- # string instead of just 'true'. See
53
- # http://www.ruby-doc.org/core/classes/Array.html#M002245
54
- # for more encoding directives.
55
- # Defaults to false unless you're using it with ActiveRecord, DataMapper, or Sequel.
56
- #
57
- # encode_iv: Defaults to true.
58
-
59
- # encode_salt: Defaults to true.
60
- #
61
- # default_encoding: Defaults to 'm' (base64).
62
- #
63
- # marshal: If set to true, attributes will be marshaled as well
64
- # as encrypted. This is useful if you're planning on
65
- # encrypting something other than a string.
66
- # Defaults to false.
67
- #
68
- # marshaler: The object to use for marshaling.
69
- # Defaults to Marshal.
70
- #
71
- # dump_method: The dump method name to call on the <tt>:marshaler</tt> object to.
72
- # Defaults to 'dump'.
73
- #
74
- # load_method: The load method name to call on the <tt>:marshaler</tt> object.
75
- # Defaults to 'load'.
76
- #
77
- # encryptor: The object to use for encrypting.
78
- # Defaults to Encryptor.
79
- #
80
- # encrypt_method: The encrypt method name to call on the <tt>:encryptor</tt> object.
81
- # Defaults to 'encrypt'.
82
- #
83
- # decrypt_method: The decrypt method name to call on the <tt>:encryptor</tt> object.
84
- # Defaults to 'decrypt'.
85
- #
86
- # if: Attributes are only encrypted if this option evaluates
87
- # to true. If you pass a symbol representing an instance
88
- # method then the result of the method will be evaluated.
89
- # Any objects that respond to <tt>:call</tt> are evaluated as well.
90
- # Defaults to true.
91
- #
92
- # unless: Attributes are only encrypted if this option evaluates
93
- # to false. If you pass a symbol representing an instance
94
- # method then the result of the method will be evaluated.
95
- # Any objects that respond to <tt>:call</tt> are evaluated as well.
96
- # Defaults to false.
97
- #
98
- # mode: Selects encryption mode for attribute: choose <tt>:single_iv_and_salt</tt> for compatibility
99
- # with the old attr_encrypted API: the IV is derived from the encryption key by the underlying Encryptor class; salt is not used.
100
- # 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.
101
- # A <tt>:per_attribute_iv</default> mode derives a unique IV per attribute; salt is not used.
102
- # Defaults to <tt>:per_attribute_iv</tt>.
19
+ # attribute: The name of the referenced encrypted attribute. For example
20
+ # <tt>attr_accessor :email, attribute: :ee</tt> would generate an
21
+ # attribute named 'ee' to store the encrypted email. This is useful when defining
22
+ # one attribute to encrypt at a time or when the :prefix and :suffix options
23
+ # aren't enough.
24
+ # Defaults to nil.
25
+ #
26
+ # prefix: A prefix used to generate the name of the referenced encrypted attributes.
27
+ # For example <tt>attr_accessor :email, prefix: 'crypted_'</tt> would
28
+ # generate attributes named 'crypted_email' to store the encrypted
29
+ # email and password.
30
+ # Defaults to 'encrypted_'.
31
+ #
32
+ # suffix: A suffix used to generate the name of the referenced encrypted attributes.
33
+ # For example <tt>attr_accessor :email, prefix: '', suffix: '_encrypted'</tt>
34
+ # would generate attributes named 'email_encrypted' to store the
35
+ # encrypted email.
36
+ # Defaults to ''.
37
+ #
38
+ # key: The encryption key. This option may not be required if
39
+ # you're using a custom encryptor. If you pass a symbol
40
+ # representing an instance method then the :key option
41
+ # will be replaced with the result of the method before
42
+ # being passed to the encryptor. Objects that respond
43
+ # to :call are evaluated as well (including procs).
44
+ # Any other key types will be passed directly to the encryptor.
45
+ # Defaults to nil.
46
+ #
47
+ # encode: If set to true, attributes will be encoded as well as
48
+ # encrypted. This is useful if you're planning on storing
49
+ # the encrypted attributes in a database. The default
50
+ # encoding is 'm' (base64), however this can be overwritten
51
+ # by setting the :encode option to some other encoding
52
+ # string instead of just 'true'. See
53
+ # http://www.ruby-doc.org/core/classes/Array.html#M002245
54
+ # for more encoding directives.
55
+ # Defaults to false unless you're using it with ActiveRecord, DataMapper, or Sequel.
56
+ #
57
+ # encode_iv: Defaults to true.
58
+
59
+ # encode_salt: Defaults to true.
60
+ #
61
+ # default_encoding: Defaults to 'm' (base64).
62
+ #
63
+ # marshal: If set to true, attributes will be marshaled as well
64
+ # as encrypted. This is useful if you're planning on
65
+ # encrypting something other than a string.
66
+ # Defaults to false.
67
+ #
68
+ # marshaler: The object to use for marshaling.
69
+ # Defaults to Marshal.
70
+ #
71
+ # dump_method: The dump method name to call on the <tt>:marshaler</tt> object to.
72
+ # Defaults to 'dump'.
73
+ #
74
+ # load_method: The load method name to call on the <tt>:marshaler</tt> object.
75
+ # Defaults to 'load'.
76
+ #
77
+ # encryptor: The object to use for encrypting.
78
+ # Defaults to Encryptor.
79
+ #
80
+ # encrypt_method: The encrypt method name to call on the <tt>:encryptor</tt> object.
81
+ # Defaults to 'encrypt'.
82
+ #
83
+ # decrypt_method: The decrypt method name to call on the <tt>:encryptor</tt> object.
84
+ # Defaults to 'decrypt'.
85
+ #
86
+ # if: Attributes are only encrypted if this option evaluates
87
+ # to true. If you pass a symbol representing an instance
88
+ # method then the result of the method will be evaluated.
89
+ # Any objects that respond to <tt>:call</tt> are evaluated as well.
90
+ # Defaults to true.
91
+ #
92
+ # unless: Attributes are only encrypted if this option evaluates
93
+ # to false. If you pass a symbol representing an instance
94
+ # method then the result of the method will be evaluated.
95
+ # Any objects that respond to <tt>:call</tt> are evaluated as well.
96
+ # Defaults to false.
97
+ #
98
+ # mode: Selects encryption mode for attribute: choose <tt>:single_iv_and_salt</tt> for compatibility
99
+ # with the old attr_encrypted API: the IV is derived from the encryption key by the underlying Encryptor class; salt is not used.
100
+ # 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.
101
+ # A <tt>:per_attribute_iv</default> mode derives a unique IV per attribute; salt is not used.
102
+ # Defaults to <tt>:per_attribute_iv</tt>.
103
+ #
104
+ # allow_empty_value: Attributes which have nil or empty string values will not be encrypted unless this option
105
+ # has a truthy value.
103
106
  #
104
107
  # You can specify your own default options
105
108
  #
@@ -140,16 +143,19 @@ module AttrEncrypted
140
143
  encrypted_attribute_name = (options[:attribute] ? options[:attribute] : [options[:prefix], attribute, options[:suffix]].join).to_sym
141
144
 
142
145
  instance_methods_as_symbols = attribute_instance_methods_as_symbols
143
- attr_reader encrypted_attribute_name unless instance_methods_as_symbols.include?(encrypted_attribute_name)
144
- attr_writer encrypted_attribute_name unless instance_methods_as_symbols.include?(:"#{encrypted_attribute_name}=")
145
146
 
146
- iv_name = "#{encrypted_attribute_name}_iv".to_sym
147
- attr_reader iv_name unless instance_methods_as_symbols.include?(iv_name)
148
- attr_writer iv_name unless instance_methods_as_symbols.include?(:"#{iv_name}=")
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}=")
149
150
 
150
- salt_name = "#{encrypted_attribute_name}_salt".to_sym
151
- attr_reader salt_name unless instance_methods_as_symbols.include?(salt_name)
152
- attr_writer salt_name unless instance_methods_as_symbols.include?(:"#{salt_name}=")
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
153
159
 
154
160
  define_method(attribute) do
155
161
  instance_variable_get("@#{attribute}") || instance_variable_set("@#{attribute}", decrypt(attribute, send(encrypted_attribute_name)))
@@ -197,6 +203,7 @@ module AttrEncrypted
197
203
  decrypt_method: 'decrypt',
198
204
  mode: :per_attribute_iv,
199
205
  algorithm: 'aes-256-gcm',
206
+ allow_empty_value: false,
200
207
  }
201
208
  end
202
209
 
@@ -228,7 +235,7 @@ module AttrEncrypted
228
235
  # email = User.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
229
236
  def decrypt(attribute, encrypted_value, options = {})
230
237
  options = encrypted_attributes[attribute.to_sym].merge(options)
231
- if options[:if] && !options[:unless] && !encrypted_value.nil? && !(encrypted_value.is_a?(String) && encrypted_value.empty?)
238
+ if options[:if] && !options[:unless] && not_empty?(encrypted_value)
232
239
  encrypted_value = encrypted_value.unpack(options[:encode]).first if options[:encode]
233
240
  value = options[:encryptor].send(options[:decrypt_method], options.merge!(value: encrypted_value))
234
241
  if options[:marshal]
@@ -254,7 +261,7 @@ module AttrEncrypted
254
261
  # encrypted_email = User.encrypt(:email, 'test@example.com')
255
262
  def encrypt(attribute, value, options = {})
256
263
  options = encrypted_attributes[attribute.to_sym].merge(options)
257
- if options[:if] && !options[:unless] && !value.nil? && !(value.is_a?(String) && value.empty?)
264
+ if options[:if] && !options[:unless] && (options[:allow_empty_value] || not_empty?(value))
258
265
  value = options[:marshal] ? options[:marshaler].send(options[:dump_method], value) : value.to_s
259
266
  encrypted_value = options[:encryptor].send(options[:encrypt_method], options.merge!(value: value))
260
267
  encrypted_value = [encrypted_value].pack(options[:encode]) if options[:encode]
@@ -264,6 +271,10 @@ module AttrEncrypted
264
271
  end
265
272
  end
266
273
 
274
+ def not_empty?(value)
275
+ !value.nil? && !(value.is_a?(String) && value.empty?)
276
+ end
277
+
267
278
  # Contains a hash of encrypted attributes with virtual attribute names as keys
268
279
  # and their corresponding options as values
269
280
  #
@@ -314,6 +325,7 @@ module AttrEncrypted
314
325
  # @user.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
315
326
  def decrypt(attribute, encrypted_value)
316
327
  encrypted_attributes[attribute.to_sym][:operation] = :decrypting
328
+ encrypted_attributes[attribute.to_sym][:value_present] = self.class.not_empty?(encrypted_value)
317
329
  self.class.decrypt(attribute, encrypted_value, evaluated_attr_encrypted_options_for(attribute))
318
330
  end
319
331
 
@@ -334,6 +346,7 @@ module AttrEncrypted
334
346
  # @user.encrypt(:email, 'test@example.com')
335
347
  def encrypt(attribute, value)
336
348
  encrypted_attributes[attribute.to_sym][:operation] = :encrypting
349
+ encrypted_attributes[attribute.to_sym][:value_present] = self.class.not_empty?(value)
337
350
  self.class.encrypt(attribute, value, evaluated_attr_encrypted_options_for(attribute))
338
351
  end
339
352
 
@@ -357,12 +370,14 @@ module AttrEncrypted
357
370
  evaluated_options[:attribute] = attribute_option_value
358
371
 
359
372
  evaluated_options.tap do |options|
360
- unless options[:mode] == :single_iv_and_salt
361
- load_iv_for_attribute(attribute, options)
362
- end
363
-
364
- if options[:mode] == :per_attribute_iv_and_salt
365
- load_salt_for_attribute(attribute, options)
373
+ if options[:if] && !options[:unless] && options[:value_present] || options[:allow_empty_value]
374
+ unless options[:mode] == :single_iv_and_salt
375
+ load_iv_for_attribute(attribute, options)
376
+ end
377
+
378
+ if options[:mode] == :per_attribute_iv_and_salt
379
+ load_salt_for_attribute(attribute, options)
380
+ end
366
381
  end
367
382
  end
368
383
  end
@@ -371,7 +386,7 @@ module AttrEncrypted
371
386
  #
372
387
  # If the option is not a symbol or proc then the original option is returned
373
388
  def evaluate_attr_encrypted_option(option)
374
- if option.is_a?(Symbol) && respond_to?(option)
389
+ if option.is_a?(Symbol) && respond_to?(option, true)
375
390
  send(option)
376
391
  elsif option.respond_to?(:call)
377
392
  option.call(self)
@@ -408,7 +423,7 @@ module AttrEncrypted
408
423
  encrypted_attribute_name = options[:attribute]
409
424
  encode_salt = options[:encode_salt]
410
425
  salt = options[:salt] || send("#{encrypted_attribute_name}_salt")
411
- if (salt == nil)
426
+ if options[:operation] == :encrypting
412
427
  salt = SecureRandom.random_bytes
413
428
  salt = prefix_and_encode_salt(salt, encode_salt) if encode_salt
414
429
  send("#{encrypted_attribute_name}_salt=", salt)
@@ -436,6 +451,10 @@ module AttrEncrypted
436
451
  instance_methods.collect { |method| method.to_sym }
437
452
  end
438
453
 
454
+ def attribute_instance_methods_as_symbols_available?
455
+ true
456
+ end
457
+
439
458
  end
440
459
 
441
460
 
@@ -3,47 +3,44 @@ require_relative 'test_helper'
3
3
  ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
4
4
 
5
5
  def create_tables
6
- silence_stream(STDOUT) do
7
- ActiveRecord::Schema.define(version: 1) do
8
- create_table :people do |t|
9
- t.string :encrypted_email
10
- t.string :password
11
- t.string :encrypted_credentials
12
- t.binary :salt
13
- t.binary :key_iv
14
- t.string :encrypted_email_salt
15
- t.string :encrypted_credentials_salt
16
- t.string :encrypted_email_iv
17
- t.string :encrypted_credentials_iv
18
- end
19
- create_table :accounts do |t|
20
- t.string :encrypted_password
21
- t.string :encrypted_password_iv
22
- t.string :encrypted_password_salt
23
- end
24
- create_table :users do |t|
25
- t.string :login
26
- t.string :encrypted_password
27
- t.string :encrypted_password_iv
28
- t.boolean :is_admin
29
- end
30
- create_table :prime_ministers do |t|
31
- t.string :encrypted_name
32
- t.string :encrypted_name_iv
33
- end
34
- create_table :addresses do |t|
35
- t.binary :encrypted_street
36
- t.binary :encrypted_street_iv
37
- t.binary :encrypted_zipcode
38
- t.string :mode
39
- end
6
+ ActiveRecord::Schema.define(version: 1) do
7
+ self.verbose = false
8
+ create_table :people do |t|
9
+ t.string :encrypted_email
10
+ t.string :password
11
+ t.string :encrypted_credentials
12
+ t.binary :salt
13
+ t.binary :key_iv
14
+ t.string :encrypted_email_salt
15
+ t.string :encrypted_credentials_salt
16
+ t.string :encrypted_email_iv
17
+ t.string :encrypted_credentials_iv
18
+ end
19
+ create_table :accounts do |t|
20
+ t.string :encrypted_password
21
+ t.string :encrypted_password_iv
22
+ t.string :encrypted_password_salt
23
+ t.string :key
24
+ end
25
+ create_table :users do |t|
26
+ t.string :login
27
+ t.string :encrypted_password
28
+ t.string :encrypted_password_iv
29
+ t.boolean :is_admin
30
+ end
31
+ create_table :prime_ministers do |t|
32
+ t.string :encrypted_name
33
+ t.string :encrypted_name_iv
34
+ end
35
+ create_table :addresses do |t|
36
+ t.binary :encrypted_street
37
+ t.binary :encrypted_street_iv
38
+ t.binary :encrypted_zipcode
39
+ t.string :mode
40
40
  end
41
41
  end
42
42
  end
43
43
 
44
- # The table needs to exist before defining the class
45
- create_tables
46
-
47
44
  ActiveRecord::MissingAttributeError = ActiveModel::MissingAttributeError unless defined?(ActiveRecord::MissingAttributeError)
48
45
 
49
46
  if ::ActiveRecord::VERSION::STRING > "4.0"
@@ -82,8 +79,20 @@ class PersonWithProcMode < Person
82
79
  end
83
80
 
84
81
  class Account < ActiveRecord::Base
85
- attr_accessor :key
86
- attr_encrypted :password, key: Proc.new {|account| account.key}
82
+ ACCOUNT_ENCRYPTION_KEY = SecureRandom.urlsafe_base64(24)
83
+ attr_encrypted :password, key: :password_encryption_key
84
+
85
+ def encrypting?(attr)
86
+ encrypted_attributes[attr][:operation] == :encrypting
87
+ end
88
+
89
+ def password_encryption_key
90
+ if encrypting?(:password)
91
+ self.key = ACCOUNT_ENCRYPTION_KEY
92
+ else
93
+ self.key
94
+ end
95
+ end
87
96
  end
88
97
 
89
98
  class PersonWithSerialization < ActiveRecord::Base
@@ -117,9 +126,8 @@ end
117
126
  class ActiveRecordTest < Minitest::Test
118
127
 
119
128
  def setup
120
- ActiveRecord::Base.connection.tables.each { |table| ActiveRecord::Base.connection.drop_table(table) }
129
+ drop_all_tables
121
130
  create_tables
122
- Account.create!(key: SECRET_KEY, password: "password")
123
131
  end
124
132
 
125
133
  def test_should_encrypt_email
@@ -169,12 +177,6 @@ class ActiveRecordTest < Minitest::Test
169
177
  Account.new.attributes = { password: "password", key: SECRET_KEY }
170
178
  end
171
179
 
172
- def test_should_preserve_hash_key_type
173
- hash = { foo: 'bar' }
174
- account = Account.create!(key: hash)
175
- assert_equal account.key, hash
176
- end
177
-
178
180
  def test_should_create_changed_predicate
179
181
  person = Person.create!(email: 'test@example.com')
180
182
  refute person.email_changed?
@@ -192,6 +194,29 @@ class ActiveRecordTest < Minitest::Test
192
194
  assert_equal original_email, person.email_was
193
195
  person.email = 'test2@example.com'
194
196
  assert_equal original_email, person.email_was
197
+ old_pm_name = "Winston Churchill"
198
+ pm = PrimeMinister.create!(name: old_pm_name)
199
+ assert_equal old_pm_name, pm.name_was
200
+ old_zipcode = "90210"
201
+ address = Address.create!(zipcode: old_zipcode, mode: "single_iv_and_salt")
202
+ assert_equal old_zipcode, address.zipcode_was
203
+ end
204
+
205
+ def test_attribute_was_works_when_options_for_old_encrypted_value_are_different_than_options_for_new_encrypted_value
206
+ pw = 'password'
207
+ crypto_key = SecureRandom.urlsafe_base64(24)
208
+ old_iv = SecureRandom.random_bytes(12)
209
+ account = Account.create
210
+ encrypted_value = Encryptor.encrypt(value: pw, iv: old_iv, key: crypto_key)
211
+ Account.where(id: account.id).update_all(key: crypto_key, encrypted_password_iv: [old_iv].pack('m'), encrypted_password: [encrypted_value].pack('m'))
212
+ account = Account.find(account.id)
213
+ assert_equal pw, account.password
214
+ account.password = pw.reverse
215
+ assert_equal pw, account.password_was
216
+ account.save
217
+ account.reload
218
+ assert_equal Account::ACCOUNT_ENCRYPTION_KEY, account.key
219
+ assert_equal pw.reverse, account.password
195
220
  end
196
221
 
197
222
  if ::ActiveRecord::VERSION::STRING > "4.0"
@@ -305,7 +330,9 @@ class ActiveRecordTest < Minitest::Test
305
330
  def test_should_evaluate_proc_based_mode
306
331
  street = '123 Elm'
307
332
  zipcode = '12345'
308
- address = Address.new(street: street, zipcode: zipcode, mode: :single_iv_and_salt)
309
- assert_nil address.encrypted_zipcode_iv
333
+ address = Address.create(street: street, zipcode: zipcode, mode: :single_iv_and_salt)
334
+ address.reload
335
+ refute_equal address.encrypted_zipcode, zipcode
336
+ assert_equal address.zipcode, zipcode
310
337
  end
311
338
  end
@@ -23,11 +23,12 @@ class User
23
23
  attr_encrypted :with_encoding, :key => SECRET_KEY, :encode => true
24
24
  attr_encrypted :with_custom_encoding, :key => SECRET_KEY, :encode => 'm'
25
25
  attr_encrypted :with_marshaling, :key => SECRET_KEY, :marshal => true
26
- attr_encrypted :with_true_if, :key => SECRET_KEY, :if => true
27
- attr_encrypted :with_false_if, :key => SECRET_KEY, :if => false
28
- attr_encrypted :with_true_unless, :key => SECRET_KEY, :unless => true
29
- attr_encrypted :with_false_unless, :key => SECRET_KEY, :unless => false
26
+ attr_encrypted :with_true_if, :key => SECRET_KEY, :if => true, mode: :per_attribute_iv_and_salt
27
+ attr_encrypted :with_false_if, :key => SECRET_KEY, :if => false, mode: :per_attribute_iv_and_salt
28
+ attr_encrypted :with_true_unless, :key => SECRET_KEY, :unless => true, mode: :per_attribute_iv_and_salt
29
+ attr_encrypted :with_false_unless, :key => SECRET_KEY, :unless => false, mode: :per_attribute_iv_and_salt
30
30
  attr_encrypted :with_if_changed, :key => SECRET_KEY, :if => :should_encrypt
31
+ attr_encrypted :with_allow_empty_value, key: SECRET_KEY, allow_empty_value: true, marshal: true
31
32
 
32
33
  attr_encryptor :aliased, :key => SECRET_KEY
33
34
 
@@ -40,6 +41,7 @@ class User
40
41
  self.should_encrypt = true
41
42
  end
42
43
 
44
+ private
43
45
  def secret_key
44
46
  SECRET_KEY
45
47
  end
@@ -114,7 +116,7 @@ class AttrEncryptedTest < Minitest::Test
114
116
  assert_nil User.encrypt_email(nil, iv: @iv)
115
117
  end
116
118
 
117
- def test_should_not_encrypt_empty_string
119
+ def test_should_not_encrypt_empty_string_by_default
118
120
  assert_equal '', User.encrypt_email('', iv: @iv)
119
121
  end
120
122
 
@@ -150,9 +152,14 @@ class AttrEncryptedTest < Minitest::Test
150
152
  def test_should_decrypt_email_when_reading
151
153
  @user = User.new
152
154
  assert_nil @user.email
153
- iv = @user.encrypted_email_iv.unpack('m').first
154
- salt = @user.encrypted_email_salt[1..-1].unpack('m').first
155
+ options = @user.encrypted_attributes[:email]
156
+ iv = @user.send(:generate_iv, options[:algorithm])
157
+ encoded_iv = [iv].pack(options[:encode_iv])
158
+ salt = SecureRandom.random_bytes
159
+ encoded_salt = @user.send(:prefix_and_encode_salt, salt, options[:encode_salt])
155
160
  @user.encrypted_email = User.encrypt_email('test@example.com', iv: iv, salt: salt)
161
+ @user.encrypted_email_iv = encoded_iv
162
+ @user.encrypted_email_salt = encoded_salt
156
163
  assert_equal 'test@example.com', @user.email
157
164
  end
158
165
 
@@ -282,6 +289,18 @@ class AttrEncryptedTest < Minitest::Test
282
289
  assert_equal 'testing', @user.encrypted_with_true_unless
283
290
  end
284
291
 
292
+ def test_should_encrypt_empty_with_truthy_allow_empty_value_option
293
+ @user = User.new
294
+ assert_nil @user.encrypted_with_allow_empty_value
295
+ @user.with_allow_empty_value = ''
296
+ refute_nil @user.encrypted_with_allow_empty_value
297
+ assert_equal '', @user.with_allow_empty_value
298
+ @user = User.new
299
+ @user.with_allow_empty_value = nil
300
+ refute_nil @user.encrypted_with_allow_empty_value
301
+ assert_nil @user.with_allow_empty_value
302
+ end
303
+
285
304
  def test_should_work_with_aliased_attr_encryptor
286
305
  assert User.encrypted_attributes.include?(:aliased)
287
306
  end
@@ -390,4 +409,61 @@ class AttrEncryptedTest < Minitest::Test
390
409
  user.email = 'revised_value@test.com'
391
410
  refute_equal original_iv, user.encrypted_email_iv
392
411
  end
412
+
413
+ def test_should_not_generate_iv_for_attribute_when_if_option_is_false
414
+ user = User.new
415
+ user.with_false_if = 'derp'
416
+ assert_nil user.encrypted_with_false_if_iv
417
+ end
418
+
419
+ def test_should_generate_iv_for_attribute_when_if_option_is_true
420
+ user = User.new
421
+ user.with_true_if = 'derp'
422
+ refute_nil user.encrypted_with_true_if_iv
423
+
424
+ user.with_true_if = Object.new
425
+ refute_nil user.encrypted_with_true_if_iv
426
+ end
427
+
428
+ def test_should_not_generate_salt_for_attribute_when_if_option_is_false
429
+ user = User.new
430
+ user.with_false_if = 'derp'
431
+ assert_nil user.encrypted_with_false_if_salt
432
+ end
433
+
434
+ def test_should_generate_salt_for_attribute_when_if_option_is_true
435
+ user = User.new
436
+ user.with_true_if = 'derp'
437
+ refute_nil user.encrypted_with_true_if_salt
438
+ end
439
+
440
+ def test_should_generate_iv_for_attribute_when_unless_option_is_false
441
+ user = User.new
442
+ user.with_false_unless = 'derp'
443
+ refute_nil user.encrypted_with_false_unless_iv
444
+ end
445
+
446
+ def test_should_not_generate_iv_for_attribute_when_unless_option_is_true
447
+ user = User.new
448
+ user.with_true_unless = 'derp'
449
+ assert_nil user.encrypted_with_true_unless_iv
450
+ end
451
+
452
+ def test_should_generate_salt_for_attribute_when_unless_option_is_false
453
+ user = User.new
454
+ user.with_false_unless = 'derp'
455
+ refute_nil user.encrypted_with_false_unless_salt
456
+ end
457
+
458
+ def test_should_not_generate_salt_for_attribute_when_unless_option_is_true
459
+ user = User.new
460
+ user.with_true_unless = 'derp'
461
+ assert_nil user.encrypted_with_true_unless_salt
462
+ end
463
+
464
+ def test_should_not_by_default_generate_iv_when_attribute_is_empty
465
+ user = User.new
466
+ user.with_true_if = nil
467
+ assert_nil user.encrypted_with_true_if_iv
468
+ end
393
469
  end
@@ -41,7 +41,7 @@ class CompatibilityTest < Minitest::Test
41
41
  end
42
42
 
43
43
  def setup
44
- ActiveRecord::Base.connection.tables.each { |table| ActiveRecord::Base.connection.drop_table(table) }
44
+ drop_all_tables
45
45
  create_tables
46
46
  end
47
47
 
@@ -81,26 +81,24 @@ class CompatibilityTest < Minitest::Test
81
81
  private
82
82
 
83
83
  def create_tables
84
- silence_stream(STDOUT) do
85
- ActiveRecord::Schema.define(:version => 1) do
86
- create_table :nonmarshalling_pets do |t|
87
- t.string :name
88
- t.string :encrypted_nickname
89
- t.string :encrypted_nickname_iv
90
- t.string :encrypted_nickname_salt
91
- t.string :encrypted_birthdate
92
- t.string :encrypted_birthdate_iv
93
- t.string :encrypted_birthdate_salt
94
- end
95
- create_table :marshalling_pets do |t|
96
- t.string :name
97
- t.string :encrypted_nickname
98
- t.string :encrypted_nickname_iv
99
- t.string :encrypted_nickname_salt
100
- t.string :encrypted_birthdate
101
- t.string :encrypted_birthdate_iv
102
- t.string :encrypted_birthdate_salt
103
- end
84
+ ActiveRecord::Schema.define(:version => 1) do
85
+ create_table :nonmarshalling_pets do |t|
86
+ t.string :name
87
+ t.string :encrypted_nickname
88
+ t.string :encrypted_nickname_iv
89
+ t.string :encrypted_nickname_salt
90
+ t.string :encrypted_birthdate
91
+ t.string :encrypted_birthdate_iv
92
+ t.string :encrypted_birthdate_salt
93
+ end
94
+ create_table :marshalling_pets do |t|
95
+ t.string :name
96
+ t.string :encrypted_nickname
97
+ t.string :encrypted_nickname_iv
98
+ t.string :encrypted_nickname_salt
99
+ t.string :encrypted_birthdate
100
+ t.string :encrypted_birthdate_iv
101
+ t.string :encrypted_birthdate_salt
104
102
  end
105
103
  end
106
104
  end