attr_encrypted 3.0.3 → 4.0.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.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'encryptor'
2
4
 
3
5
  # Adds attr_accessors that encrypt and decrypt an object's attributes
@@ -8,7 +10,7 @@ module AttrEncrypted
8
10
  base.class_eval do
9
11
  include InstanceMethods
10
12
  attr_writer :attr_encrypted_options
11
- @attr_encrypted_options, @encrypted_attributes = {}, {}
13
+ @attr_encrypted_options, @attr_encrypted_encrypted_attributes = {}, {}
12
14
  end
13
15
  end
14
16
 
@@ -16,90 +18,93 @@ module AttrEncrypted
16
18
  #
17
19
  # Options (any other options you specify are passed to the Encryptor's encrypt and decrypt methods)
18
20
  #
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>.
21
+ # attribute: The name of the referenced encrypted attribute. For example
22
+ # <tt>attr_accessor :email, attribute: :ee</tt> would generate an
23
+ # attribute named 'ee' to store the encrypted email. This is useful when defining
24
+ # one attribute to encrypt at a time or when the :prefix and :suffix options
25
+ # aren't enough.
26
+ # Defaults to nil.
27
+ #
28
+ # prefix: A prefix used to generate the name of the referenced encrypted attributes.
29
+ # For example <tt>attr_accessor :email, prefix: 'crypted_'</tt> would
30
+ # generate attributes named 'crypted_email' to store the encrypted
31
+ # email and password.
32
+ # Defaults to 'encrypted_'.
33
+ #
34
+ # suffix: A suffix used to generate the name of the referenced encrypted attributes.
35
+ # For example <tt>attr_accessor :email, prefix: '', suffix: '_encrypted'</tt>
36
+ # would generate attributes named 'email_encrypted' to store the
37
+ # encrypted email.
38
+ # Defaults to ''.
39
+ #
40
+ # key: The encryption key. This option may not be required if
41
+ # you're using a custom encryptor. If you pass a symbol
42
+ # representing an instance method then the :key option
43
+ # will be replaced with the result of the method before
44
+ # being passed to the encryptor. Objects that respond
45
+ # to :call are evaluated as well (including procs).
46
+ # Any other key types will be passed directly to the encryptor.
47
+ # Defaults to nil.
48
+ #
49
+ # encode: If set to true, attributes will be encoded as well as
50
+ # encrypted. This is useful if you're planning on storing
51
+ # the encrypted attributes in a database. The default
52
+ # encoding is 'm' (base64), however this can be overwritten
53
+ # by setting the :encode option to some other encoding
54
+ # string instead of just 'true'. See
55
+ # http://www.ruby-doc.org/core/classes/Array.html#M002245
56
+ # for more encoding directives.
57
+ # Defaults to false unless you're using it with ActiveRecord, DataMapper, or Sequel.
58
+ #
59
+ # encode_iv: Defaults to true.
60
+
61
+ # encode_salt: Defaults to true.
62
+ #
63
+ # default_encoding: Defaults to 'm' (base64).
64
+ #
65
+ # marshal: If set to true, attributes will be marshaled as well
66
+ # as encrypted. This is useful if you're planning on
67
+ # encrypting something other than a string.
68
+ # Defaults to false.
69
+ #
70
+ # marshaler: The object to use for marshaling.
71
+ # Defaults to Marshal.
72
+ #
73
+ # dump_method: The dump method name to call on the <tt>:marshaler</tt> object to.
74
+ # Defaults to 'dump'.
75
+ #
76
+ # load_method: The load method name to call on the <tt>:marshaler</tt> object.
77
+ # Defaults to 'load'.
78
+ #
79
+ # encryptor: The object to use for encrypting.
80
+ # Defaults to Encryptor.
81
+ #
82
+ # encrypt_method: The encrypt method name to call on the <tt>:encryptor</tt> object.
83
+ # Defaults to 'encrypt'.
84
+ #
85
+ # decrypt_method: The decrypt method name to call on the <tt>:encryptor</tt> object.
86
+ # Defaults to 'decrypt'.
87
+ #
88
+ # if: Attributes are only encrypted if this option evaluates
89
+ # to true. If you pass a symbol representing an instance
90
+ # method then the result of the method will be evaluated.
91
+ # Any objects that respond to <tt>:call</tt> are evaluated as well.
92
+ # Defaults to true.
93
+ #
94
+ # unless: Attributes are only encrypted if this option evaluates
95
+ # to false. If you pass a symbol representing an instance
96
+ # method then the result of the method will be evaluated.
97
+ # Any objects that respond to <tt>:call</tt> are evaluated as well.
98
+ # Defaults to false.
99
+ #
100
+ # mode: Selects encryption mode for attribute: choose <tt>:single_iv_and_salt</tt> for compatibility
101
+ # with the old attr_encrypted API: the IV is derived from the encryption key by the underlying Encryptor class; salt is not used.
102
+ # 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.
103
+ # A <tt>:per_attribute_iv</default> mode derives a unique IV per attribute; salt is not used.
104
+ # Defaults to <tt>:per_attribute_iv</tt>.
105
+ #
106
+ # allow_empty_value: Attributes which have nil or empty string values will not be encrypted unless this option
107
+ # has a truthy value.
103
108
  #
