attr_encrypted 1.4.0 → 3.0.3

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,33 +1,41 @@
1
- require File.expand_path('../test_helper', __FILE__)
1
+ require_relative 'test_helper'
2
2
 
3
- ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:'
3
+ ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
4
4
 
5
5
  def create_tables
6
- silence_stream(STDOUT) do
7
- ActiveRecord::Schema.define(:version => 1) do
8
- create_table :people do |t|
9
- t.string :encrypted_email
10
- t.string :password
11
- t.string :encrypted_credentials
12
- t.binary :salt
13
- t.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
6
+ ActiveRecord::Schema.define(version: 1) do
7
+ create_table :people do |t|
8
+ t.string :encrypted_email
9
+ t.string :password
10
+ t.string :encrypted_credentials
11
+ t.binary :salt
12
+ t.binary :key_iv
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
+ t.binary :key
23
+ end
24
+ create_table :users do |t|
25
+ t.string :login
26
+ t.string :encrypted_password
27
+ t.string :encrypted_password_iv
28
+ t.boolean :is_admin
29
+ end
30
+ create_table :prime_ministers do |t|
31
+ t.string :encrypted_name
32
+ t.string :encrypted_name_iv
33
+ end
34
+ create_table :addresses do |t|
35
+ t.binary :encrypted_street
36
+ t.binary :encrypted_street_iv
37
+ t.binary :encrypted_zipcode
38
+ t.string :mode
31
39
  end
32
40
  end
33
41
  end
@@ -36,7 +44,6 @@ end
36
44
  create_tables
37
45
 
38
46
  ActiveRecord::MissingAttributeError = ActiveModel::MissingAttributeError unless defined?(ActiveRecord::MissingAttributeError)
39
- ActiveRecord::Base.logger = Logger.new(nil) if ::ActiveRecord::VERSION::STRING < "3.0"
40
47
 
41
48
  if ::ActiveRecord::VERSION::STRING > "4.0"
42
49
  module Rack
@@ -50,22 +57,17 @@ end
50
57
 
51
58
  class Person < ActiveRecord::Base
52
59
  self.attr_encrypted_options[:mode] = :per_attribute_iv_and_salt
53
- attr_encrypted :email, :key => SECRET_KEY
54
- attr_encrypted :credentials, :key => Proc.new { |user| Encryptor.encrypt(:value => user.salt, :key => SECRET_KEY) }, :marshal => true
60
+ attr_encrypted :email, key: SECRET_KEY
61
+ attr_encrypted :credentials, key: Proc.new { |user| Encryptor.encrypt(value: user.salt, key: SECRET_KEY, iv: user.key_iv) }, marshal: true
55
62
 
56
- if ActiveRecord::VERSION::STRING < "3"
57
- def after_initialize
58
- initialize_salt_and_credentials
59
- end
60
- else
61
- after_initialize :initialize_salt_and_credentials
62
- end
63
+ after_initialize :initialize_salt_and_credentials
63
64
 
64
65
  protected
65
66
 
66
67
  def initialize_salt_and_credentials
68
+ self.key_iv ||= SecureRandom.random_bytes(12)
67
69
  self.salt ||= Digest::SHA256.hexdigest((Time.now.to_i * rand(1000)).to_s)[0..15]
68
- self.credentials ||= { :username => 'example', :password => 'test' }
70
+ self.credentials ||= { username: 'example', password: 'test' }
69
71
  end
70
72
  end
71
73
 
@@ -74,34 +76,53 @@ class PersonWithValidation < Person
74
76
  end
75
77
 
76
78
  class PersonWithProcMode < Person
77
- attr_encrypted :email, :key => SECRET_KEY, :mode => Proc.new { :per_attribute_iv_and_salt }
78
- attr_encrypted :credentials, :key => SECRET_KEY, :mode => Proc.new { :single_iv_and_salt }
79
+ attr_encrypted :email, key: SECRET_KEY, mode: Proc.new { :per_attribute_iv_and_salt }
80
+ attr_encrypted :credentials, key: SECRET_KEY, mode: Proc.new { :single_iv_and_salt }, insecure_mode: true
79
81
  end
80
82
 
81
83
  class Account < ActiveRecord::Base
