attr_encrypted 1.4.0 → 3.0.3

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