104
109
  # You can specify your own default options
105
110
  #
@@ -140,23 +145,26 @@ module AttrEncrypted
140
145
  encrypted_attribute_name = (options[:attribute] ? options[:attribute] : [options[:prefix], attribute, options[:suffix]].join).to_sym
141
146
 
142
147
  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
148
 
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}=")
149
+ if attribute_instance_methods_as_symbols_available?
150
+ attr_reader encrypted_attribute_name unless instance_methods_as_symbols.include?(encrypted_attribute_name)
151
+ attr_writer encrypted_attribute_name unless instance_methods_as_symbols.include?(:"#{encrypted_attribute_name}=")
152
+
153
+ iv_name = "#{encrypted_attribute_name}_iv".to_sym
154
+ attr_reader iv_name unless instance_methods_as_symbols.include?(iv_name)
155
+ attr_writer iv_name unless instance_methods_as_symbols.include?(:"#{iv_name}=")
149
156
 
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}=")
157
+ salt_name = "#{encrypted_attribute_name}_salt".to_sym
158
+ attr_reader salt_name unless instance_methods_as_symbols.include?(salt_name)
159
+ attr_writer salt_name unless instance_methods_as_symbols.include?(:"#{salt_name}=")
160
+ end
153
161
 
154
162
  define_method(attribute) do
155
- instance_variable_get("@#{attribute}") || instance_variable_set("@#{attribute}", decrypt(attribute, send(encrypted_attribute_name)))
163
+ instance_variable_get("@#{attribute}") || instance_variable_set("@#{attribute}", attr_encrypted_decrypt(attribute, send(encrypted_attribute_name)))
156
164
  end
157
165
 
158
166
  define_method("#{attribute}=") do |value|
159
- send("#{encrypted_attribute_name}=", encrypt(attribute, value))
167
+ send("#{encrypted_attribute_name}=", attr_encrypted_encrypt(attribute, value))
160
168
  instance_variable_set("@#{attribute}", value)
161
169
  end
162
170
 
@@ -165,7 +173,7 @@ module AttrEncrypted
165
173
  value.respond_to?(:empty?) ? !value.empty? : !!value
166
174
  end
167
175
 
168
- encrypted_attributes[attribute.to_sym] = options.merge(attribute: encrypted_attribute_name)
176
+ self.attr_encrypted_encrypted_attributes[attribute.to_sym] = options.merge(attribute: encrypted_attribute_name)
169
177
  end
170
178
  end
171
179
 
@@ -197,6 +205,7 @@ module AttrEncrypted
197
205
  decrypt_method: 'decrypt',
198
206
  mode: :per_attribute_iv,
199
207
  algorithm: 'aes-256-gcm',
208
+ allow_empty_value: false,
200
209
  }
201
210
  end
202
211
 
