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.
- 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
|