82
- attr_accessor :key
83
- attr_encrypted :password, :key => Proc.new {|account| account.key}
84
+ ACCOUNT_ENCRYPTION_KEY = SecureRandom.base64(32)
85
+ attr_encrypted :password, key: :password_encryption_key
86
+
87
+ def encrypting?(attr)
88
+ encrypted_attributes[attr][:operation] == :encrypting
89
+ end
90
+
91
+ def password_encryption_key
92
+ if encrypting?(:password)
93
+ self.key = ACCOUNT_ENCRYPTION_KEY
94
+ else
95
+ self.key
96
+ end
97
+ end
84
98
  end
85
99
 
86
100
  class PersonWithSerialization < ActiveRecord::Base
87
101
  self.table_name = 'people'
88
- attr_encrypted :email, :key => 'a secret key'
102
+ attr_encrypted :email, key: SECRET_KEY
89
103
  serialize :password
90
104
  end
91
105
 
92
106
  class UserWithProtectedAttribute < ActiveRecord::Base
93
107
  self.table_name = 'users'
94
- attr_encrypted :password, :key => 'a secret key'
108
+ attr_encrypted :password, key: SECRET_KEY
95
109
  attr_protected :is_admin if ::ActiveRecord::VERSION::STRING < "4.0"
96
110
  end
97
111
 
98
112
  class PersonUsingAlias < ActiveRecord::Base
99
113
  self.table_name = 'people'
100
- attr_encryptor :email, :key => 'a secret key'
114
+ attr_encryptor :email, key: SECRET_KEY
101
115
  end
102
116
 
103
117
  class PrimeMinister < ActiveRecord::Base
104
- attr_encrypted :name, :marshal => true, :key => 'SECRET_KEY'
118
+ attr_encrypted :name, marshal: true, key: SECRET_KEY
119
+ end
120
+
121
+ class Address < ActiveRecord::Base
122
+ self.attr_encrypted_options[:marshal] = false
123
+ self.attr_encrypted_options[:encode] = false
124
+ attr_encrypted :street, encode_iv: false, key: SECRET_KEY
125
+ attr_encrypted :zipcode, key: SECRET_KEY, mode: Proc.new { |address| address.mode.to_sym }, insecure_mode: true
105
126
  end
106
127
 
107
128
  class ActiveRecordTest < Minitest::Test
@@ -109,21 +130,20 @@ class ActiveRecordTest < Minitest::Test
109
130
  def setup
110
131
  ActiveRecord::Base.connection.tables.each { |table| ActiveRecord::Base.connection.drop_table(table) }
111
132
  create_tables
112
- Account.create!(:key => SECRET_KEY, :password => "password")
113
133
  end
114
134
 
115
135
  def test_should_encrypt_email
116
- @person = Person.create :email => 'test@example.com'
136
+ @person = Person.create(email: 'test@example.com')
117
137
  refute_nil @person.encrypted_email
118
138
  refute_equal @person.email, @person.encrypted_email
119
- assert_equal @person.email, Person.find(:first).email
139
+ assert_equal @person.email, Person.first.email
120
140
  end
121
141
 
122
142
  def test_should_marshal_and_encrypt_credentials
123
143
  @person = Person.create
124
144
  refute_nil @person.encrypted_credentials
125
145
  refute_equal @person.credentials, @person.encrypted_credentials
126
- assert_equal @person.credentials, Person.find(:first).credentials
146
+ assert_equal @person.credentials, Person.first.credentials
127
147
  end
128
148
 
129
149
  def test_should_encode_by_default
@@ -137,92 +157,114 @@ class ActiveRecordTest < Minitest::Test
137
157
  end
138
158
 
139
159
  def test_should_encrypt_decrypt_with_iv
140
- @person = Person.create :email => 'test@example.com'
160
+ @person = Person.create(email: 'test@example.com')
141
161
  @person2 = Person.find(@person.id)
142
162
  refute_nil @person2.encrypted_email_iv
143
163
  assert_equal 'test@example.com', @person2.email
144
164
  end
145
165
 
146
166
  def test_should_ensure_attributes_can_be_deserialized
147
- @person = PersonWithSerialization.new :email => 'test@example.com', :password => %w(an array of strings)
167
+ @person = PersonWithSerialization.new(email: 'test@example.com', password: %w(an array of strings))
148
168
  @person.save
149
169
  assert_equal @person.password, %w(an array of strings)
150
170
  end
151
171
 
152
172
  def test_should_create_an_account_regardless_of_arguments_order
153
- Account.create!(:key => SECRET_KEY, :password => "password")
154
- Account.create!(:password => "password" , :key => SECRET_KEY)
173
+ Account.create!(key: SECRET_KEY, password: "password")
174
+ Account.create!(password: "password" , key: SECRET_KEY)
155
175
  end