@@ -214,7 +223,7 @@ module AttrEncrypted
214
223
  # User.attr_encrypted?(:name) # false
215
224
  # User.attr_encrypted?(:email) # true
216
225
  def attr_encrypted?(attribute)
217
- encrypted_attributes.has_key?(attribute.to_sym)
226
+ attr_encrypted_encrypted_attributes.has_key?(attribute.to_sym)
218
227
  end
219
228
 
220
229
  # Decrypts a value for the attribute specified
@@ -225,10 +234,10 @@ module AttrEncrypted
225
234
  # attr_encrypted :email
226
235
  # end
227
236
  #
228
- # email = User.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
229
- def decrypt(attribute, encrypted_value, options = {})
230
- 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?)
237
+ # email = User.attr_encrypted_decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
238
+ def attr_encrypted_decrypt(attribute, encrypted_value, options = {})
239
+ options = attr_encrypted_encrypted_attributes[attribute.to_sym].merge(options)
240
+ if options[:if] && !options[:unless] && not_empty?(encrypted_value)
232
241
  encrypted_value = encrypted_value.unpack(options[:encode]).first if options[:encode]
233
242
  value = options[:encryptor].send(options[:decrypt_method], options.merge!(value: encrypted_value))
234
243
  if options[:marshal]
@@ -251,10 +260,10 @@ module AttrEncrypted
251
260
  # attr_encrypted :email
252
261
  # end
253
262
  #
254
- # encrypted_email = User.encrypt(:email, 'test@example.com')
255
- def encrypt(attribute, value, options = {})
256
- options = encrypted_attributes[attribute.to_sym].merge(options)
257
- if options[:if] && !options[:unless] && !value.nil? && !(value.is_a?(String) && value.empty?)
263
+ # encrypted_email = User.attr_encrypted_encrypt(:email, 'test@example.com')
264
+ def attr_encrypted_encrypt(attribute, value, options = {})
265
+ options = attr_encrypted_encrypted_attributes[attribute.to_sym].merge(options)
266
+ if options[:if] && !options[:unless] && (options[:allow_empty_value] || not_empty?(value))
258
267
  value = options[:marshal] ? options[:marshaler].send(options[:dump_method], value) : value.to_s
259
268
  encrypted_value = options[:encryptor].send(options[:encrypt_method], options.merge!(value: value))
260
269
  encrypted_value = [encrypted_value].pack(options[:encode]) if options[:encode]
@@ -264,6 +273,10 @@ module AttrEncrypted
264
273
  end
265
274
  end
266
275
 
276
+ def not_empty?(value)
277
+ !value.nil? && !(value.is_a?(String) && value.empty?)
278
+ end
279
+
267
280
  # Contains a hash of encrypted attributes with virtual attribute names as keys
268
281
  # and their corresponding options as values
269
282
  #
@@ -273,9 +286,9 @@ module AttrEncrypted
273
286
  # attr_encrypted :email, key: 'my secret key'
274
287
  # end
275
288
  #
276
- # User.encrypted_attributes # { email: { attribute: 'encrypted_email', key: 'my secret key' } }
277
- def encrypted_attributes
278
- @encrypted_attributes ||= superclass.encrypted_attributes.dup
289
+ # User.attr_encrypted_encrypted_attributes # { email: { attribute: 'encrypted_email', key: 'my secret key' } }
290
+ def attr_encrypted_encrypted_attributes
291
+ @attr_encrypted_encrypted_attributes ||= superclass.attr_encrypted_encrypted_attributes.dup
279
292
  end
280
293
 
281
294
  # Forwards calls to :encrypt_#{attribute} or :decrypt_#{attribute} to the corresponding encrypt or decrypt method
@@ -290,7 +303,7 @@ module AttrEncrypted
290
303
  # User.encrypt_email('SOME_ENCRYPTED_EMAIL_STRING')
291
304
  def method_missing(method, *arguments, &block)
292
305
  if method.to_s =~ /^((en|de)crypt)_(.+)$/ && attr_encrypted?($3)
