attr_encrypted 1.3.5 → 3.0.0
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 +7 -0
- checksums.yaml.gz.sig +0 -0
- data/.gitignore +6 -0
- data/.travis.yml +24 -0
- data/CHANGELOG.md +76 -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/lib/attr_encrypted/adapters/active_record.rb +28 -9
- 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 +99 -54
- data/test/attr_encrypted_test.rb +101 -39
- data/test/compatibility_test.rb +19 -36
- data/test/data_mapper_test.rb +1 -1
- data/test/legacy_active_record_test.rb +11 -7
- data/test/legacy_attr_encrypted_test.rb +17 -16
- data/test/legacy_compatibility_test.rb +21 -30
- 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 +98 -84
- metadata.gz.sig +0 -0
- data/README.rdoc +0 -344
data/test/active_record_test.rb
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
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
6
|
silence_stream(STDOUT) do
|
|
7
|
-
ActiveRecord::Schema.define(:
|
|
7
|
+
ActiveRecord::Schema.define(version: 1) do
|
|
8
8
|
create_table :people do |t|
|
|
9
9
|
t.string :encrypted_email
|
|
10
10
|
t.string :password
|
|
11
11
|
t.string :encrypted_credentials
|
|
12
12
|
t.binary :salt
|
|
13
|
+
t.binary :key_iv
|
|
13
14
|
t.string :encrypted_email_salt
|
|
14
15
|
t.string :encrypted_credentials_salt
|
|
15
16
|
t.string :encrypted_email_iv
|
|
@@ -23,10 +24,18 @@ def create_tables
|
|
|
23
24
|
create_table :users do |t|
|
|
24
25
|
t.string :login
|
|
25
26
|
t.string :encrypted_password
|
|
27
|
+
t.string :encrypted_password_iv
|
|
26
28
|
t.boolean :is_admin
|
|
27
29
|
end
|
|
28
30
|
create_table :prime_ministers do |t|
|
|
29
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
|
|
30
39
|
end
|
|
31
40
|
end
|
|
32
41
|
end
|
|
@@ -36,7 +45,6 @@ end
|
|
|
36
45
|
create_tables
|
|
37
46
|
|
|
38
47
|
ActiveRecord::MissingAttributeError = ActiveModel::MissingAttributeError unless defined?(ActiveRecord::MissingAttributeError)
|
|
39
|
-
ActiveRecord::Base.logger = Logger.new(nil) if ::ActiveRecord::VERSION::STRING < "3.0"
|
|
40
48
|
|
|
41
49
|
if ::ActiveRecord::VERSION::STRING > "4.0"
|
|
42
50
|
module Rack
|
|
@@ -50,22 +58,17 @@ end
|
|
|
50
58
|
|
|
51
59
|
class Person < ActiveRecord::Base
|
|
52
60
|
self.attr_encrypted_options[:mode] = :per_attribute_iv_and_salt
|
|
53
|
-
attr_encrypted :email, :
|
|
54
|
-
attr_encrypted :credentials, :
|
|
61
|
+
attr_encrypted :email, key: SECRET_KEY
|
|
62
|
+
attr_encrypted :credentials, key: Proc.new { |user| Encryptor.encrypt(value: user.salt, key: SECRET_KEY, iv: user.key_iv) }, marshal: true
|
|
55
63
|
|
|
56
|
-
|
|
57
|
-
def after_initialize
|
|
58
|
-
initialize_salt_and_credentials
|
|
59
|
-
end
|
|
60
|
-
else
|
|
61
|
-
after_initialize :initialize_salt_and_credentials
|
|
62
|
-
end
|
|
64
|
+
after_initialize :initialize_salt_and_credentials
|
|
63
65
|
|
|
64
66
|
protected
|
|
65
67
|
|
|
66
68
|
def initialize_salt_and_credentials
|
|
69
|
+
self.key_iv ||= SecureRandom.random_bytes(12)
|
|
67
70
|
self.salt ||= Digest::SHA256.hexdigest((Time.now.to_i * rand(1000)).to_s)[0..15]
|
|
68
|
-
self.credentials ||= { :
|
|
71
|
+
self.credentials ||= { username: 'example', password: 'test' }
|
|
69
72
|
end
|
|
70
73
|
end
|
|
71
74
|
|
|
@@ -74,34 +77,41 @@ class PersonWithValidation < Person
|
|
|
74
77
|
end
|
|
75
78
|
|
|
76
79
|
class PersonWithProcMode < Person
|
|
77
|
-
attr_encrypted :email, :
|
|
78
|
-
attr_encrypted :credentials, :
|
|
80
|
+
attr_encrypted :email, key: SECRET_KEY, mode: Proc.new { :per_attribute_iv_and_salt }
|
|
81
|
+
attr_encrypted :credentials, key: SECRET_KEY, mode: Proc.new { :single_iv_and_salt }, insecure_mode: true
|
|
79
82
|
end
|
|
80
83
|
|
|
81
84
|
class Account < ActiveRecord::Base
|
|
82
85
|
attr_accessor :key
|
|
83
|
-
attr_encrypted :password, :
|
|
86
|
+
attr_encrypted :password, key: Proc.new {|account| account.key}
|
|
84
87
|
end
|
|
85
88
|
|
|
86
89
|
class PersonWithSerialization < ActiveRecord::Base
|
|
87
90
|
self.table_name = 'people'
|
|
88
|
-
attr_encrypted :email, :
|
|
91
|
+
attr_encrypted :email, key: SECRET_KEY
|
|
89
92
|
serialize :password
|
|
90
93
|
end
|
|
91
94
|
|
|
92
95
|
class UserWithProtectedAttribute < ActiveRecord::Base
|
|
93
96
|
self.table_name = 'users'
|
|
94
|
-
attr_encrypted :password, :
|
|
97
|
+
attr_encrypted :password, key: SECRET_KEY
|
|
95
98
|
attr_protected :is_admin if ::ActiveRecord::VERSION::STRING < "4.0"
|
|
96
99
|
end
|
|
97
100
|
|
|
98
101
|
class PersonUsingAlias < ActiveRecord::Base
|
|
99
102
|
self.table_name = 'people'
|
|
100
|
-
attr_encryptor :email, :
|
|
103
|
+
attr_encryptor :email, key: SECRET_KEY
|
|
101
104
|
end
|
|
102
105
|
|
|
103
106
|
class PrimeMinister < ActiveRecord::Base
|
|
104
|
-
attr_encrypted :name, :
|
|
107
|
+
attr_encrypted :name, marshal: true, key: SECRET_KEY
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
class Address < ActiveRecord::Base
|
|
111
|
+
self.attr_encrypted_options[:marshal] = false
|
|
112
|
+
self.attr_encrypted_options[:encode] = false
|
|
113
|
+
attr_encrypted :street, encode_iv: false, key: SECRET_KEY
|
|
114
|
+
attr_encrypted :zipcode, key: SECRET_KEY, mode: Proc.new { |address| address.mode.to_sym }, insecure_mode: true
|
|
105
115
|
end
|
|
106
116
|
|
|
107
117
|
class ActiveRecordTest < Minitest::Test
|
|
@@ -109,21 +119,21 @@ class ActiveRecordTest < Minitest::Test
|
|
|
109
119
|
def setup
|
|
110
120
|
ActiveRecord::Base.connection.tables.each { |table| ActiveRecord::Base.connection.drop_table(table) }
|
|
111
121
|
create_tables
|
|
112
|
-
Account.create!(:
|
|
122
|
+
Account.create!(key: SECRET_KEY, password: "password")
|
|
113
123
|
end
|
|
114
124
|
|
|
115
125
|
def test_should_encrypt_email
|
|
116
|
-
@person = Person.create
|
|
126
|
+
@person = Person.create(email: 'test@example.com')
|
|
117
127
|
refute_nil @person.encrypted_email
|
|
118
128
|
refute_equal @person.email, @person.encrypted_email
|
|
119
|
-
assert_equal @person.email, Person.
|
|
129
|
+
assert_equal @person.email, Person.first.email
|
|
120
130
|
end
|
|
121
131
|
|
|
122
132
|
def test_should_marshal_and_encrypt_credentials
|
|
123
133
|
@person = Person.create
|
|
124
134
|
refute_nil @person.encrypted_credentials
|
|
125
135
|
refute_equal @person.credentials, @person.encrypted_credentials
|
|
126
|
-
assert_equal @person.credentials, Person.
|
|
136
|
+
assert_equal @person.credentials, Person.first.credentials
|
|
127
137
|
end
|
|
128
138
|
|
|
129
139
|
def test_should_encode_by_default
|
|
@@ -137,77 +147,97 @@ class ActiveRecordTest < Minitest::Test
|
|
|
137
147
|
end
|
|
138
148
|
|
|
139
149
|
def test_should_encrypt_decrypt_with_iv
|
|
140
|
-
@person = Person.create
|
|
150
|
+
@person = Person.create(email: 'test@example.com')
|
|
141
151
|
@person2 = Person.find(@person.id)
|
|
142
152
|
refute_nil @person2.encrypted_email_iv
|
|
143
153
|
assert_equal 'test@example.com', @person2.email
|
|
144
154
|
end
|
|
145
155
|
|
|
146
156
|
def test_should_ensure_attributes_can_be_deserialized
|
|
147
|
-
@person = PersonWithSerialization.new
|
|
157
|
+
@person = PersonWithSerialization.new(email: 'test@example.com', password: %w(an array of strings))
|
|
148
158
|
@person.save
|
|
149
159
|
assert_equal @person.password, %w(an array of strings)
|
|
150
160
|
end
|
|
151
161
|
|
|
152
162
|
def test_should_create_an_account_regardless_of_arguments_order
|
|
153
|
-
Account.create!(:
|
|
154
|
-
Account.create!(:
|
|
163
|
+
Account.create!(key: SECRET_KEY, password: "password")
|
|
164
|
+
Account.create!(password: "password" , key: SECRET_KEY)
|
|
155
165
|
end
|
|
156
166
|
|
|
157
167
|
def test_should_set_attributes_regardless_of_arguments_order
|
|
158
|
-
|
|
168
|
+
# minitest does not implement `assert_nothing_raised` https://github.com/seattlerb/minitest/issues/112
|
|
169
|
+
Account.new.attributes = { password: "password", key: SECRET_KEY }
|
|
159
170
|
end
|
|
160
171
|
|
|
161
172
|
def test_should_preserve_hash_key_type
|
|
162
|
-
hash = { :
|
|
163
|
-
account = Account.create!(:
|
|
173
|
+
hash = { foo: 'bar' }
|
|
174
|
+
account = Account.create!(key: hash)
|
|
164
175
|
assert_equal account.key, hash
|
|
165
176
|
end
|
|
166
177
|
|
|
178
|
+
def test_should_create_changed_predicate
|
|
179
|
+
person = Person.create!(email: 'test@example.com')
|
|
180
|
+
refute person.email_changed?
|
|
181
|
+
person.email = 'test@example.com'
|
|
182
|
+
refute person.email_changed?
|
|
183
|
+
person.email = nil
|
|
184
|
+
assert person.email_changed?
|
|
185
|
+
person.email = 'test2@example.com'
|
|
186
|
+
assert person.email_changed?
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def test_should_create_was_predicate
|
|
190
|
+
original_email = 'test@example.com'
|
|
191
|
+
person = Person.create!(email: original_email)
|
|
192
|
+
assert_equal original_email, person.email_was
|
|
193
|
+
person.email = 'test2@example.com'
|
|
194
|
+
assert_equal original_email, person.email_was
|
|
195
|
+
end
|
|
196
|
+
|
|
167
197
|
if ::ActiveRecord::VERSION::STRING > "4.0"
|
|
168
198
|
def test_should_assign_attributes
|
|
169
|
-
@user = UserWithProtectedAttribute.new
|
|
170
|
-
@user.attributes = ActionController::Parameters.new(:
|
|
199
|
+
@user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
|
|
200
|
+
@user.attributes = ActionController::Parameters.new(login: 'modified', is_admin: true).permit(:login)
|
|
171
201
|
assert_equal 'modified', @user.login
|
|
172
202
|
end
|
|
173
203
|
|
|
174
204
|
def test_should_not_assign_protected_attributes
|
|
175
|
-
@user = UserWithProtectedAttribute.new
|
|
176
|
-
@user.attributes = ActionController::Parameters.new(:
|
|
205
|
+
@user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
|
|
206
|
+
@user.attributes = ActionController::Parameters.new(login: 'modified', is_admin: true).permit(:login)
|
|
177
207
|
assert !@user.is_admin?
|
|
178
208
|
end
|
|
179
209
|
|
|
180
210
|
def test_should_raise_exception_if_not_permitted
|
|
181
|
-
@user = UserWithProtectedAttribute.new
|
|
211
|
+
@user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
|
|
182
212
|
assert_raises ActiveModel::ForbiddenAttributesError do
|
|
183
|
-
@user.attributes = ActionController::Parameters.new(:
|
|
213
|
+
@user.attributes = ActionController::Parameters.new(login: 'modified', is_admin: true)
|
|
184
214
|
end
|
|
185
215
|
end
|
|
186
216
|
|
|
187
217
|
def test_should_raise_exception_on_init_if_not_permitted
|
|
188
218
|
assert_raises ActiveModel::ForbiddenAttributesError do
|
|
189
|
-
@user = UserWithProtectedAttribute.new ActionController::Parameters.new(:
|
|
219
|
+
@user = UserWithProtectedAttribute.new ActionController::Parameters.new(login: 'modified', is_admin: true)
|
|
190
220
|
end
|
|
191
221
|
end
|
|
192
222
|
else
|
|
193
223
|
def test_should_assign_attributes
|
|
194
|
-
@user = UserWithProtectedAttribute.new
|
|
195
|
-
@user.attributes = {:
|
|
224
|
+
@user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
|
|
225
|
+
@user.attributes = { login: 'modified', is_admin: true }
|
|
196
226
|
assert_equal 'modified', @user.login
|
|
197
227
|
end
|
|
198
228
|
|
|
199
229
|
def test_should_not_assign_protected_attributes
|
|
200
|
-
@user = UserWithProtectedAttribute.new
|
|
201
|
-
@user.attributes = {:
|
|
230
|
+
@user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
|
|
231
|
+
@user.attributes = { login: 'modified', is_admin: true }
|
|
202
232
|
assert !@user.is_admin?
|
|
203
233
|
end
|
|
204
234
|
|
|
205
235
|
def test_should_assign_protected_attributes
|
|
206
|
-
@user = UserWithProtectedAttribute.new
|
|
236
|
+
@user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
|
|
207
237
|
if ::ActiveRecord::VERSION::STRING > "3.1"
|
|
208
|
-
@user.send
|
|
238
|
+
@user.send(:assign_attributes, { login: 'modified', is_admin: true }, without_protection: true)
|
|
209
239
|
else
|
|
210
|
-
@user.send
|
|
240
|
+
@user.send(:attributes=, { login: 'modified', is_admin: true }, false)
|
|
211
241
|
end
|
|
212
242
|
assert @user.is_admin?
|
|
213
243
|
end
|
|
@@ -219,7 +249,7 @@ class ActiveRecordTest < Minitest::Test
|
|
|
219
249
|
end
|
|
220
250
|
|
|
221
251
|
def test_should_allow_proc_based_mode
|
|
222
|
-
@person = PersonWithProcMode.create
|
|
252
|
+
@person = PersonWithProcMode.create(email: 'test@example.com', credentials: 'password123')
|
|
223
253
|
|
|
224
254
|
# Email is :per_attribute_iv_and_salt
|
|
225
255
|
assert_equal @person.class.encrypted_attributes[:email][:mode].class, Proc
|
|
@@ -248,19 +278,34 @@ class ActiveRecordTest < Minitest::Test
|
|
|
248
278
|
|
|
249
279
|
refute_nil user.encrypted_email
|
|
250
280
|
refute_equal user.email, user.encrypted_email
|
|
251
|
-
assert_equal user.email, PersonUsingAlias.
|
|
281
|
+
assert_equal user.email, PersonUsingAlias.first.email
|
|
252
282
|
end
|
|
253
283
|
|
|
254
284
|
# See https://github.com/attr-encrypted/attr_encrypted/issues/68
|
|
255
285
|
def test_should_invalidate_virtual_attributes_on_reload
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
pm.name
|
|
260
|
-
|
|
286
|
+
old_pm_name = 'Winston Churchill'
|
|
287
|
+
new_pm_name = 'Neville Chamberlain'
|
|
288
|
+
pm = PrimeMinister.create!(name: old_pm_name)
|
|
289
|
+
assert_equal old_pm_name, pm.name
|
|
290
|
+
pm.name = new_pm_name
|
|
291
|
+
assert_equal new_pm_name, pm.name
|
|
261
292
|
|
|
262
293
|
result = pm.reload
|
|
263
294
|
assert_equal pm, result
|
|
264
|
-
assert_equal
|
|
295
|
+
assert_equal old_pm_name, pm.name
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def test_should_save_encrypted_data_as_binary
|
|
299
|
+
street = '123 Elm'
|
|
300
|
+
address = Address.create!(street: street)
|
|
301
|
+
refute_equal address.encrypted_street, street
|
|
302
|
+
assert_equal Address.first.street, street
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def test_should_evaluate_proc_based_mode
|
|
306
|
+
street = '123 Elm'
|
|
307
|
+
zipcode = '12345'
|
|
308
|
+
address = Address.new(street: street, zipcode: zipcode, mode: :single_iv_and_salt)
|
|
309
|
+
assert_nil address.encrypted_zipcode_iv
|
|
265
310
|
end
|
|
266
311
|
end
|
data/test/attr_encrypted_test.rb
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# encoding: UTF-8
|
|
2
|
-
|
|
2
|
+
require_relative 'test_helper'
|
|
3
3
|
|
|
4
4
|
class SillyEncryptor
|
|
5
5
|
def self.silly_encrypt(options)
|
|
@@ -12,6 +12,7 @@ class SillyEncryptor
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
class User
|
|
15
|
+
extend AttrEncrypted
|
|
15
16
|
self.attr_encrypted_options[:key] = Proc.new { |user| SECRET_KEY } # default key
|
|
16
17
|
self.attr_encrypted_options[:mode] = :per_attribute_iv_and_salt
|
|
17
18
|
|
|
@@ -33,7 +34,8 @@ class User
|
|
|
33
34
|
attr_accessor :salt
|
|
34
35
|
attr_accessor :should_encrypt
|
|
35
36
|
|
|
36
|
-
def initialize
|
|
37
|
+
def initialize(email: nil)
|
|
38
|
+
self.email = email
|
|
37
39
|
self.salt = Time.now.to_i.to_s
|
|
38
40
|
self.should_encrypt = true
|
|
39
41
|
end
|
|
@@ -48,19 +50,40 @@ class Admin < User
|
|
|
48
50
|
end
|
|
49
51
|
|
|
50
52
|
class SomeOtherClass
|
|
53
|
+
extend AttrEncrypted
|
|
51
54
|
def self.call(object)
|
|
52
55
|
object.class
|
|
53
56
|
end
|
|
54
57
|
end
|
|
55
58
|
|
|
59
|
+
class YetAnotherClass
|
|
60
|
+
extend AttrEncrypted
|
|
61
|
+
self.attr_encrypted_options[:encode_iv] = false
|
|
62
|
+
|
|
63
|
+
attr_encrypted :email, :key => SECRET_KEY
|
|
64
|
+
attr_encrypted :phone_number, :key => SECRET_KEY, mode: Proc.new { |thing| thing.mode }, encode_iv: Proc.new { |thing| thing.encode_iv }, encode_salt: Proc.new { |thing| thing.encode_salt }
|
|
65
|
+
|
|
66
|
+
def initialize(email: nil, encode_iv: 'm', encode_salt: 'm', mode: :per_attribute_iv_and_salt)
|
|
67
|
+
self.email = email
|
|
68
|
+
@encode_iv = encode_iv
|
|
69
|
+
@encode_salt = encode_salt
|
|
70
|
+
@mode = mode
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
attr_reader :encode_iv, :encode_salt, :mode
|
|
74
|
+
end
|
|
75
|
+
|
|
56
76
|
class AttrEncryptedTest < Minitest::Test
|
|
77
|
+
def setup
|
|
78
|
+
@iv = SecureRandom.random_bytes(12)
|
|
79
|
+
end
|
|
57
80
|
|
|
58
81
|
def test_should_store_email_in_encrypted_attributes
|
|
59
82
|
assert User.encrypted_attributes.include?(:email)
|
|
60
83
|
end
|
|
61
84
|
|
|
62
85
|
def test_should_not_store_salt_in_encrypted_attributes
|
|
63
|
-
|
|
86
|
+
refute User.encrypted_attributes.include?(:salt)
|
|
64
87
|
end
|
|
65
88
|
|
|
66
89
|
def test_attr_encrypted_should_return_true_for_email
|
|
@@ -88,16 +111,16 @@ class AttrEncryptedTest < Minitest::Test
|
|
|
88
111
|
end
|
|
89
112
|
|
|
90
113
|
def test_should_not_encrypt_nil_value
|
|
91
|
-
assert_nil User.encrypt_email(nil)
|
|
114
|
+
assert_nil User.encrypt_email(nil, iv: @iv)
|
|
92
115
|
end
|
|
93
116
|
|
|
94
117
|
def test_should_not_encrypt_empty_string
|
|
95
|
-
assert_equal '', User.encrypt_email('')
|
|
118
|
+
assert_equal '', User.encrypt_email('', iv: @iv)
|
|
96
119
|
end
|
|
97
120
|
|
|
98
121
|
def test_should_encrypt_email
|
|
99
|
-
refute_nil User.encrypt_email('test@example.com')
|
|
100
|
-
refute_equal 'test@example.com', User.encrypt_email('test@example.com')
|
|
122
|
+
refute_nil User.encrypt_email('test@example.com', iv: @iv)
|
|
123
|
+
refute_equal 'test@example.com', User.encrypt_email('test@example.com', iv: @iv)
|
|
101
124
|
end
|
|
102
125
|
|
|
103
126
|
def test_should_encrypt_email_when_modifying_the_attr_writer
|
|
@@ -105,48 +128,52 @@ class AttrEncryptedTest < Minitest::Test
|
|
|
105
128
|
assert_nil @user.encrypted_email
|
|
106
129
|
@user.email = 'test@example.com'
|
|
107
130
|
refute_nil @user.encrypted_email
|
|
108
|
-
|
|
131
|
+
iv = @user.encrypted_email_iv.unpack('m').first
|
|
132
|
+
salt = @user.encrypted_email_salt[1..-1].unpack('m').first
|
|
133
|
+
assert_equal User.encrypt_email('test@example.com', iv: iv, salt: salt), @user.encrypted_email
|
|
109
134
|
end
|
|
110
135
|
|
|
111
136
|
def test_should_not_decrypt_nil_value
|
|
112
|
-
assert_nil User.decrypt_email(nil)
|
|
137
|
+
assert_nil User.decrypt_email(nil, iv: @iv)
|
|
113
138
|
end
|
|
114
139
|
|
|
115
140
|
def test_should_not_decrypt_empty_string
|
|
116
|
-
assert_equal '', User.decrypt_email('')
|
|
141
|
+
assert_equal '', User.decrypt_email('', iv: @iv)
|
|
117
142
|
end
|
|
118
143
|
|
|
119
144
|
def test_should_decrypt_email
|
|
120
|
-
encrypted_email = User.encrypt_email('test@example.com')
|
|
145
|
+
encrypted_email = User.encrypt_email('test@example.com', iv: @iv)
|
|
121
146
|
refute_equal 'test@test.com', encrypted_email
|
|
122
|
-
assert_equal 'test@example.com', User.decrypt_email(encrypted_email)
|
|
147
|
+
assert_equal 'test@example.com', User.decrypt_email(encrypted_email, iv: @iv)
|
|
123
148
|
end
|
|
124
149
|
|
|
125
150
|
def test_should_decrypt_email_when_reading
|
|
126
151
|
@user = User.new
|
|
127
152
|
assert_nil @user.email
|
|
128
|
-
|
|
153
|
+
iv = @user.encrypted_email_iv.unpack('m').first
|
|
154
|
+
salt = @user.encrypted_email_salt[1..-1].unpack('m').first
|
|
155
|
+
@user.encrypted_email = User.encrypt_email('test@example.com', iv: iv, salt: salt)
|
|
129
156
|
assert_equal 'test@example.com', @user.email
|
|
130
157
|
end
|
|
131
158
|
|
|
132
159
|
def test_should_encrypt_with_encoding
|
|
133
|
-
assert_equal User.encrypt_with_encoding('test'), [User.encrypt_without_encoding('test')].pack('m')
|
|
160
|
+
assert_equal User.encrypt_with_encoding('test', iv: @iv), [User.encrypt_without_encoding('test', iv: @iv)].pack('m')
|
|
134
161
|
end
|
|
135
162
|
|
|
136
163
|
def test_should_decrypt_with_encoding
|
|
137
|
-
encrypted = User.encrypt_with_encoding('test')
|
|
138
|
-
assert_equal 'test', User.decrypt_with_encoding(encrypted)
|
|
139
|
-
assert_equal User.decrypt_with_encoding(encrypted), User.decrypt_without_encoding(encrypted.unpack('m').first)
|
|
164
|
+
encrypted = User.encrypt_with_encoding('test', iv: @iv)
|
|
165
|
+
assert_equal 'test', User.decrypt_with_encoding(encrypted, iv: @iv)
|
|
166
|
+
assert_equal User.decrypt_with_encoding(encrypted, iv: @iv), User.decrypt_without_encoding(encrypted.unpack('m').first, iv: @iv)
|
|
140
167
|
end
|
|
141
168
|
|
|
142
169
|
def test_should_encrypt_with_custom_encoding
|
|
143
|
-
assert_equal User.encrypt_with_encoding('test'), [User.encrypt_without_encoding('test')].pack('m')
|
|
170
|
+
assert_equal User.encrypt_with_encoding('test', iv: @iv), [User.encrypt_without_encoding('test', iv: @iv)].pack('m')
|
|
144
171
|
end
|
|
145
172
|
|
|
146
173
|
def test_should_decrypt_with_custom_encoding
|
|
147
|
-
encrypted = User.encrypt_with_encoding('test')
|
|
148
|
-
assert_equal 'test', User.decrypt_with_encoding(encrypted)
|
|
149
|
-
assert_equal User.decrypt_with_encoding(encrypted), User.decrypt_without_encoding(encrypted.unpack('m').first)
|
|
174
|
+
encrypted = User.encrypt_with_encoding('test', iv: @iv)
|
|
175
|
+
assert_equal 'test', User.decrypt_with_encoding(encrypted, iv: @iv)
|
|
176
|
+
assert_equal User.decrypt_with_encoding(encrypted, iv: @iv), User.decrypt_without_encoding(encrypted.unpack('m').first, iv: @iv)
|
|
150
177
|
end
|
|
151
178
|
|
|
152
179
|
def test_should_encrypt_with_marshaling
|
|
@@ -164,7 +191,7 @@ class AttrEncryptedTest < Minitest::Test
|
|
|
164
191
|
assert_nil @user.ssn_encrypted
|
|
165
192
|
@user.ssn = 'testing'
|
|
166
193
|
refute_nil @user.ssn_encrypted
|
|
167
|
-
encrypted = Encryptor.encrypt(:value => 'testing', :key => SECRET_KEY, :iv => @user.ssn_encrypted_iv.unpack("m").first, :salt => @user.ssn_encrypted_salt )
|
|
194
|
+
encrypted = Encryptor.encrypt(:value => 'testing', :key => SECRET_KEY, :iv => @user.ssn_encrypted_iv.unpack("m").first, :salt => @user.ssn_encrypted_salt.unpack("m").first )
|
|
168
195
|
assert_equal encrypted, @user.ssn_encrypted
|
|
169
196
|
end
|
|
170
197
|
|
|
@@ -173,7 +200,7 @@ class AttrEncryptedTest < Minitest::Test
|
|
|
173
200
|
assert_nil @user.crypted_password_test
|
|
174
201
|
@user.password = 'testing'
|
|
175
202
|
refute_nil @user.crypted_password_test
|
|
176
|
-
encrypted = Encryptor.encrypt(:value => 'testing', :key => SECRET_KEY, :iv => @user.crypted_password_test_iv.unpack("m").first, :salt => @user.crypted_password_test_salt)
|
|
203
|
+
encrypted = Encryptor.encrypt(:value => 'testing', :key => SECRET_KEY, :iv => @user.crypted_password_test_iv.unpack("m").first, :salt => @user.crypted_password_test_salt.unpack("m").first)
|
|
177
204
|
assert_equal encrypted, @user.crypted_password_test
|
|
178
205
|
end
|
|
179
206
|
|
|
@@ -182,7 +209,7 @@ class AttrEncryptedTest < Minitest::Test
|
|
|
182
209
|
assert_nil @user.crypted_password_test
|
|
183
210
|
@user.password = 'testing'
|
|
184
211
|
refute_nil @user.crypted_password_test
|
|
185
|
-
encrypted = Encryptor.encrypt(:value => 'testing', :key => SECRET_KEY, :iv => @user.crypted_password_test_iv.unpack("m").first, :salt => @user.crypted_password_test_salt)
|
|
212
|
+
encrypted = Encryptor.encrypt(:value => 'testing', :key => SECRET_KEY, :iv => @user.crypted_password_test_iv.unpack("m").first, :salt => @user.crypted_password_test_salt.unpack("m").first)
|
|
186
213
|
assert_equal encrypted, @user.crypted_password_test
|
|
187
214
|
end
|
|
188
215
|
|
|
@@ -201,23 +228,24 @@ class AttrEncryptedTest < Minitest::Test
|
|
|
201
228
|
end
|
|
202
229
|
|
|
203
230
|
def test_should_evaluate_a_symbol_option
|
|
204
|
-
assert_equal
|
|
231
|
+
assert_equal SomeOtherClass, SomeOtherClass.new.send(:evaluate_attr_encrypted_option, :class)
|
|
205
232
|
end
|
|
206
233
|
|
|
207
234
|
def test_should_evaluate_a_proc_option
|
|
208
|
-
assert_equal
|
|
235
|
+
assert_equal SomeOtherClass, SomeOtherClass.new.send(:evaluate_attr_encrypted_option, proc { |object| object.class })
|
|
209
236
|
end
|
|
210
237
|
|
|
211
238
|
def test_should_evaluate_a_lambda_option
|
|
212
|
-
assert_equal
|
|
239
|
+
assert_equal SomeOtherClass, SomeOtherClass.new.send(:evaluate_attr_encrypted_option, lambda { |object| object.class })
|
|
213
240
|
end
|
|
214
241
|
|
|
215
242
|
def test_should_evaluate_a_method_option
|
|
216
|
-
assert_equal
|
|
243
|
+
assert_equal SomeOtherClass, SomeOtherClass.new.send(:evaluate_attr_encrypted_option, SomeOtherClass.method(:call))
|
|
217
244
|
end
|
|
218
245
|
|
|
219
246
|
def test_should_return_a_string_option
|
|
220
|
-
|
|
247
|
+
class_string = 'SomeOtherClass'
|
|
248
|
+
assert_equal class_string, SomeOtherClass.new.send(:evaluate_attr_encrypted_option, class_string)
|
|
221
249
|
end
|
|
222
250
|
|
|
223
251
|
def test_should_encrypt_with_true_if
|
|
@@ -225,7 +253,7 @@ class AttrEncryptedTest < Minitest::Test
|
|
|
225
253
|
assert_nil @user.encrypted_with_true_if
|
|
226
254
|
@user.with_true_if = 'testing'
|
|
227
255
|
refute_nil @user.encrypted_with_true_if
|
|
228
|
-
encrypted = Encryptor.encrypt(:value => 'testing', :key => SECRET_KEY, :iv => @user.encrypted_with_true_if_iv.unpack("m").first, :salt => @user.encrypted_with_true_if_salt)
|
|
256
|
+
encrypted = Encryptor.encrypt(:value => 'testing', :key => SECRET_KEY, :iv => @user.encrypted_with_true_if_iv.unpack("m").first, :salt => @user.encrypted_with_true_if_salt.unpack("m").first)
|
|
229
257
|
assert_equal encrypted, @user.encrypted_with_true_if
|
|
230
258
|
end
|
|
231
259
|
|
|
@@ -242,7 +270,7 @@ class AttrEncryptedTest < Minitest::Test
|
|
|
242
270
|
assert_nil @user.encrypted_with_false_unless
|
|
243
271
|
@user.with_false_unless = 'testing'
|
|
244
272
|
refute_nil @user.encrypted_with_false_unless
|
|
245
|
-
encrypted = Encryptor.encrypt(:value => 'testing', :key => SECRET_KEY, :iv => @user.encrypted_with_false_unless_iv.unpack("m").first, :salt => @user.encrypted_with_false_unless_salt)
|
|
273
|
+
encrypted = Encryptor.encrypt(:value => 'testing', :key => SECRET_KEY, :iv => @user.encrypted_with_false_unless_iv.unpack("m").first, :salt => @user.encrypted_with_false_unless_salt.unpack("m").first)
|
|
246
274
|
assert_equal encrypted, @user.encrypted_with_false_unless
|
|
247
275
|
end
|
|
248
276
|
|
|
@@ -261,11 +289,6 @@ class AttrEncryptedTest < Minitest::Test
|
|
|
261
289
|
def test_should_always_reset_options
|
|
262
290
|
@user = User.new
|
|
263
291
|
@user.with_if_changed = "encrypt_stuff"
|
|
264
|
-
@user.stubs(:instance_variable_get).returns(nil)
|
|
265
|
-
@user.stubs(:instance_variable_set).raises("BadStuff")
|
|
266
|
-
assert_raises RuntimeError do
|
|
267
|
-
@user.with_if_changed
|
|
268
|
-
end
|
|
269
292
|
|
|
270
293
|
@user = User.new
|
|
271
294
|
@user.should_encrypt = false
|
|
@@ -275,9 +298,9 @@ class AttrEncryptedTest < Minitest::Test
|
|
|
275
298
|
end
|
|
276
299
|
|
|
277
300
|
def test_should_cast_values_as_strings_before_encrypting
|
|
278
|
-
string_encrypted_email = User.encrypt_email('3')
|
|
279
|
-
assert_equal string_encrypted_email, User.encrypt_email(3)
|
|
280
|
-
assert_equal '3', User.decrypt_email(string_encrypted_email)
|
|
301
|
+
string_encrypted_email = User.encrypt_email('3', iv: @iv)
|
|
302
|
+
assert_equal string_encrypted_email, User.encrypt_email(3, iv: @iv)
|
|
303
|
+
assert_equal '3', User.decrypt_email(string_encrypted_email, iv: @iv)
|
|
281
304
|
end
|
|
282
305
|
|
|
283
306
|
def test_should_create_query_accessor
|
|
@@ -296,12 +319,18 @@ class AttrEncryptedTest < Minitest::Test
|
|
|
296
319
|
refute_equal @user.encrypted_email_iv, @user.crypted_password_test_iv
|
|
297
320
|
end
|
|
298
321
|
|
|
322
|
+
def test_should_generate_iv_per_attribute_by_default
|
|
323
|
+
thing = YetAnotherClass.new(email: 'thing@thing.com')
|
|
324
|
+
refute_nil thing.encrypted_email_iv
|
|
325
|
+
end
|
|
326
|
+
|
|
299
327
|
def test_should_vary_iv_per_instance
|
|
300
328
|
@user1 = User.new
|
|
301
329
|
@user1.email = 'email@example.com'
|
|
302
330
|
@user2 = User.new
|
|
303
331
|
@user2.email = 'email@example.com'
|
|
304
332
|
refute_equal @user1.encrypted_email_iv, @user2.encrypted_email_iv
|
|
333
|
+
refute_equal @user1.encrypted_email, @user2.encrypted_email
|
|
305
334
|
end
|
|
306
335
|
|
|
307
336
|
def test_should_vary_salt_per_attribute
|
|
@@ -319,6 +348,11 @@ class AttrEncryptedTest < Minitest::Test
|
|
|
319
348
|
refute_equal @user1.encrypted_email_salt, @user2.encrypted_email_salt
|
|
320
349
|
end
|
|
321
350
|
|
|
351
|
+
def test_should_not_generate_salt_per_attribute_by_default
|
|
352
|
+
thing = YetAnotherClass.new(email: 'thing@thing.com')
|
|
353
|
+
assert_nil thing.encrypted_email_salt
|
|
354
|
+
end
|
|
355
|
+
|
|
322
356
|
def test_should_decrypt_second_record
|
|
323
357
|
@user1 = User.new
|
|
324
358
|
@user1.email = 'test@example.com'
|
|
@@ -328,4 +362,32 @@ class AttrEncryptedTest < Minitest::Test
|
|
|
328
362
|
|
|
329
363
|
assert_equal 'test@example.com', @user1.decrypt(:email, @user1.encrypted_email)
|
|
330
364
|
end
|
|
365
|
+
|
|
366
|
+
def test_should_specify_the_default_algorithm
|
|
367
|
+
assert YetAnotherClass.encrypted_attributes[:email][:algorithm]
|
|
368
|
+
assert_equal YetAnotherClass.encrypted_attributes[:email][:algorithm], 'aes-256-gcm'
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def test_should_not_encode_iv_when_encode_iv_is_false
|
|
372
|
+
email = 'thing@thing.com'
|
|
373
|
+
thing = YetAnotherClass.new(email: email)
|
|
374
|
+
refute thing.encrypted_email_iv =~ base64_encoding_regex
|
|
375
|
+
assert_equal thing.email, email
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def test_should_base64_encode_iv_by_default
|
|
379
|
+
phone_number = '555-555-5555'
|
|
380
|
+
thing = YetAnotherClass.new
|
|
381
|
+
thing.phone_number = phone_number
|
|
382
|
+
assert thing.encrypted_phone_number_iv =~ base64_encoding_regex
|
|
383
|
+
assert_equal thing.phone_number, phone_number
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
def test_should_generate_unique_iv_for_every_encrypt_operation
|
|
387
|
+
user = User.new
|
|
388
|
+
user.email = 'initial_value@test.com'
|
|
389
|
+
original_iv = user.encrypted_email_iv
|
|
390
|
+
user.email = 'revised_value@test.com'
|
|
391
|
+
refute_equal original_iv, user.encrypted_email_iv
|
|
392
|
+
end
|
|
331
393
|
end
|