156
176
 
157
177
  def test_should_set_attributes_regardless_of_arguments_order
158
- Account.new.attributes = { :password => "password" , :key => SECRET_KEY }
159
- end
160
-
161
- def test_should_preserve_hash_key_type
162
- hash = { :foo => 'bar' }
163
- account = Account.create!(:key => hash)
164
- assert_equal account.key, hash
178
+ # minitest does not implement `assert_nothing_raised` https://github.com/seattlerb/minitest/issues/112
179
+ Account.new.attributes = { password: "password", key: SECRET_KEY }
165
180
  end
166
181
 
167
182
  def test_should_create_changed_predicate
168
- person = Person.create!(:email => 'test@example.com')
169
- assert !person.email_changed?
183
+ person = Person.create!(email: 'test@example.com')
184
+ refute person.email_changed?
185
+ person.email = 'test@example.com'
186
+ refute person.email_changed?
187
+ person.email = nil
188
+ assert person.email_changed?
170
189
  person.email = 'test2@example.com'
171
190
  assert person.email_changed?
172
191
  end
173
192
 
174
193
  def test_should_create_was_predicate
175
194
  original_email = 'test@example.com'
176
- person = Person.create!(:email => original_email)
177
- assert !person.email_was
195
+ person = Person.create!(email: original_email)
196
+ assert_equal original_email, person.email_was
178
197
  person.email = 'test2@example.com'
179
- assert_equal person.email_was, original_email
198
+ assert_equal original_email, person.email_was
199
+ old_pm_name = "Winston Churchill"
200
+ pm = PrimeMinister.create!(name: old_pm_name)
201
+ assert_equal old_pm_name, pm.name_was
202
+ old_zipcode = "90210"
203
+ address = Address.create!(zipcode: old_zipcode, mode: "single_iv_and_salt")
204
+ assert_equal old_zipcode, address.zipcode_was
205
+ end
206
+
207
+ def test_attribute_was_works_when_options_for_old_encrypted_value_are_different_than_options_for_new_encrypted_value
208
+ pw = 'password'
209
+ crypto_key = SecureRandom.base64(32)
210
+ old_iv = SecureRandom.random_bytes(12)
211
+ account = Account.create
212
+ encrypted_value = Encryptor.encrypt(value: pw, iv: old_iv, key: crypto_key)
213
+ Account.where(id: account.id).update_all(key: crypto_key, encrypted_password_iv: [old_iv].pack('m'), encrypted_password: [encrypted_value].pack('m'))
214
+ account = Account.find(account.id)
215
+ assert_equal pw, account.password
216
+ account.password = pw.reverse
217
+ assert_equal pw, account.password_was
218
+ account.save
219
+ account.reload
220
+ assert_equal Account::ACCOUNT_ENCRYPTION_KEY, account.key
221
+ assert_equal pw.reverse, account.password
180
222
  end
181
223
 
182
224
  if ::ActiveRecord::VERSION::STRING > "4.0"
183
225
  def test_should_assign_attributes
184
- @user = UserWithProtectedAttribute.new :login => 'login', :is_admin => false
185
- @user.attributes = ActionController::Parameters.new(:login => 'modified', :is_admin => true).permit(:login)
226
+ @user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
227
+ @user.attributes = ActionController::Parameters.new(login: 'modified', is_admin: true).permit(:login)
186
228
  assert_equal 'modified', @user.login
187
229
  end
188
230
 
189
231
  def test_should_not_assign_protected_attributes
190
- @user = UserWithProtectedAttribute.new :login => 'login', :is_admin => false
191
- @user.attributes = ActionController::Parameters.new(:login => 'modified', :is_admin => true).permit(:login)
232
+ @user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
233
+ @user.attributes = ActionController::Parameters.new(login: 'modified', is_admin: true).permit(:login)
192
234
  assert !@user.is_admin?
193
235
  end
194
236
 
195
237
  def test_should_raise_exception_if_not_permitted
196
- @user = UserWithProtectedAttribute.new :login => 'login', :is_admin => false
238
+ @user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
197
239
  assert_raises ActiveModel::ForbiddenAttributesError do
198
- @user.attributes = ActionController::Parameters.new(:login => 'modified', :is_admin => true)
240
+ @user.attributes = ActionController::Parameters.new(login: 'modified', is_admin: true)
199
241
  end