293
- send($1, $3, *arguments)
306
+ send("attr_encrypted_#{$1}", $3, *arguments)
294
307
  else
295
308
  super
296
309
  end
@@ -312,9 +325,10 @@ module AttrEncrypted
312
325
  #
313
326
  # @user = User.new('some-secret-key')
314
327
  # @user.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
315
- def decrypt(attribute, encrypted_value)
316
- encrypted_attributes[attribute.to_sym][:operation] = :decrypting
317
- self.class.decrypt(attribute, encrypted_value, evaluated_attr_encrypted_options_for(attribute))
328
+ def attr_encrypted_decrypt(attribute, encrypted_value)
329
+ attr_encrypted_encrypted_attributes[attribute.to_sym][:operation] = :decrypting
330
+ attr_encrypted_encrypted_attributes[attribute.to_sym][:value_present] = self.class.not_empty?(encrypted_value)
331
+ self.class.attr_encrypted_decrypt(attribute, encrypted_value, evaluated_attr_encrypted_options_for(attribute))
318
332
  end
319
333
 
320
334
  # Encrypts a value for the attribute specified using options evaluated in the current object's scope
@@ -331,17 +345,22 @@ module AttrEncrypted
331
345
  # end
332
346
  #
333
347
  # @user = User.new('some-secret-key')
334
- # @user.encrypt(:email, 'test@example.com')
335
- def encrypt(attribute, value)
336
- encrypted_attributes[attribute.to_sym][:operation] = :encrypting
337
- self.class.encrypt(attribute, value, evaluated_attr_encrypted_options_for(attribute))
348
+ # @user.attr_encrypted_encrypt(:email, 'test@example.com')
349
+ def attr_encrypted_encrypt(attribute, value)
350
+ attr_encrypted_encrypted_attributes[attribute.to_sym][:operation] = :encrypting
351
+ attr_encrypted_encrypted_attributes[attribute.to_sym][:value_present] = self.class.not_empty?(value)
352
+ self.class.attr_encrypted_encrypt(attribute, value, evaluated_attr_encrypted_options_for(attribute))
338
353
  end
339
354
 
340
355
  # Copies the class level hash of encrypted attributes with virtual attribute names as keys
341
356
  # and their corresponding options as values to the instance
342
357
  #
343
- def encrypted_attributes
344
- @encrypted_attributes ||= self.class.encrypted_attributes.dup
358
+ def attr_encrypted_encrypted_attributes
359
+ @attr_encrypted_encrypted_attributes ||= begin
360
+ duplicated= {}
361
+ self.class.attr_encrypted_encrypted_attributes.map { |key, value| duplicated[key] = value.dup }
362
+ duplicated
363
+ end
345
364
  end
346
365
 
347
366
  protected
@@ -349,20 +368,28 @@ module AttrEncrypted
349
368
  # Returns attr_encrypted options evaluated in the current object's scope for the attribute specified
350
369
  def evaluated_attr_encrypted_options_for(attribute)
351
370
  evaluated_options = Hash.new
352
- attribute_option_value = encrypted_attributes[attribute.to_sym][:attribute]
353
- encrypted_attributes[attribute.to_sym].map do |option, value|
354
- evaluated_options[option] = evaluate_attr_encrypted_option(value)
371
+ attributes = attr_encrypted_encrypted_attributes[attribute.to_sym]
372
+ attribute_option_value = attributes[:attribute]
373
+
374
+ [:if, :unless, :value_present, :allow_empty_value].each do |option|
375
+ evaluated_options[option] = evaluate_attr_encrypted_option(attributes[option])
355
376
  end
356
377
 
357
378
  evaluated_options[:attribute] = attribute_option_value
358
379
 
359
380
  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)
381
+ if options[:if] && !options[:unless] && options[:value_present] || options[:allow_empty_value]
382
+ (attributes.keys - evaluated_options.keys).each do |option|
383
+ options[option] = evaluate_attr_encrypted_option(attributes[option])
384
+ end
385
+
386
+ unless options[:mode] == :single_iv_and_salt
387
+ load_iv_for_attribute(attribute, options)
388
+ end
389
+
390
+ if options[:mode] == :per_attribute_iv_and_salt
391
+ load_salt_for_attribute(attribute, options)
392
+ end
366
393
  end
