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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.travis.yml +37 -1
- data/CHANGELOG.md +22 -0
- data/README.md +16 -5
- data/Rakefile +1 -0
- data/attr_encrypted.gemspec +6 -1
- data/certs/saghaulor.pem +15 -15
- data/checksum/attr_encrypted-3.0.0.gem.sha256 +1 -0
- data/checksum/attr_encrypted-3.0.0.gem.sha512 +1 -0
- data/checksum/attr_encrypted-3.0.1.gem.sha256 +1 -0
- data/checksum/attr_encrypted-3.0.1.gem.sha512 +1 -0
- data/checksum/attr_encrypted-3.0.2.gem.sha256 +1 -0
- data/checksum/attr_encrypted-3.0.2.gem.sha512 +1 -0
- data/checksum/attr_encrypted-3.0.3.gem.sha256 +1 -0
- data/checksum/attr_encrypted-3.0.3.gem.sha512 +1 -0
- data/lib/attr_encrypted/adapters/active_record.rb +35 -20
- data/lib/attr_encrypted/version.rb +1 -1
- data/lib/attr_encrypted.rb +121 -102
- data/test/active_record_test.rb +76 -49
- data/test/attr_encrypted_test.rb +83 -7
- data/test/compatibility_test.rb +19 -21
- data/test/legacy_active_record_test.rb +7 -9
- data/test/legacy_compatibility_test.rb +13 -15
- data/test/test_helper.rb +7 -0
- data.tar.gz.sig +0 -0
- metadata +40 -32
- metadata.gz.sig +0 -0
data/lib/attr_encrypted.rb
CHANGED
|
@@ -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:
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
# prefix:
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
# suffix:
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
# key:
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
# encode:
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
# encode_iv:
|
|
58
|
-
|
|
59
|
-
# encode_salt:
|
|
60
|
-
#
|
|
61
|
-
# default_encoding:
|
|
62
|
-
#
|
|
63
|
-
# marshal:
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
# marshaler:
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
# dump_method:
|
|
72
|
-
#
|
|
73
|
-
#
|
|
74
|
-
# load_method:
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
# encryptor:
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
# encrypt_method:
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
# decrypt_method:
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
# if:
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
#
|
|
91
|
-
#
|
|
92
|
-
# unless:
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
# mode:
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
102
|
-
#
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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] &&
|
|
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] &&
|
|
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[:
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
|
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
|
|
data/test/active_record_test.rb
CHANGED
|
@@ -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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
86
|
-
attr_encrypted :password, 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
|
-
|
|
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.
|
|
309
|
-
|
|
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
|
data/test/attr_encrypted_test.rb
CHANGED
|
@@ -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
|
|
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
|
-
|
|
154
|
-
|
|
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
|
data/test/compatibility_test.rb
CHANGED
|
@@ -41,7 +41,7 @@ class CompatibilityTest < Minitest::Test
|
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def setup
|
|
44
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|