attr_encrypted-magicless 1.3.42

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.
@@ -0,0 +1 @@
1
+ require 'attr_encrypted'
@@ -0,0 +1,384 @@
1
+ require 'attr_encrypted/version'
2
+ require 'encryptor'
3
+ require 'active_support/core_ext/object/blank'
4
+ require 'active_support/core_ext/class/attribute'
5
+ require 'active_support/core_ext/array/extract_options'
6
+ require 'active_support/core_ext/hash/reverse_merge'
7
+ require 'active_support/dependencies/autoload'
8
+ require 'active_support/concern'
9
+
10
+ # Adds attr_accessors that encrypt and decrypt an object's attributes
11
+ module AttrEncrypted
12
+ extend ActiveSupport::Concern
13
+ extend ActiveSupport::Autoload
14
+
15
+ autoload_under "adapters" do
16
+ autoload :ActiveRecord
17
+ end
18
+
19
+ ##
20
+ # :singleton-method: attr_encrypted_options
21
+ # Default options to use with calls to <tt>attr_encrypted</tt>
22
+ #
23
+ # It will inherit existing options from its superclass
24
+
25
+ ##
26
+ # :singleton-method: encrypted_attributes
27
+ # Contains a hash of encrypted attributes with virtual attribute names as keys
28
+ # and their corresponding options as values
29
+ #
30
+ # Example
31
+ #
32
+ # class User
33
+ # attr_encrypted :email, :key => 'my secret key'
34
+ # end
35
+ #
36
+ # User.encrypted_attributes # { :email => { :attribute => 'encrypted_email', :key => 'my secret key' } }
37
+
38
+
39
+ included do
40
+ class_attribute :attr_encrypted_options, instance_accessor: false
41
+ self.attr_encrypted_options ||= {}
42
+
43
+ class_attribute :encrypted_attributes, instance_accessor: false
44
+ self.encrypted_attributes ||= {}
45
+
46
+ if defined?(::ActiveRecord) && self <= ::ActiveRecord::Base
47
+ include AttrEncrypted::ActiveRecord
48
+ end
49
+ end
50
+
51
+ module ClassMethods
52
+ DEFAULT_ATTR_ENCRYPTED_OPTIONS = {
53
+ :prefix => 'encrypted_',
54
+ :suffix => '',
55
+ :if => true,
56
+ :unless => false,
57
+ :encode => false,
58
+ :default_encoding => 'm',
59
+ :marshal => false,
60
+ :marshaler => Marshal,
61
+ :dump_method => 'dump',
62
+ :load_method => 'load',
63
+ :encryptor => Encryptor,
64
+ :encrypt_method => 'encrypt',
65
+ :decrypt_method => 'decrypt',
66
+ :mode => :single_iv_and_salt
67
+ }
68
+
69
+ private_constant :DEFAULT_ATTR_ENCRYPTED_OPTIONS
70
+
71
+ # Generates attr_accessors that encrypt and decrypt attributes transparently
72
+ #
73
+ # Options (any other options you specify are passed to the encryptor's encrypt and decrypt methods)
74
+ #
75
+ # :attribute => The name of the referenced encrypted attribute. For example
76
+ # <tt>attr_accessor :email, :attribute => :ee</tt> would generate an
77
+ # attribute named 'ee' to store the encrypted email. This is useful when defining
78
+ # one attribute to encrypt at a time or when the :prefix and :suffix options
79
+ # aren't enough. Defaults to nil.
80
+ #
81
+ # :prefix => A prefix used to generate the name of the referenced encrypted attributes.
82
+ # For example <tt>attr_accessor :email, :password, :prefix => 'crypted_'</tt> would
83
+ # generate attributes named 'crypted_email' and 'crypted_password' to store the
84
+ # encrypted email and password. Defaults to 'encrypted_'.
85
+ #
86
+ # :suffix => A suffix used to generate the name of the referenced encrypted attributes.
87
+ # For example <tt>attr_accessor :email, :password, :prefix => '', :suffix => '_encrypted'</tt>
88
+ # would generate attributes named 'email_encrypted' and 'password_encrypted' to store the
89
+ # encrypted email. Defaults to ''.
90
+ #
91
+ # :key => The encryption key. This option may not be required if you're using a custom encryptor. If you pass
92
+ # a symbol representing an instance method then the :key option will be replaced with the result of the
93
+ # method before being passed to the encryptor. Objects that respond to :call are evaluated as well (including procs).
94
+ # Any other key types will be passed directly to the encryptor.
95
+ #
96
+ # :encode => If set to true, attributes will be encoded as well as encrypted. This is useful if you're
97
+ # planning on storing the encrypted attributes in a database. The default encoding is 'm' (base64),
98
+ # however this can be overwritten by setting the :encode option to some other encoding string instead of
99
+ # just 'true'. See http://www.ruby-doc.org/core/classes/Array.html#M002245 for more encoding directives.
100
+ # Defaults to false unless you're using it with ActiveRecord, DataMapper, or Sequel.
101
+ #
102
+ # :default_encoding => Defaults to 'm' (base64).
103
+ #
104
+ # :marshal => If set to true, attributes will be marshaled as well as encrypted. This is useful if you're planning
105
+ # on encrypting something other than a string. Defaults to false unless you're using it with ActiveRecord
106
+ # or DataMapper.
107
+ #
108
+ # :marshaler => The object to use for marshaling. Defaults to Marshal.
109
+ #
110
+ # :dump_method => The dump method name to call on the <tt>:marshaler</tt> object to. Defaults to 'dump'.
111
+ #
112
+ # :load_method => The load method name to call on the <tt>:marshaler</tt> object. Defaults to 'load'.
113
+ #
114
+ # :encryptor => The object to use for encrypting. Defaults to Encryptor.
115
+ #
116
+ # :encrypt_method => The encrypt method name to call on the <tt>:encryptor</tt> object. Defaults to 'encrypt'.
117
+ #
118
+ # :decrypt_method => The decrypt method name to call on the <tt>:encryptor</tt> object. Defaults to 'decrypt'.
119
+ #
120
+ # :if => Attributes are only encrypted if this option evaluates to true. If you pass a symbol representing an instance
121
+ # method then the result of the method will be evaluated. Any objects that respond to <tt>:call</tt> are evaluated as well.
122
+ # Defaults to true.
123
+ #
124
+ # :unless => Attributes are only encrypted if this option evaluates to false. If you pass a symbol representing an instance
125
+ # method then the result of the method will be evaluated. Any objects that respond to <tt>:call</tt> are evaluated as well.
126
+ # Defaults to false.
127
+ #
128
+ # :mode => Selects encryption mode for attribute: choose <tt>:single_iv_and_salt</tt> for compatibility
129
+ # with the old attr_encrypted API: the default IV and salt of the underlying encryptor object
130
+ # is used; <tt>:per_attribute_iv_and_salt</tt> uses a per-attribute IV and salt attribute and
131
+ # is the recommended mode for new deployments.
132
+ # Defaults to <tt>:single_iv_and_salt</tt>.
133
+ #
134
+ # You can specify your own default options
135
+ #
136
+ # class User
137
+ # # now all attributes will be encoded and marshaled by default
138
+ # attr_encrypted_options.merge!(:encode => true, :marshal => true, :some_other_option => true)
139
+ # attr_encrypted :configuration, :key => 'my secret key'
140
+ # end
141
+ #
142
+ #
143
+ # Example
144
+ #
145
+ # class User
146
+ # attr_encrypted :email, :credit_card, :key => 'some secret key'
147
+ # attr_encrypted :configuration, :key => 'some other secret key', :marshal => true
148
+ # end
149
+ #
150
+ # @user = User.new
151
+ # @user.encrypted_email # nil
152
+ # @user.email? # false
153
+ # @user.email = 'test@example.com'
154
+ # @user.email? # true
155
+ # @user.encrypted_email # returns the encrypted version of 'test@example.com'
156
+ #
157
+ # @user.configuration = { :time_zone => 'UTC' }
158
+ # @user.encrypted_configuration # returns the encrypted version of configuration
159
+ #
160
+ # See README for more examples
161
+ def attr_encrypted(*attributes)
162
+ options = DEFAULT_ATTR_ENCRYPTED_OPTIONS.merge(attr_encrypted_options).merge!(attributes.extract_options!)
163
+
164
+ options[:encode] = options[:default_encoding] if options[:encode].is_a?(TrueClass)
165
+
166
+ attributes.each do |attribute|
167
+ encrypted_attribute_name = (options[:attribute] ? options[:attribute] : [options[:prefix], attribute, options[:suffix]].join.to_sym)
168
+ iv_name = :"#{encrypted_attribute_name}_iv"
169
+ salt_name = :"#{encrypted_attribute_name}_salt"
170
+ ivar_name = :"@#{attribute}"
171
+
172
+ attr_reader encrypted_attribute_name unless attribute_method_already_implemented?(encrypted_attribute_name)
173
+ attr_writer encrypted_attribute_name unless attribute_method_already_implemented?(:"#{encrypted_attribute_name}=")
174
+
175
+ if options[:mode] == :per_attribute_iv_and_salt
176
+ attr_reader iv_name unless attribute_method_already_implemented?(iv_name)
177
+ attr_writer iv_name unless attribute_method_already_implemented?(:"#{iv_name}=")
178
+
179
+ attr_reader salt_name unless attribute_method_already_implemented?(salt_name)
180
+ attr_writer salt_name unless attribute_method_already_implemented?(:"#{salt_name}=")
181
+ end
182
+
183
+ define_method(attribute) do
184
+ instance_variable_get(ivar_name) || instance_variable_set(ivar_name, decrypt(attribute, send(encrypted_attribute_name)))
185
+ end
186
+
187
+ define_method(:"#{attribute}=") do |value|
188
+ send(:"#{encrypted_attribute_name}=", encrypt(attribute, value))
189
+ instance_variable_set(ivar_name, value)
190
+ end
191
+
192
+ define_method(:"#{attribute}?") do
193
+ send(attribute).present?
194
+ end
195
+
196
+ encrypted_attributes[attribute.to_sym] = options.merge(:attribute => encrypted_attribute_name)
197
+
198
+ yield(attribute) if block_given?
199
+ end
200
+ end
201
+
202
+ alias_method :attr_encryptor, :attr_encrypted
203
+
204
+ protected :attr_encryptor, :attr_encrypted
205
+
206
+ # Checks if an attribute is configured with <tt>attr_encrypted</tt>
207
+ #
208
+ # Example
209
+ #
210
+ # class User
211
+ # attr_accessor :name
212
+ # attr_encrypted :email
213
+ # end
214
+ #
215
+ # User.attr_encrypted?(:name) # false
216
+ # User.attr_encrypted?(:email) # true
217
+ def attr_encrypted?(attribute)
218
+ encrypted_attributes.include?(attribute.to_sym)
219
+ end
220
+
221
+ # Decrypts a value for the attribute specified
222
+ #
223
+ # Example
224
+ #
225
+ # class User
226
+ # attr_encrypted :email
227
+ # end
228
+ #
229
+ # email = User.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
230
+ def decrypt(attribute, encrypted_value, options = {})
231
+ options = encrypted_attributes[attribute.to_sym].merge(options)
232
+ if options[:if] && !options[:unless] && !encrypted_value.nil? && !(encrypted_value.is_a?(String) && encrypted_value.empty?)
233
+ encrypted_value = encrypted_value.unpack(options[:encode]).first if options[:encode]
234
+ value = options[:encryptor].send(options[:decrypt_method], options.merge!(:value => encrypted_value))
235
+ if options[:marshal]
236
+ value = options[:marshaler].send(options[:load_method], value)
237
+ elsif defined?(Encoding)
238
+ encoding = Encoding.default_internal || Encoding.default_external
239
+ value = value.force_encoding(encoding.name)
240
+ end
241
+ value
242
+ else
243
+ encrypted_value
244
+ end
245
+ end
246
+
247
+ # Encrypts a value for the attribute specified
248
+ #
249
+ # Example
250
+ #
251
+ # class User
252
+ # attr_encrypted :email
253
+ # end
254
+ #
255
+ # encrypted_email = User.encrypt(:email, 'test@example.com')
256
+ def encrypt(attribute, value, options = {})
257
+ options = encrypted_attributes[attribute.to_sym].merge(options)
258
+ if options[:if] && !options[:unless] && !value.nil? && !(value.is_a?(String) && value.empty?)
259
+ value = options[:marshal] ? options[:marshaler].send(options[:dump_method], value) : value.to_s
260
+ encrypted_value = options[:encryptor].send(options[:encrypt_method], options.merge!(:value => value))
261
+ encrypted_value = [encrypted_value].pack(options[:encode]) if options[:encode]
262
+ encrypted_value
263
+ else
264
+ value
265
+ end
266
+ end
267
+
268
+ protected
269
+
270
+ def attribute_method_already_implemented?(method_name)
271
+ method_defined?(method_name) || private_method_defined?(method_name)
272
+ end
273
+
274
+ private
275
+
276
+ # Forwards calls to :encrypt_#{attribute} or :decrypt_#{attribute} to the corresponding encrypt or decrypt method
277
+ # if attribute was configured with attr_encrypted
278
+ #
279
+ # Example
280
+ #
281
+ # class User
282
+ # attr_encrypted :email, :key => 'my secret key'
283
+ # end
284
+ #
285
+ # User.encrypt_email('SOME_ENCRYPTED_EMAIL_STRING')
286
+ def method_missing(method, *arguments, &block)
287
+ if method.to_s =~ /^((en|de)crypt)_(.+)$/ && attr_encrypted?($3)
288
+ send($1, $3, *arguments)
289
+ else
290
+ super
291
+ end
292
+ end
293
+
294
+ def inherited(subclass)
295
+ super
296
+ subclass.attr_encrypted_options = attr_encrypted_options.dup
297
+ subclass.encrypted_attributes = encrypted_attributes.dup
298
+ end
299
+ end
300
+
301
+ # Decrypts a value for the attribute specified using options evaluated in the current object's scope
302
+ #
303
+ # Example
304
+ #
305
+ # class User
306
+ # attr_accessor :secret_key
307
+ # attr_encrypted :email, :key => :secret_key
308
+ #
309
+ # def initialize(secret_key)
310
+ # self.secret_key = secret_key
311
+ # end
312
+ # end
313
+ #
314
+ # @user = User.new('some-secret-key')
315
+ # @user.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
316
+ def decrypt(attribute, encrypted_value)
317
+ self.class.decrypt(attribute, encrypted_value, evaluated_attr_encrypted_options_for(attribute))
318
+ end
319
+
320
+ # Encrypts a value for the attribute specified using options evaluated in the current object's scope
321
+ #
322
+ # Example
323
+ #
324
+ # class User
325
+ # attr_accessor :secret_key
326
+ # attr_encrypted :email, :key => :secret_key
327
+ #
328
+ # def initialize(secret_key)
329
+ # self.secret_key = secret_key
330
+ # end
331
+ # end
332
+ #
333
+ # @user = User.new('some-secret-key')
334
+ # @user.encrypt(:email, 'test@example.com')
335
+ def encrypt(attribute, value)
336
+ self.class.encrypt(attribute, value, evaluated_attr_encrypted_options_for(attribute))
337
+ end
338
+
339
+ protected
340
+
341
+ # Returns attr_encrypted options evaluated in the current object's scope for the attribute specified
342
+ def evaluated_attr_encrypted_options_for(attribute)
343
+ if evaluate_attr_encrypted_option(self.class.encrypted_attributes[attribute.to_sym][:mode]) == :per_attribute_iv_and_salt
344
+ load_iv_for_attribute(attribute, self.class.encrypted_attributes[attribute.to_sym][:algorithm])
345
+ load_salt_for_attribute(attribute)
346
+ end
347
+
348
+ self.class.encrypted_attributes[attribute.to_sym].inject({}) { |hash, (option, value)| hash[option] = evaluate_attr_encrypted_option(value); hash }
349
+ end
350
+
351
+ # Evaluates symbol (method reference) or proc (responds to call) options
352
+ #
353
+ # If the option is not a symbol or proc then the original option is returned
354
+ def evaluate_attr_encrypted_option(option)
355
+ if option.is_a?(Symbol) && respond_to?(option)
356
+ send(option)
357
+ elsif option.respond_to?(:call)
358
+ option.call(self)
359
+ else
360
+ option
361
+ end
362
+ end
363
+
364
+ def load_iv_for_attribute(attribute, algorithm)
365
+ encrypted_attribute_name = self.class.encrypted_attributes[attribute.to_sym][:attribute]
366
+ iv = send(:"#{encrypted_attribute_name}_iv")
367
+ if iv.nil?
368
+ begin
369
+ algorithm ||= "aes-256-cbc"
370
+ algo = OpenSSL::Cipher::Cipher.new(algorithm)
371
+ iv = [algo.random_iv].pack("m")
372
+ send(:"#{encrypted_attribute_name}_iv=", iv)
373
+ rescue RuntimeError
374
+ end
375
+ end
376
+ self.class.encrypted_attributes[attribute.to_sym][:iv] = iv.unpack("m").first if iv.present?
377
+ end
378
+
379
+ def load_salt_for_attribute(attribute)
380
+ encrypted_attribute_name = self.class.encrypted_attributes[attribute.to_sym][:attribute]
381
+ salt = send(:"#{encrypted_attribute_name}_salt") || send(:"#{encrypted_attribute_name}_salt=", Digest::SHA256.hexdigest((Time.now.to_i * rand(1000)).to_s)[0..15])
382
+ self.class.encrypted_attributes[attribute.to_sym][:salt] = salt
383
+ end
384
+ end
@@ -0,0 +1,49 @@
1
+ require 'active_support/core_ext/hash/slice'
2
+ require 'active_support/core_ext/hash/keys'
3
+ require 'active_support/core_ext/module/aliasing'
4
+
5
+ module AttrEncrypted
6
+ module ActiveRecord
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ alias_method_chain :reload, :attr_encrypted
11
+ alias_method_chain :assign_attributes, :attr_encrypted
12
+ alias_method :attributes=, :assign_attributes_with_attr_encrypted
13
+ attr_encrypted_options[:encode] = true
14
+ end
15
+
16
+ module ClassMethods
17
+ protected
18
+
19
+ # <tt>attr_encrypted</tt> method
20
+ def attr_encrypted(*)
21
+ super do |attribute_name|
22
+ alias_method :"#{attribute_name}_before_type_cast", attribute_name
23
+ end
24
+ end
25
+
26
+ def attribute_method_already_implemented?(method_name)
27
+ super || attribute_method?(method_name)
28
+ end
29
+ end
30
+
31
+ # https://github.com/attr-encrypted/attr_encrypted/issues/68
32
+ def reload_with_attr_encrypted(*args, &block)
33
+ reload_without_attr_encrypted(*args, &block).tap do
34
+ self.class.encrypted_attributes.each_key do |attribute_name|
35
+ instance_variable_set(:"@#{attribute_name}", nil)
36
+ end
37
+ end
38
+ end
39
+
40
+ def assign_attributes_with_attr_encrypted(new_attributes)
41
+ return if new_attributes.blank?
42
+ new_attributes = new_attributes.to_options
43
+ encrypted_part = new_attributes.extract!(*self.class.encrypted_attributes.keys)
44
+
45
+ assign_attributes_without_attr_encrypted(new_attributes)
46
+ assign_attributes_without_attr_encrypted(encrypted_part)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,3 @@
1
+ module AttrEncrypted
2
+ VERSION = "1.3.42"
3
+ end
@@ -0,0 +1,230 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:'
4
+
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.string :encrypted_email_salt
14
+ t.string :encrypted_credentials_salt
15
+ t.string :encrypted_email_iv
16
+ t.string :encrypted_credentials_iv
17
+ end
18
+ create_table :accounts do |t|
19
+ t.string :encrypted_password
20
+ t.string :encrypted_password_iv
21
+ t.string :encrypted_password_salt
22
+ end
23
+ create_table :users do |t|
24
+ t.string :login
25
+ t.string :encrypted_password
26
+ t.boolean :is_admin
27
+ end
28
+ create_table :prime_ministers do |t|
29
+ t.string :encrypted_name
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ # The table needs to exist before defining the class
36
+ create_tables
37
+
38
+ ActiveRecord::MissingAttributeError = ActiveModel::MissingAttributeError unless defined?(ActiveRecord::MissingAttributeError)
39
+
40
+ module Rack
41
+ module Test
42
+ class UploadedFile; end
43
+ end
44
+ end
45
+
46
+ require 'action_controller/metal/strong_parameters'
47
+
48
+ class Person < ActiveRecord::Base
49
+ self.attr_encrypted_options[:mode] = :per_attribute_iv_and_salt
50
+ attr_encrypted :email, :key => SECRET_KEY
51
+ attr_encrypted :credentials, :key => Proc.new { |user| Encryptor.encrypt(:value => user.salt, :key => SECRET_KEY) }, :marshal => true
52
+
53
+ after_initialize :initialize_salt_and_credentials
54
+
55
+ protected
56
+
57
+ def initialize_salt_and_credentials
58
+ self.salt ||= Digest::SHA256.hexdigest((Time.now.to_i * rand(1000)).to_s)[0..15]
59
+ self.credentials ||= { :username => 'example', :password => 'test' }
60
+ end
61
+ end
62
+
63
+ class PersonWithValidation < Person
64
+ validates_presence_of :email
65
+ end
66
+
67
+ class PersonWithProcMode < Person
68
+ attr_encrypted :email, :key => SECRET_KEY, :mode => Proc.new { :per_attribute_iv_and_salt }
69
+ attr_encrypted :credentials, :key => SECRET_KEY, :mode => Proc.new { :single_iv_and_salt }
70
+ end
71
+
72
+ class Account < ActiveRecord::Base
73
+ attr_accessor :key
74
+ attr_encrypted :password, :key => Proc.new {|account| account.key}
75
+ end
76
+
77
+ class PersonWithSerialization < ActiveRecord::Base
78
+ self.table_name = 'people'
79
+ attr_encrypted :email, :key => 'a secret key'
80
+ serialize :password
81
+ end
82
+
83
+ class UserWithProtectedAttribute < ActiveRecord::Base
84
+ self.table_name = 'users'
85
+ attr_encrypted :password, :key => 'a secret key'
86
+ end
87
+
88
+ class PersonUsingAlias < ActiveRecord::Base
89
+ self.table_name = 'people'
90
+ attr_encryptor :email, :key => 'a secret key'
91
+ end
92
+
93
+ class PrimeMinister < ActiveRecord::Base
94
+ attr_encrypted :name, :marshal => true, :key => 'SECRET_KEY'
95
+ end
96
+
97
+ class ActiveRecordTest < Minitest::Test
98
+
99
+ def setup
100
+ ActiveRecord::Base.connection.tables.each { |table| ActiveRecord::Base.connection.drop_table(table) }
101
+ create_tables
102
+ Account.create!(:key => SECRET_KEY, :password => "password")
103
+ end
104
+
105
+ def test_should_encrypt_email
106
+ @person = Person.create :email => 'test@example.com'
107
+ refute_nil @person.encrypted_email
108
+ refute_equal @person.email, @person.encrypted_email
109
+ assert_equal @person.email, Person.first.email
110
+ end
111
+
112
+ def test_should_marshal_and_encrypt_credentials
113
+ @person = Person.create
114
+ refute_nil @person.encrypted_credentials
115
+ refute_equal @person.credentials, @person.encrypted_credentials
116
+ assert_equal @person.credentials, Person.first.credentials
117
+ end
118
+
119
+ def test_should_encode_by_default
120
+ assert Person.attr_encrypted_options[:encode]
121
+ end
122
+
123
+ def test_should_validate_presence_of_email
124
+ @person = PersonWithValidation.new
125
+ assert !@person.valid?
126
+ assert !@person.errors[:email].empty? || @person.errors.on(:email)
127
+ end
128
+
129
+ def test_should_encrypt_decrypt_with_iv
130
+ @person = Person.create :email => 'test@example.com'
131
+ @person2 = Person.find(@person.id)
132
+ refute_nil @person2.encrypted_email_iv
133
+ assert_equal 'test@example.com', @person2.email
134
+ end
135
+
136
+ def test_should_ensure_attributes_can_be_deserialized
137
+ @person = PersonWithSerialization.new :email => 'test@example.com', :password => %w(an array of strings)
138
+ @person.save
139
+ assert_equal @person.password, %w(an array of strings)
140
+ end
141
+
142
+ def test_should_create_an_account_regardless_of_arguments_order
143
+ Account.create!(:key => SECRET_KEY, :password => "password")
144
+ Account.create!(:password => "password" , :key => SECRET_KEY)
145
+ end
146
+
147
+ def test_should_set_attributes_regardless_of_arguments_order
148
+ Account.new.attributes = { :password => "password" , :key => SECRET_KEY }
149
+ end
150
+
151
+ def test_should_preserve_hash_key_type
152
+ hash = { :foo => 'bar' }
153
+ account = Account.create!(:key => hash)
154
+ assert_equal account.key, hash
155
+ end
156
+
157
+ def test_should_assign_attributes
158
+ @user = UserWithProtectedAttribute.new :login => 'login', :is_admin => false
159
+ @user.attributes = ActionController::Parameters.new(:login => 'modified', :is_admin => true).permit(:login)
160
+ assert_equal 'modified', @user.login
161
+ end
162
+
163
+ def test_should_not_assign_protected_attributes
164
+ @user = UserWithProtectedAttribute.new :login => 'login', :is_admin => false
165
+ @user.attributes = ActionController::Parameters.new(:login => 'modified', :is_admin => true).permit(:login)
166
+ assert !@user.is_admin?
167
+ end
168
+
169
+ def test_should_raise_exception_if_not_permitted
170
+ @user = UserWithProtectedAttribute.new :login => 'login', :is_admin => false
171
+ assert_raises ActiveModel::ForbiddenAttributesError do
172
+ @user.attributes = ActionController::Parameters.new(:login => 'modified', :is_admin => true)
173
+ end
174
+ end
175
+
176
+ def test_should_raise_exception_on_init_if_not_permitted
177
+ assert_raises ActiveModel::ForbiddenAttributesError do
178
+ @user = UserWithProtectedAttribute.new ActionController::Parameters.new(:login => 'modified', :is_admin => true)
179
+ end
180
+ end
181
+
182
+ def test_should_allow_assignment_of_nil_attributes
183
+ @person = Person.new
184
+ assert_nil(@person.attributes = nil)
185
+ end
186
+
187
+ def test_should_allow_proc_based_mode
188
+ @person = PersonWithProcMode.create :email => 'test@example.com', :credentials => 'password123'
189
+
190
+ # Email is :per_attribute_iv_and_salt
191
+ assert_equal @person.class.encrypted_attributes[:email][:mode].class, Proc
192
+ assert_equal @person.class.encrypted_attributes[:email][:mode].call, :per_attribute_iv_and_salt
193
+ refute_nil @person.encrypted_email_salt
194
+ refute_nil @person.encrypted_email_iv
195
+
196
+ # Credentials is :single_iv_and_salt
197
+ assert_equal @person.class.encrypted_attributes[:credentials][:mode].class, Proc
198
+ assert_equal @person.class.encrypted_attributes[:credentials][:mode].call, :single_iv_and_salt
199
+ assert_nil @person.encrypted_credentials_salt
200
+ assert_nil @person.encrypted_credentials_iv
201
+ end
202
+
203
+ def test_should_allow_assign_attributes_with_nil
204
+ @person = Person.new
205
+ assert_nil(@person.assign_attributes nil)
206
+ end
207
+
208
+ def test_that_alias_encrypts_column
209
+ user = PersonUsingAlias.new
210
+ user.email = 'test@example.com'
211
+ user.save
212
+
213
+ refute_nil user.encrypted_email
214
+ refute_equal user.email, user.encrypted_email
215
+ assert_equal user.email, PersonUsingAlias.first.email
216
+ end
217
+
218
+ # See https://github.com/attr-encrypted/attr_encrypted/issues/68
219
+ def test_should_invalidate_virtual_attributes_on_reload
220
+ pm = PrimeMinister.new(:name => 'Winston Churchill')
221
+ pm.save!
222
+ assert_equal 'Winston Churchill', pm.name
223
+ pm.name = 'Neville Chamberlain'
224
+ assert_equal 'Neville Chamberlain', pm.name
225
+
226
+ result = pm.reload
227
+ assert_equal pm, result
228
+ assert_equal 'Winston Churchill', pm.name
229
+ end
230
+ end