367
394
  end
368
395
  end
@@ -371,7 +398,7 @@ module AttrEncrypted
371
398
  #
372
399
  # If the option is not a symbol or proc then the original option is returned
373
400
  def evaluate_attr_encrypted_option(option)
374
- if option.is_a?(Symbol) && respond_to?(option)
401
+ if option.is_a?(Symbol) && respond_to?(option, true)
375
402
  send(option)
376
403
  elsif option.respond_to?(:call)
377
404
  option.call(self)
@@ -408,7 +435,7 @@ module AttrEncrypted
408
435
  encrypted_attribute_name = options[:attribute]
409
436
  encode_salt = options[:encode_salt]
410
437
  salt = options[:salt] || send("#{encrypted_attribute_name}_salt")
411
- if (salt == nil)
438
+ if options[:operation] == :encrypting
412
439
  salt = SecureRandom.random_bytes
413
440
  salt = prefix_and_encode_salt(salt, encode_salt) if encode_salt
414
441
  send("#{encrypted_attribute_name}_salt=", salt)
@@ -436,6 +463,10 @@ module AttrEncrypted
436
463
  instance_methods.collect { |method| method.to_sym }
437
464
  end
438
465
 
466
+ def attribute_instance_methods_as_symbols_available?
467
+ true
468
+ end
469
+
439
470
  end
440
471
 
441
472
 
@@ -1,9 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'test_helper'
2
4
 
5
+ RAILS_VERSION = Gem::Version.new(::ActiveRecord::VERSION::STRING).freeze
3
6
  ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
4
7
 
5
8
  def create_tables
6
9
  ActiveRecord::Schema.define(version: 1) do
10
+ self.verbose = false
7
11
  create_table :people do |t|
8
12
  t.string :encrypted_email
9
13
  t.string :password
@@ -19,7 +23,7 @@ def create_tables
19
23
  t.string :encrypted_password
20
24
  t.string :encrypted_password_iv
21
25
  t.string :encrypted_password_salt
22
- t.binary :key
26
+ t.string :key
23
27
  end
24
28
  create_table :users do |t|
25
29
  t.string :login
@@ -40,21 +44,16 @@ def create_tables
40
44
  end
41
45
  end
42
46
 
43
- # The table needs to exist before defining the class
44
- create_tables
45
-
46
47
  ActiveRecord::MissingAttributeError = ActiveModel::MissingAttributeError unless defined?(ActiveRecord::MissingAttributeError)
47
48
 
48
- if ::ActiveRecord::VERSION::STRING > "4.0"
49
- module Rack
50
- module Test
51
- class UploadedFile; end
52
- end
49
+ module Rack
50
+ module Test
51
+ class UploadedFile; end
53
52
  end
54
-
55
- require 'action_controller/metal/strong_parameters'
56
53
  end
57
54
 
55
+ require 'action_controller/metal/strong_parameters'
56
+
58
57
  class Person < ActiveRecord::Base
59
58
  self.attr_encrypted_options[:mode] = :per_attribute_iv_and_salt
60
59
  attr_encrypted :email, key: SECRET_KEY
@@ -81,11 +80,11 @@ class PersonWithProcMode < Person
81
80
  end
82
81
 
83
82
  class Account < ActiveRecord::Base
84
- ACCOUNT_ENCRYPTION_KEY = SecureRandom.base64(32)
83
+ ACCOUNT_ENCRYPTION_KEY = SecureRandom.urlsafe_base64(24)
85
84
  attr_encrypted :password, key: :password_encryption_key
86
85
 
87
86
  def encrypting?(attr)
88
- encrypted_attributes[attr][:operation] == :encrypting
87
+ attr_encrypted_encrypted_attributes[attr][:operation] == :encrypting
89
88
  end
90
89
 
91
90
  def password_encryption_key
