attr_encrypted-magicless 1.3.42

Sign up to get free protection for your applications and to get access to all the features.
@@ -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