200
242
  end
201
243
 
202
244
  def test_should_raise_exception_on_init_if_not_permitted
203
245
  assert_raises ActiveModel::ForbiddenAttributesError do
204
- @user = UserWithProtectedAttribute.new ActionController::Parameters.new(:login => 'modified', :is_admin => true)
246
+ @user = UserWithProtectedAttribute.new ActionController::Parameters.new(login: 'modified', is_admin: true)
205
247
  end
206
248
  end
207
249
  else
208
250
  def test_should_assign_attributes
209
- @user = UserWithProtectedAttribute.new :login => 'login', :is_admin => false
210
- @user.attributes = {:login => 'modified', :is_admin => true}
251
+ @user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
252
+ @user.attributes = { login: 'modified', is_admin: true }
211
253
  assert_equal 'modified', @user.login
212
254
  end
213
255
 
214
256
  def test_should_not_assign_protected_attributes
215
- @user = UserWithProtectedAttribute.new :login => 'login', :is_admin => false
216
- @user.attributes = {:login => 'modified', :is_admin => true}
257
+ @user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
258
+ @user.attributes = { login: 'modified', is_admin: true }
217
259
  assert !@user.is_admin?
218
260
  end
219
261
 
220
262
  def test_should_assign_protected_attributes
221
- @user = UserWithProtectedAttribute.new :login => 'login', :is_admin => false
263
+ @user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
222
264
  if ::ActiveRecord::VERSION::STRING > "3.1"
223
- @user.send :assign_attributes, {:login => 'modified', :is_admin => true}, :without_protection => true
265
+ @user.send(:assign_attributes, { login: 'modified', is_admin: true }, without_protection: true)
224
266
  else
225
- @user.send :attributes=, {:login => 'modified', :is_admin => true}, false
267
+ @user.send(:attributes=, { login: 'modified', is_admin: true }, false)
226
268
  end
227
269
  assert @user.is_admin?
228
270
  end
@@ -234,7 +276,7 @@ class ActiveRecordTest < Minitest::Test
234
276
  end
235
277
 
236
278
  def test_should_allow_proc_based_mode
237
- @person = PersonWithProcMode.create :email => 'test@example.com', :credentials => 'password123'
279
+ @person = PersonWithProcMode.create(email: 'test@example.com', credentials: 'password123')
238
280
 
239
281
  # Email is :per_attribute_iv_and_salt
240
282
  assert_equal @person.class.encrypted_attributes[:email][:mode].class, Proc
@@ -263,19 +305,34 @@ class ActiveRecordTest < Minitest::Test
263
305
 
264
306
  refute_nil user.encrypted_email
265
307
  refute_equal user.email, user.encrypted_email
266
- assert_equal user.email, PersonUsingAlias.find(:first).email
308
+ assert_equal user.email, PersonUsingAlias.first.email
267
309
  end
268
310
 
269
311
  # See https://github.com/attr-encrypted/attr_encrypted/issues/68
270
312
  def test_should_invalidate_virtual_attributes_on_reload
271
- pm = PrimeMinister.new(:name => 'Winston Churchill')
272
- pm.save!
273
- assert_equal 'Winston Churchill', pm.name
274
- pm.name = 'Neville Chamberlain'
275
- assert_equal 'Neville Chamberlain', pm.name
313
+ old_pm_name = 'Winston Churchill'
314
+ new_pm_name = 'Neville Chamberlain'
315
+ pm = PrimeMinister.create!(name: old_pm_name)
316
+ assert_equal old_pm_name, pm.name
317
+ pm.name = new_pm_name
318
+ assert_equal new_pm_name, pm.name
276
319
 
277
320
  result = pm.reload
278
321
  assert_equal pm, result
279
- assert_equal 'Winston Churchill', pm.name
322
+ assert_equal old_pm_name, pm.name
323
+ end
324
+
325
+ def test_should_save_encrypted_data_as_binary
326
+ street = '123 Elm'
327
+ address = Address.create!(street: street)
328
+ refute_equal address.encrypted_street, street
329
+ assert_equal Address.first.street, street
330
+ end
331
+
332
+ def test_should_evaluate_proc_based_mode
333
+ street = '123 Elm'
334
+ zipcode = '12345'
335
+ address = Address.new(street: street, zipcode: zipcode, mode: :single_iv_and_salt)
336
+ assert_nil address.encrypted_zipcode_iv
280
337
  end
281
338
  end