@@ -106,7 +105,6 @@ end
106
105
  class UserWithProtectedAttribute < ActiveRecord::Base
107
106
  self.table_name = 'users'
108
107
  attr_encrypted :password, key: SECRET_KEY
109
- attr_protected :is_admin if ::ActiveRecord::VERSION::STRING < "4.0"
110
108
  end
111
109
 
112
110
  class PersonUsingAlias < ActiveRecord::Base
@@ -128,7 +126,7 @@ end
128
126
  class ActiveRecordTest < Minitest::Test
129
127
 
130
128
  def setup
131
- ActiveRecord::Base.connection.tables.each { |table| ActiveRecord::Base.connection.drop_table(table) }
129
+ drop_all_tables
132
130
  create_tables
133
131
  end
134
132
 
@@ -206,7 +204,7 @@ class ActiveRecordTest < Minitest::Test
206
204
 
207
205
  def test_attribute_was_works_when_options_for_old_encrypted_value_are_different_than_options_for_new_encrypted_value
208
206
  pw = 'password'
209
- crypto_key = SecureRandom.base64(32)
207
+ crypto_key = SecureRandom.urlsafe_base64(24)
210
208
  old_iv = SecureRandom.random_bytes(12)
211
209
  account = Account.create
212
210
  encrypted_value = Encryptor.encrypt(value: pw, iv: old_iv, key: crypto_key)
@@ -221,52 +219,53 @@ class ActiveRecordTest < Minitest::Test
221
219
  assert_equal pw.reverse, account.password
222
220
  end
223
221
 
224
- if ::ActiveRecord::VERSION::STRING > "4.0"
225
- def test_should_assign_attributes
226
- @user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
227
- @user.attributes = ActionController::Parameters.new(login: 'modified', is_admin: true).permit(:login)
228
- assert_equal 'modified', @user.login
222
+ if Gem::Requirement.new('>= 5.2').satisfied_by?(RAILS_VERSION)
223
+ def test_should_create_will_save_change_to_predicate
224
+ person = Person.create!(email: 'test@example.com')
225
+ refute person.will_save_change_to_email?
226
+ person.email = 'test@example.com'
227
+ refute person.will_save_change_to_email?
228
+ person.email = 'test2@example.com'
229
+ assert person.will_save_change_to_email?
229
230
  end
230
231
 
231
- def test_should_not_assign_protected_attributes
232
- @user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
233
- @user.attributes = ActionController::Parameters.new(login: 'modified', is_admin: true).permit(:login)
234
- assert !@user.is_admin?
232
+ def test_should_create_saved_change_to_predicate
233
+ person = Person.create!(email: 'test@example.com')
234
+ assert person.saved_change_to_email?
235
+ person.reload
236
+ person.email = 'test@example.com'
237
+ refute person.saved_change_to_email?
238
+ person.email = nil
239
+ refute person.saved_change_to_email?
240
+ person.email = 'test2@example.com'
241
+ refute person.saved_change_to_email?
242
+ person.save
243
+ assert person.saved_change_to_email?
235
244
  end
245
+ end
236
246
 
237
- def test_should_raise_exception_if_not_permitted
238
- @user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
239
- assert_raises ActiveModel::ForbiddenAttributesError do
240
- @user.attributes = ActionController::Parameters.new(login: 'modified', is_admin: true)
241
- end
242
- end
247
+ def test_should_assign_attributes
248
+ @user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
249
+ @user.attributes = ActionController::Parameters.new(login: 'modified', is_admin: true).permit(:login)
250
+ assert_equal 'modified', @user.login
251
+ end
243
252
 
244
- def test_should_raise_exception_on_init_if_not_permitted
245
- assert_raises ActiveModel::ForbiddenAttributesError do
246
- @user = UserWithProtectedAttribute.new ActionController::Parameters.new(login: 'modified', is_admin: true)
247
- end
248
- end
249
- else
250
- def test_should_assign_attributes
251
- @user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
252
- @user.attributes = { login: 'modified', is_admin: true }
253
- assert_equal 'modified', @user.login
254
- end
253
+ def test_should_not_assign_protected_attributes
254
+ @user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
255
+ @user.attributes = ActionController::Parameters.new(login: 'modified', is_admin: true).permit(:login)
256
+ assert !@user.is_admin?
257
+ end
255
258
 
