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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.gitignore +6 -0
- data/.travis.yml +31 -0
- data/CHANGELOG.md +87 -0
- data/Gemfile +3 -0
- data/README.md +444 -0
- data/Rakefile +3 -15
- data/attr_encrypted.gemspec +63 -0
- data/certs/saghaulor.pem +21 -0
- data/checksum/attr_encrypted-3.0.0.gem.sha256 +1 -0
- data/checksum/attr_encrypted-3.0.0.gem.sha512 +1 -0
- data/checksum/attr_encrypted-3.0.1.gem.sha256 +1 -0
- data/checksum/attr_encrypted-3.0.1.gem.sha512 +1 -0
- data/checksum/attr_encrypted-3.0.2.gem.sha256 +1 -0
- data/checksum/attr_encrypted-3.0.2.gem.sha512 +1 -0
- data/lib/attr_encrypted/adapters/active_record.rb +38 -19
- data/lib/attr_encrypted/adapters/data_mapper.rb +1 -0
- data/lib/attr_encrypted/adapters/sequel.rb +1 -0
- data/lib/attr_encrypted/version.rb +3 -3
- data/lib/attr_encrypted.rb +198 -115
- data/test/active_record_test.rb +145 -88
- data/test/attr_encrypted_test.rb +101 -39
- data/test/compatibility_test.rb +37 -56
- data/test/data_mapper_test.rb +1 -1
- data/test/legacy_active_record_test.rb +17 -15
- data/test/legacy_attr_encrypted_test.rb +17 -16
- data/test/legacy_compatibility_test.rb +33 -44
- data/test/legacy_data_mapper_test.rb +6 -3
- data/test/legacy_sequel_test.rb +8 -4
- data/test/run.sh +12 -52
- data/test/sequel_test.rb +1 -1
- data/test/test_helper.rb +27 -17
- data.tar.gz.sig +0 -0
- metadata +75 -29
- metadata.gz.sig +0 -0
- data/README.rdoc +0 -344
data/test/active_record_test.rb
CHANGED
@@ -1,33 +1,41 @@
|
|
1
|
-
|
1
|
+
require_relative 'test_helper'
|
2
2
|
|
3
|
-
ActiveRecord::Base.establish_connection
|
3
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
|
4
4
|
|
5
5
|
def create_tables
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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, :
|
54
|
-
attr_encrypted :credentials, :
|
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
|
-
|
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 ||= { :
|
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, :
|
78
|
-
attr_encrypted :credentials, :
|
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
|
-
|
83
|
-
attr_encrypted :password, :
|
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, :
|
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, :
|
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, :
|
114
|
+
attr_encryptor :email, key: SECRET_KEY
|
101
115
|
end
|
102
116
|
|
103
117
|
class PrimeMinister < ActiveRecord::Base
|
104
|
-
attr_encrypted :name, :
|
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
|
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.
|
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.
|
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
|
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
|
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!(:
|
154
|
-
Account.create!(:
|
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
|
-
|
159
|
-
|
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!(:
|
169
|
-
|
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!(:
|
177
|
-
|
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
|
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
|
185
|
-
@user.attributes = ActionController::Parameters.new(:
|
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
|
191
|
-
@user.attributes = ActionController::Parameters.new(:
|
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
|
238
|
+
@user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
|
197
239
|
assert_raises ActiveModel::ForbiddenAttributesError do
|
198
|
-
@user.attributes = ActionController::Parameters.new(:
|
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(:
|
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
|
210
|
-
@user.attributes = {:
|
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
|
216
|
-
@user.attributes = {:
|
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
|
263
|
+
@user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
|
222
264
|
if ::ActiveRecord::VERSION::STRING > "3.1"
|
223
|
-
@user.send
|
265
|
+
@user.send(:assign_attributes, { login: 'modified', is_admin: true }, without_protection: true)
|
224
266
|
else
|
225
|
-
@user.send
|
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
|
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.
|
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
|
-
|
272
|
-
|
273
|
-
|
274
|
-
pm.name
|
275
|
-
|
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
|
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
|