256
- def test_should_not_assign_protected_attributes
257
- @user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
258
- @user.attributes = { login: 'modified', is_admin: true }
259
- assert !@user.is_admin?
259
+ def test_should_raise_exception_if_not_permitted
260
+ @user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
261
+ assert_raises ActiveModel::ForbiddenAttributesError do
262
+ @user.attributes = ActionController::Parameters.new(login: 'modified', is_admin: true)
260
263
  end
264
+ end
261
265
 
262
- def test_should_assign_protected_attributes
263
- @user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
264
- if ::ActiveRecord::VERSION::STRING > "3.1"
265
- @user.send(:assign_attributes, { login: 'modified', is_admin: true }, without_protection: true)
266
- else
267
- @user.send(:attributes=, { login: 'modified', is_admin: true }, false)
268
- end
269
- assert @user.is_admin?
266
+ def test_should_raise_exception_on_init_if_not_permitted
267
+ assert_raises ActiveModel::ForbiddenAttributesError do
268
+ @user = UserWithProtectedAttribute.new ActionController::Parameters.new(login: 'modified', is_admin: true)
270
269
  end
271
270
  end
272
271
 
@@ -279,23 +278,21 @@ class ActiveRecordTest < Minitest::Test
279
278
  @person = PersonWithProcMode.create(email: 'test@example.com', credentials: 'password123')
280
279
 
281
280
  # Email is :per_attribute_iv_and_salt
282
- assert_equal @person.class.encrypted_attributes[:email][:mode].class, Proc
283
- assert_equal @person.class.encrypted_attributes[:email][:mode].call, :per_attribute_iv_and_salt
281
+ assert_equal @person.class.attr_encrypted_encrypted_attributes[:email][:mode].class, Proc
282
+ assert_equal @person.class.attr_encrypted_encrypted_attributes[:email][:mode].call, :per_attribute_iv_and_salt
284
283
  refute_nil @person.encrypted_email_salt
285
284
  refute_nil @person.encrypted_email_iv
286
285
 
287
286
  # Credentials is :single_iv_and_salt
288
- assert_equal @person.class.encrypted_attributes[:credentials][:mode].class, Proc
289
- assert_equal @person.class.encrypted_attributes[:credentials][:mode].call, :single_iv_and_salt
287
+ assert_equal @person.class.attr_encrypted_encrypted_attributes[:credentials][:mode].class, Proc
288
+ assert_equal @person.class.attr_encrypted_encrypted_attributes[:credentials][:mode].call, :single_iv_and_salt
290
289
  assert_nil @person.encrypted_credentials_salt
291
290
  assert_nil @person.encrypted_credentials_iv
292
291
  end
293
292
 
294
- if ::ActiveRecord::VERSION::STRING > "3.1"
295
- def test_should_allow_assign_attributes_with_nil
296
- @person = Person.new
297
- assert_nil(@person.assign_attributes nil)
298
- end
293
+ def test_should_allow_assign_attributes_with_nil
294
+ @person = Person.new
295
+ assert_nil(@person.assign_attributes nil)
299
296
  end
300
297
 
301
298
  def test_that_alias_encrypts_column
@@ -332,7 +329,9 @@ class ActiveRecordTest < Minitest::Test
332
329
  def test_should_evaluate_proc_based_mode
333
330
  street = '123 Elm'
334
331
  zipcode = '12345'
335
- address = Address.new(street: street, zipcode: zipcode, mode: :single_iv_and_salt)
336
- assert_nil address.encrypted_zipcode_iv
332
+ address = Address.create(street: street, zipcode: zipcode, mode: :single_iv_and_salt)
333
+ address.reload
334
+ refute_equal address.encrypted_zipcode, zipcode
335
+ assert_equal address.zipcode, zipcode
337
336
  end
338
337
  end