symmetric-encryption 4.0.0 → 4.0.1
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 +5 -5
- data/Rakefile +2 -2
- data/bin/symmetric-encryption +1 -1
- data/lib/symmetric-encryption.rb +1 -1
- data/lib/symmetric_encryption.rb +2 -2
- data/lib/symmetric_encryption/cipher.rb +15 -18
- data/lib/symmetric_encryption/cli.rb +30 -36
- data/lib/symmetric_encryption/coerce.rb +3 -4
- data/lib/symmetric_encryption/config.rb +30 -34
- data/lib/symmetric_encryption/encoder.rb +0 -1
- data/lib/symmetric_encryption/exception.rb +0 -2
- data/lib/symmetric_encryption/extensions/active_record/base.rb +5 -2
- data/lib/symmetric_encryption/extensions/mongo_mapper/plugins/encrypted_key.rb +3 -5
- data/lib/symmetric_encryption/extensions/mongoid/encrypted.rb +0 -2
- data/lib/symmetric_encryption/generator.rb +3 -3
- data/lib/symmetric_encryption/header.rb +9 -4
- data/lib/symmetric_encryption/key.rb +3 -4
- data/lib/symmetric_encryption/keystore.rb +9 -9
- data/lib/symmetric_encryption/keystore/environment.rb +6 -7
- data/lib/symmetric_encryption/keystore/file.rb +5 -6
- data/lib/symmetric_encryption/keystore/memory.rb +2 -2
- data/lib/symmetric_encryption/railtie.rb +4 -7
- data/lib/symmetric_encryption/railties/symmetric_encryption_validator.rb +2 -1
- data/lib/symmetric_encryption/reader.rb +28 -39
- data/lib/symmetric_encryption/symmetric_encryption.rb +10 -8
- data/lib/symmetric_encryption/utils/re_encrypt_files.rb +5 -8
- data/lib/symmetric_encryption/version.rb +2 -2
- data/lib/symmetric_encryption/writer.rb +12 -17
- data/test/active_record_test.rb +237 -200
- data/test/cipher_test.rb +12 -6
- data/test/encoder_test.rb +1 -3
- data/test/header_test.rb +0 -4
- data/test/key_test.rb +0 -2
- data/test/keystore/environment_test.rb +10 -11
- data/test/keystore/file_test.rb +9 -10
- data/test/keystore_test.rb +2 -3
- data/test/mongoid_test.rb +37 -40
- data/test/reader_test.rb +24 -32
- data/test/symmetric_encryption_test.rb +17 -18
- data/test/test_db.sqlite3 +0 -0
- data/test/writer_test.rb +0 -1
- metadata +23 -23
@@ -8,7 +8,6 @@ require 'erb'
|
|
8
8
|
# The symmetric key is protected using the private key below and must
|
9
9
|
# be distributed separately from the application
|
10
10
|
module SymmetricEncryption
|
11
|
-
|
12
11
|
# Defaults
|
13
12
|
@@cipher = nil
|
14
13
|
@@secondary_ciphers = []
|
@@ -26,7 +25,7 @@ module SymmetricEncryption
|
|
26
25
|
# :date => Date
|
27
26
|
# :json => Uses JSON serialization, useful for hashes and arrays
|
28
27
|
# :yaml => Uses YAML serialization, useful for hashes and arrays
|
29
|
-
COERCION_TYPES = [
|
28
|
+
COERCION_TYPES = %i[string integer float decimal datetime time date boolean json yaml].freeze
|
30
29
|
|
31
30
|
# Set the Primary Symmetric Cipher to be used
|
32
31
|
#
|
@@ -48,10 +47,15 @@ module SymmetricEncryption
|
|
48
47
|
# Returns the primary cipher if no match was found and version == 0
|
49
48
|
# Returns nil if no match was found and version != 0
|
50
49
|
def self.cipher(version = nil)
|
51
|
-
|
50
|
+
unless cipher?
|
51
|
+
raise(
|
52
|
+
SymmetricEncryption::ConfigError,
|
53
|
+
'Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data'
|
54
|
+
)
|
55
|
+
end
|
52
56
|
|
53
57
|
return @@cipher if version.nil? || (@@cipher.version == version)
|
54
|
-
secondary_ciphers.find { |c| c.version == version } || (@@cipher if version
|
58
|
+
secondary_ciphers.find { |c| c.version == version } || (@@cipher if version.zero?)
|
55
59
|
end
|
56
60
|
|
57
61
|
# Returns whether a primary cipher has been set
|
@@ -139,9 +143,7 @@ module SymmetricEncryption
|
|
139
143
|
end
|
140
144
|
|
141
145
|
# Try to force result to UTF-8 encoding, but if it is not valid, force it back to Binary
|
142
|
-
unless decrypted.force_encoding(SymmetricEncryption::UTF8_ENCODING).valid_encoding?
|
143
|
-
decrypted.force_encoding(SymmetricEncryption::BINARY_ENCODING)
|
144
|
-
end
|
146
|
+
decrypted.force_encoding(SymmetricEncryption::BINARY_ENCODING) unless decrypted.force_encoding(SymmetricEncryption::UTF8_ENCODING).valid_encoding?
|
145
147
|
Coerce.coerce_from_string(decrypted, type)
|
146
148
|
end
|
147
149
|
|
@@ -155,7 +157,7 @@ module SymmetricEncryption
|
|
155
157
|
return if decoded.nil? || decoded.empty?
|
156
158
|
|
157
159
|
h = Header.new
|
158
|
-
h.parse(decoded)
|
160
|
+
h.parse(decoded).zero? ? nil : h
|
159
161
|
end
|
160
162
|
|
161
163
|
# AES Symmetric Encryption of supplied string
|
@@ -39,7 +39,7 @@ module SymmetricEncryption
|
|
39
39
|
|
40
40
|
# Re-encrypt the supplied encrypted value with the new cipher
|
41
41
|
def re_encrypt(encrypted)
|
42
|
-
if unencrypted = SymmetricEncryption.try_decrypt(encrypted)
|
42
|
+
if (unencrypted = SymmetricEncryption.try_decrypt(encrypted))
|
43
43
|
cipher.encrypt(unencrypted)
|
44
44
|
else
|
45
45
|
encrypted
|
@@ -72,9 +72,7 @@ module SymmetricEncryption
|
|
72
72
|
line
|
73
73
|
end
|
74
74
|
end
|
75
|
-
if hits
|
76
|
-
File.open(file_name, 'wb') { |file| file.write(output_lines) }
|
77
|
-
end
|
75
|
+
File.open(file_name, 'wb') { |file| file.write(output_lines) } if hits.positive?
|
78
76
|
hits
|
79
77
|
end
|
80
78
|
|
@@ -86,7 +84,7 @@ module SymmetricEncryption
|
|
86
84
|
end
|
87
85
|
File.delete(file_name)
|
88
86
|
File.rename(temp_file_name, file_name)
|
89
|
-
rescue
|
87
|
+
rescue StandardError
|
90
88
|
File.delete(temp_file_name) if temp_file_name && File.exist?(temp_file_name)
|
91
89
|
raise
|
92
90
|
end
|
@@ -101,7 +99,7 @@ module SymmetricEncryption
|
|
101
99
|
Dir[path].each do |file_name|
|
102
100
|
next if File.directory?(file_name)
|
103
101
|
|
104
|
-
if v = encrypted_file_version(file_name)
|
102
|
+
if (v = encrypted_file_version(file_name))
|
105
103
|
if v == version
|
106
104
|
puts "Skipping already re-encrypted file: #{file_name}"
|
107
105
|
else
|
@@ -111,7 +109,7 @@ module SymmetricEncryption
|
|
111
109
|
else
|
112
110
|
begin
|
113
111
|
count = re_encrypt_contents(file_name)
|
114
|
-
puts "Re-encrypted #{count} encrypted value(s) in: #{file_name}" if count
|
112
|
+
puts "Re-encrypted #{count} encrypted value(s) in: #{file_name}" if count.positive?
|
115
113
|
rescue StandardError => exc
|
116
114
|
puts "Failed re-encrypting the file contents of: #{file_name}. #{exc.class.name}: #{exc.message}"
|
117
115
|
end
|
@@ -135,7 +133,6 @@ module SymmetricEncryption
|
|
135
133
|
rescue OpenSSL::Cipher::CipherError
|
136
134
|
nil
|
137
135
|
end
|
138
|
-
|
139
136
|
end
|
140
137
|
end
|
141
138
|
end
|
@@ -1,3 +1,3 @@
|
|
1
|
-
module SymmetricEncryption
|
2
|
-
VERSION = '4.0.
|
1
|
+
module SymmetricEncryption
|
2
|
+
VERSION = '4.0.1'.freeze
|
3
3
|
end
|
@@ -51,7 +51,7 @@ module SymmetricEncryption
|
|
51
51
|
ios = file_name_or_stream.is_a?(String) ? ::File.open(file_name_or_stream, 'wb') : file_name_or_stream
|
52
52
|
|
53
53
|
begin
|
54
|
-
file =
|
54
|
+
file = new(ios, compress: compress, **args)
|
55
55
|
file = Zlib::GzipWriter.new(file) if compress
|
56
56
|
block_given? ? yield(file) : file
|
57
57
|
ensure
|
@@ -64,7 +64,7 @@ module SymmetricEncryption
|
|
64
64
|
# Notes:
|
65
65
|
# * Do not use this method for writing large files.
|
66
66
|
def self.write(file_name_or_stream, data, **args)
|
67
|
-
open(file_name_or_stream, **args) { |f| f.write(data) }
|
67
|
+
self.open(file_name_or_stream, **args) { |f| f.write(data) }
|
68
68
|
end
|
69
69
|
|
70
70
|
# Encrypt an entire file.
|
@@ -89,17 +89,15 @@ module SymmetricEncryption
|
|
89
89
|
#
|
90
90
|
# Notes:
|
91
91
|
# * The file contents are streamed so that the entire file is _not_ loaded into memory.
|
92
|
-
def self.encrypt(source:, target:, block_size:
|
92
|
+
def self.encrypt(source:, target:, block_size: 65_535, **args)
|
93
93
|
source_ios = source.is_a?(String) ? ::File.open(source, 'rb') : source
|
94
94
|
bytes_written = 0
|
95
|
-
open(target, **args) do |output_file|
|
96
|
-
|
97
|
-
bytes_written += output_file.write(source_ios.read(block_size))
|
98
|
-
end
|
95
|
+
self.open(target, **args) do |output_file|
|
96
|
+
bytes_written += output_file.write(source_ios.read(block_size)) until source_ios.eof?
|
99
97
|
end
|
100
98
|
bytes_written
|
101
99
|
ensure
|
102
|
-
source_ios.close if source_ios
|
100
|
+
source_ios.close if source_ios&.respond_to?(:closed?) && !source_ios.closed?
|
103
101
|
end
|
104
102
|
|
105
103
|
# Encrypt data before writing to the supplied stream
|
@@ -114,9 +112,7 @@ module SymmetricEncryption
|
|
114
112
|
raise(SymmetricEncryption::CipherError, "Cipher with version:#{version} not found in any of the configured SymmetricEncryption ciphers") unless cipher
|
115
113
|
|
116
114
|
# Force header if compressed or using random iv, key
|
117
|
-
if (header == true) || compress || random_key || random_iv
|
118
|
-
header = Header.new(version: cipher.version, compress: compress, cipher_name: cipher_name)
|
119
|
-
end
|
115
|
+
header = Header.new(version: cipher.version, compress: compress, cipher_name: cipher_name) if (header == true) || compress || random_key || random_iv
|
120
116
|
|
121
117
|
@stream_cipher = ::OpenSSL::Cipher.new(cipher_name || cipher.cipher_name)
|
122
118
|
@stream_cipher.encrypt
|
@@ -129,8 +125,8 @@ module SymmetricEncryption
|
|
129
125
|
|
130
126
|
if random_iv
|
131
127
|
header.iv = @stream_cipher.iv = @stream_cipher.random_iv
|
132
|
-
|
133
|
-
@stream_cipher.iv = cipher.iv
|
128
|
+
elsif cipher.iv
|
129
|
+
@stream_cipher.iv = cipher.iv
|
134
130
|
end
|
135
131
|
|
136
132
|
@ios.write(header.to_s) if header
|
@@ -153,9 +149,9 @@ module SymmetricEncryption
|
|
153
149
|
# ensure that the encrypted stream is closed before the stream itself is closed.
|
154
150
|
def close(close_child_stream = true)
|
155
151
|
return if closed?
|
156
|
-
if size
|
152
|
+
if size.positive?
|
157
153
|
final = @stream_cipher.final
|
158
|
-
@ios.write(final)
|
154
|
+
@ios.write(final) unless final.empty?
|
159
155
|
end
|
160
156
|
@ios.close if close_child_stream
|
161
157
|
@closed = true
|
@@ -170,7 +166,7 @@ module SymmetricEncryption
|
|
170
166
|
bytes = data.to_s
|
171
167
|
@size += bytes.size
|
172
168
|
partial = @stream_cipher.update(bytes)
|
173
|
-
@ios.write(partial)
|
169
|
+
@ios.write(partial) unless partial.empty?
|
174
170
|
data.length
|
175
171
|
end
|
176
172
|
|
@@ -201,6 +197,5 @@ module SymmetricEncryption
|
|
201
197
|
# Returns [Integer] the number of unencrypted and uncompressed bytes
|
202
198
|
# written to the file so far.
|
203
199
|
attr_reader :size
|
204
|
-
|
205
200
|
end
|
206
201
|
end
|
data/test/active_record_test.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
require_relative 'test_helper'
|
2
2
|
|
3
|
-
ActiveRecord::Base.configurations = YAML
|
3
|
+
ActiveRecord::Base.configurations = YAML.safe_load(ERB.new(IO.read('test/config/database.yml')).result)
|
4
4
|
ActiveRecord::Base.establish_connection(:test)
|
5
5
|
|
6
|
-
|
6
|
+
# @formatter:off
|
7
7
|
ActiveRecord::Schema.define version: 0 do
|
8
8
|
create_table :users, force: true do |t|
|
9
9
|
t.string :encrypted_bank_account_number
|
@@ -59,7 +59,7 @@ class User < ActiveRecord::Base
|
|
59
59
|
attr_encrypted :text, type: :string
|
60
60
|
attr_encrypted :number, type: :integer
|
61
61
|
|
62
|
-
validates :text, format: {
|
62
|
+
validates :text, format: {with: /\A[a-zA-Z ]+\z/, message: 'only allows letters'}, presence: true
|
63
63
|
validates :number, presence: true
|
64
64
|
end
|
65
65
|
|
@@ -71,11 +71,11 @@ class UniqueUser < ActiveRecord::Base
|
|
71
71
|
validates_uniqueness_of :encrypted_username, allow_blank: true, if: :encrypted_username_changed?
|
72
72
|
|
73
73
|
validates :username,
|
74
|
-
|
75
|
-
|
76
|
-
|
74
|
+
length: {in: 3..20},
|
75
|
+
format: {with: /\A[\w\-]+\z/},
|
76
|
+
allow_blank: true
|
77
77
|
end
|
78
|
-
|
78
|
+
# @formatter:on
|
79
79
|
|
80
80
|
#
|
81
81
|
# Unit Test for attr_encrypted extensions in ActiveRecord
|
@@ -84,31 +84,45 @@ class ActiveRecordTest < Minitest::Test
|
|
84
84
|
describe 'ActiveRecord' do
|
85
85
|
INTEGER_VALUE = 12
|
86
86
|
FLOAT_VALUE = 88.12345
|
87
|
-
DECIMAL_VALUE = BigDecimal
|
88
|
-
DATETIME_VALUE = DateTime.new(2001, 11, 26, 20, 55, 54,
|
89
|
-
TIME_VALUE = Time.new(2013,
|
90
|
-
DATE_VALUE = Date.new(1927,
|
91
|
-
STRING_VALUE = 'A string containing some data to be encrypted with a random initialization vector'
|
92
|
-
LONG_STRING_VALUE = 'A string containing some data to be encrypted with a random initialization vector and compressed since it takes up so much space in plain text form'
|
87
|
+
DECIMAL_VALUE = BigDecimal('22.51')
|
88
|
+
DATETIME_VALUE = DateTime.new(2001, 11, 26, 20, 55, 54, '-5')
|
89
|
+
TIME_VALUE = Time.new(2013, 1, 1, 22, 30, 0, '-04:00')
|
90
|
+
DATE_VALUE = Date.new(1927, 4, 2)
|
91
|
+
STRING_VALUE = 'A string containing some data to be encrypted with a random initialization vector'.freeze
|
92
|
+
LONG_STRING_VALUE = 'A string containing some data to be encrypted with a random initialization vector and compressed since it takes up so much space in plain text form'.freeze
|
93
93
|
BINARY_STRING_VALUE = "Non-UTF8 Binary \x92 string".force_encoding('BINARY')
|
94
94
|
|
95
|
-
|
96
|
-
|
97
|
-
|
95
|
+
let :bank_account_number do
|
96
|
+
'1234567890'
|
97
|
+
end
|
98
98
|
|
99
|
-
|
100
|
-
|
99
|
+
let :bank_account_number_encrypted do
|
100
|
+
'QEVuQwIAL94ArJeFlJrZp6SYsvoOGA=='
|
101
|
+
end
|
101
102
|
|
102
|
-
|
103
|
+
let :social_security_number do
|
104
|
+
'987654321'
|
105
|
+
end
|
103
106
|
|
104
|
-
|
107
|
+
let :social_security_number_encrypted do
|
108
|
+
'QEVuQwIAS+8X1NRrqdfEIQyFHVPuVA=='
|
109
|
+
end
|
105
110
|
|
106
|
-
|
111
|
+
let :person_name do
|
112
|
+
'Joe Bloggs'
|
113
|
+
end
|
114
|
+
|
115
|
+
let :hash_data do
|
116
|
+
{a: 'A', b: 'B'}
|
117
|
+
end
|
118
|
+
|
119
|
+
let :user do
|
120
|
+
User.new(
|
107
121
|
# Encrypted Attribute
|
108
|
-
bank_account_number:
|
122
|
+
bank_account_number: bank_account_number,
|
109
123
|
# Encrypted Attribute
|
110
|
-
social_security_number:
|
111
|
-
name:
|
124
|
+
social_security_number: social_security_number,
|
125
|
+
name: person_name,
|
112
126
|
# data type specific fields
|
113
127
|
string_value: STRING_VALUE,
|
114
128
|
long_string_value: LONG_STRING_VALUE,
|
@@ -121,127 +135,127 @@ class ActiveRecordTest < Minitest::Test
|
|
121
135
|
date_value: DATE_VALUE,
|
122
136
|
true_value: true,
|
123
137
|
false_value: false,
|
124
|
-
data_yaml:
|
125
|
-
data_json:
|
138
|
+
data_yaml: hash_data.dup,
|
139
|
+
data_json: hash_data.dup,
|
126
140
|
text: 'hello',
|
127
141
|
number: '21'
|
128
142
|
)
|
129
143
|
end
|
130
144
|
|
131
145
|
it 'has encrypted methods' do
|
132
|
-
assert_equal true,
|
133
|
-
assert_equal true,
|
134
|
-
assert_equal true,
|
135
|
-
assert_equal true,
|
136
|
-
assert_equal true,
|
137
|
-
assert_equal true,
|
138
|
-
assert_equal false,
|
139
|
-
assert_equal true,
|
140
|
-
assert_equal true,
|
146
|
+
assert_equal true, user.respond_to?(:encrypted_bank_account_number)
|
147
|
+
assert_equal true, user.respond_to?(:bank_account_number)
|
148
|
+
assert_equal true, user.respond_to?(:encrypted_social_security_number)
|
149
|
+
assert_equal true, user.respond_to?(:social_security_number)
|
150
|
+
assert_equal true, user.respond_to?(:data_yaml)
|
151
|
+
assert_equal true, user.respond_to?(:data_json)
|
152
|
+
assert_equal false, user.respond_to?(:encrypted_name)
|
153
|
+
assert_equal true, user.respond_to?(:encrypted_bank_account_number_changed?)
|
154
|
+
assert_equal true, user.respond_to?(:bank_account_number_changed?)
|
141
155
|
end
|
142
156
|
|
143
157
|
it 'has unencrypted values' do
|
144
|
-
assert_equal
|
145
|
-
assert_equal
|
158
|
+
assert_equal bank_account_number, user.bank_account_number
|
159
|
+
assert_equal social_security_number, user.social_security_number
|
146
160
|
end
|
147
161
|
|
148
162
|
it 'has encrypted values' do
|
149
|
-
assert_equal
|
150
|
-
assert_equal
|
163
|
+
assert_equal bank_account_number_encrypted, user.encrypted_bank_account_number
|
164
|
+
assert_equal social_security_number_encrypted, user.encrypted_social_security_number
|
151
165
|
end
|
152
166
|
|
153
167
|
describe ':random_iv' do
|
154
168
|
it 'false' do
|
155
|
-
|
156
|
-
assert first_value =
|
169
|
+
user.social_security_number = social_security_number
|
170
|
+
assert first_value = user.social_security_number
|
157
171
|
# Assign the same value
|
158
|
-
|
159
|
-
assert_equal first_value,
|
172
|
+
user.social_security_number = social_security_number
|
173
|
+
assert_equal first_value, user.social_security_number
|
160
174
|
end
|
161
175
|
|
162
176
|
it 'true' do
|
163
|
-
|
164
|
-
assert first_value =
|
165
|
-
|
166
|
-
|
167
|
-
refute_equal first_value,
|
177
|
+
user.string_value = STRING_VALUE
|
178
|
+
assert first_value = user.encrypted_string_value
|
179
|
+
user.string_value = 'blah'
|
180
|
+
user.string_value = STRING_VALUE
|
181
|
+
refute_equal first_value, user.encrypted_string_value
|
168
182
|
end
|
169
183
|
|
170
184
|
it 'true and compress: true' do
|
171
|
-
|
172
|
-
|
185
|
+
user.string_value = STRING_VALUE
|
186
|
+
user.long_string_value = STRING_VALUE
|
173
187
|
|
174
|
-
refute_equal
|
188
|
+
refute_equal user.encrypted_long_string_value, user.encrypted_string_value
|
175
189
|
end
|
176
190
|
|
177
191
|
describe 'changed?' do
|
178
192
|
it 'true for a new instance' do
|
179
|
-
assert
|
193
|
+
assert user.string_value_changed?
|
180
194
|
end
|
181
195
|
|
182
196
|
it 'clears after save' do
|
183
|
-
|
184
|
-
refute
|
197
|
+
user.save!
|
198
|
+
refute user.string_value_changed?
|
185
199
|
end
|
186
200
|
|
187
201
|
it 'does not change when equal' do
|
188
|
-
|
189
|
-
before =
|
190
|
-
|
191
|
-
refute
|
192
|
-
assert_equal before,
|
202
|
+
user.save!
|
203
|
+
before = user.encrypted_string_value
|
204
|
+
user.string_value = STRING_VALUE
|
205
|
+
refute user.string_value_changed?
|
206
|
+
assert_equal before, user.encrypted_string_value
|
193
207
|
end
|
194
208
|
end
|
195
209
|
end
|
196
210
|
|
197
211
|
describe 'attribute=' do
|
198
212
|
it 'handles nil' do
|
199
|
-
|
200
|
-
assert_nil
|
201
|
-
assert_nil
|
202
|
-
|
203
|
-
|
204
|
-
assert_nil
|
205
|
-
assert_nil
|
213
|
+
user.string_value = nil
|
214
|
+
assert_nil user.string_value
|
215
|
+
assert_nil user.encrypted_string_value
|
216
|
+
user.save!
|
217
|
+
user.reload
|
218
|
+
assert_nil user.string_value
|
219
|
+
assert_nil user.encrypted_string_value
|
206
220
|
end
|
207
221
|
|
208
222
|
it 'handles empty string' do
|
209
|
-
|
210
|
-
assert_equal '',
|
211
|
-
assert_equal '',
|
212
|
-
|
213
|
-
|
214
|
-
assert_equal '',
|
215
|
-
assert_equal '',
|
223
|
+
user.string_value = ''
|
224
|
+
assert_equal '', user.string_value
|
225
|
+
assert_equal '', user.encrypted_string_value
|
226
|
+
user.save!
|
227
|
+
user.reload
|
228
|
+
assert_equal '', user.string_value
|
229
|
+
assert_equal '', user.encrypted_string_value
|
216
230
|
end
|
217
231
|
|
218
232
|
it 'encrypt' do
|
219
233
|
user = User.new
|
220
|
-
user.bank_account_number =
|
221
|
-
assert_equal
|
222
|
-
assert_equal
|
234
|
+
user.bank_account_number = bank_account_number
|
235
|
+
assert_equal bank_account_number, user.bank_account_number
|
236
|
+
assert_equal bank_account_number_encrypted, user.encrypted_bank_account_number
|
223
237
|
end
|
224
238
|
|
225
239
|
it 'all paths it lead to the same result' do
|
226
|
-
assert_equal
|
227
|
-
assert_equal
|
228
|
-
assert_equal
|
240
|
+
assert_equal bank_account_number_encrypted, (user.encrypted_social_security_number = bank_account_number_encrypted)
|
241
|
+
assert_equal bank_account_number, user.social_security_number
|
242
|
+
assert_equal bank_account_number_encrypted, user.encrypted_social_security_number
|
229
243
|
end
|
230
244
|
|
231
245
|
it 'all paths it lead to the same result 2' do
|
232
|
-
assert_equal
|
233
|
-
assert_equal
|
234
|
-
assert_equal
|
246
|
+
assert_equal bank_account_number, (user.social_security_number = bank_account_number)
|
247
|
+
assert_equal bank_account_number_encrypted, user.encrypted_social_security_number
|
248
|
+
assert_equal bank_account_number, user.social_security_number
|
235
249
|
end
|
236
250
|
|
237
251
|
it 'all paths it lead to the same result, check uninitialized' do
|
238
252
|
user = User.new
|
239
253
|
assert_nil user.social_security_number
|
240
|
-
assert_equal
|
241
|
-
assert_equal
|
242
|
-
assert_equal
|
254
|
+
assert_equal bank_account_number, (user.social_security_number = bank_account_number)
|
255
|
+
assert_equal bank_account_number, user.social_security_number
|
256
|
+
assert_equal bank_account_number_encrypted, user.encrypted_social_security_number
|
243
257
|
|
244
|
-
|
258
|
+
user.social_security_number = nil
|
245
259
|
assert_nil user.social_security_number
|
246
260
|
assert_nil user.encrypted_social_security_number
|
247
261
|
end
|
@@ -249,11 +263,11 @@ class ActiveRecordTest < Minitest::Test
|
|
249
263
|
|
250
264
|
describe '.new' do
|
251
265
|
it 'allows unencrypted values to be passed to the constructor' do
|
252
|
-
user = User.new(bank_account_number:
|
253
|
-
assert_equal
|
254
|
-
assert_equal
|
255
|
-
assert_equal
|
256
|
-
assert_equal
|
266
|
+
user = User.new(bank_account_number: bank_account_number, social_security_number: social_security_number)
|
267
|
+
assert_equal bank_account_number, user.bank_account_number
|
268
|
+
assert_equal social_security_number, user.social_security_number
|
269
|
+
assert_equal bank_account_number_encrypted, user.encrypted_bank_account_number
|
270
|
+
assert_equal social_security_number_encrypted, user.encrypted_social_security_number
|
257
271
|
end
|
258
272
|
end
|
259
273
|
|
@@ -261,13 +275,13 @@ class ActiveRecordTest < Minitest::Test
|
|
261
275
|
it 'returns encrypted attributes for the class' do
|
262
276
|
expect = {social_security_number: :encrypted_social_security_number, bank_account_number: :encrypted_bank_account_number}
|
263
277
|
result = User.encrypted_attributes
|
264
|
-
expect.each_pair { |k,
|
278
|
+
expect.each_pair { |k, _v| assert_equal expect[k], result[k] }
|
265
279
|
end
|
266
280
|
end
|
267
281
|
|
268
282
|
describe '.encrypted_keys' do
|
269
283
|
it 'return encrypted keys for the class' do
|
270
|
-
expect = [
|
284
|
+
expect = %i[social_security_number bank_account_number]
|
271
285
|
result = User.encrypted_keys
|
272
286
|
expect.each { |val| assert result.include?(val) }
|
273
287
|
|
@@ -278,7 +292,7 @@ class ActiveRecordTest < Minitest::Test
|
|
278
292
|
|
279
293
|
describe '.encrypted_columns' do
|
280
294
|
it 'return encrypted columns for the class' do
|
281
|
-
expect = [
|
295
|
+
expect = %i[encrypted_social_security_number encrypted_bank_account_number]
|
282
296
|
result = User.encrypted_columns
|
283
297
|
expect.each { |val| assert result.include?(val) }
|
284
298
|
|
@@ -288,123 +302,152 @@ class ActiveRecordTest < Minitest::Test
|
|
288
302
|
end
|
289
303
|
|
290
304
|
describe '#valid?' do
|
291
|
-
|
292
|
-
assert
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
305
|
+
before do
|
306
|
+
assert user.valid?
|
307
|
+
end
|
308
|
+
|
309
|
+
it 'fails invalid data' do
|
310
|
+
user.encrypted_bank_account_number = '123'
|
311
|
+
assert_equal false, user.valid?
|
312
|
+
assert_equal ['must be a value encrypted using SymmetricEncryption.encrypt'], user.errors[:encrypted_bank_account_number]
|
313
|
+
end
|
314
|
+
|
315
|
+
it 'passes encrypted data' do
|
316
|
+
user.encrypted_bank_account_number = SymmetricEncryption.encrypt('123')
|
317
|
+
assert user.valid?
|
318
|
+
end
|
319
|
+
|
320
|
+
it 'passes valid data' do
|
321
|
+
user.bank_account_number = '123'
|
322
|
+
assert user.valid?
|
323
|
+
end
|
324
|
+
|
325
|
+
it 'passes nil encrypted data' do
|
326
|
+
user.encrypted_bank_account_number = nil
|
327
|
+
assert user.valid?
|
328
|
+
end
|
329
|
+
|
330
|
+
it 'passes empty string encrypted data' do
|
331
|
+
user.encrypted_bank_account_number = ''
|
332
|
+
assert user.valid?
|
333
|
+
end
|
334
|
+
|
335
|
+
it 'passes nil data' do
|
336
|
+
user.bank_account_number = nil
|
337
|
+
assert user.valid?
|
338
|
+
end
|
339
|
+
|
340
|
+
it 'passes empty string data' do
|
341
|
+
user.bank_account_number = ''
|
342
|
+
assert user.valid?
|
300
343
|
end
|
301
344
|
|
302
345
|
it 'validate un-encrypted string data' do
|
303
|
-
assert
|
304
|
-
|
305
|
-
assert_equal false,
|
306
|
-
assert_equal ['only allows letters'],
|
307
|
-
|
308
|
-
assert_equal false,
|
309
|
-
assert_equal ['only allows letters', "can't be blank"],
|
310
|
-
|
311
|
-
assert_equal false,
|
312
|
-
assert_equal ['only allows letters', "can't be blank"],
|
346
|
+
assert user.valid?
|
347
|
+
user.text = '123'
|
348
|
+
assert_equal false, user.valid?
|
349
|
+
assert_equal ['only allows letters'], user.errors[:text]
|
350
|
+
user.text = nil
|
351
|
+
assert_equal false, user.valid?
|
352
|
+
assert_equal ['only allows letters', "can't be blank"], user.errors[:text]
|
353
|
+
user.text = ''
|
354
|
+
assert_equal false, user.valid?
|
355
|
+
assert_equal ['only allows letters', "can't be blank"], user.errors[:text]
|
313
356
|
end
|
314
357
|
|
315
358
|
it 'validate un-encrypted integer data with coercion' do
|
316
|
-
assert
|
317
|
-
|
318
|
-
assert
|
319
|
-
assert_equal 123,
|
320
|
-
assert
|
321
|
-
|
322
|
-
assert_equal false,
|
323
|
-
assert_equal '',
|
324
|
-
assert_equal ["can't be blank"],
|
325
|
-
|
326
|
-
assert_nil
|
327
|
-
assert_nil
|
328
|
-
assert_equal false,
|
329
|
-
assert_equal ["can't be blank"],
|
359
|
+
assert user.valid?
|
360
|
+
user.number = '123'
|
361
|
+
assert user.valid?
|
362
|
+
assert_equal 123, user.number
|
363
|
+
assert user.valid?
|
364
|
+
user.number = ''
|
365
|
+
assert_equal false, user.valid?
|
366
|
+
assert_equal '', user.number
|
367
|
+
assert_equal ["can't be blank"], user.errors[:number]
|
368
|
+
user.number = nil
|
369
|
+
assert_nil user.number
|
370
|
+
assert_nil user.encrypted_number
|
371
|
+
assert_equal false, user.valid?
|
372
|
+
assert_equal ["can't be blank"], user.errors[:number]
|
330
373
|
end
|
331
374
|
end
|
332
375
|
|
333
376
|
describe 'with saved user' do
|
334
377
|
before do
|
335
|
-
|
378
|
+
user.save!
|
336
379
|
end
|
337
380
|
|
338
381
|
after do
|
339
|
-
|
382
|
+
user&.destroy
|
340
383
|
end
|
341
384
|
|
342
385
|
it 'return correct data type before save' do
|
343
386
|
u = User.new(integer_value: '5')
|
344
387
|
assert_equal 5, u.integer_value
|
345
|
-
assert u.integer_value.
|
388
|
+
assert u.integer_value.is_a?(Integer)
|
346
389
|
end
|
347
390
|
|
348
391
|
it 'handle gsub! for non-encrypted_field' do
|
349
|
-
|
350
|
-
new_name =
|
351
|
-
assert_equal new_name,
|
352
|
-
|
353
|
-
assert_equal new_name,
|
392
|
+
user.name.tr!('a', 'v')
|
393
|
+
new_name = person_name.tr('a', 'v')
|
394
|
+
assert_equal new_name, user.name
|
395
|
+
user.reload
|
396
|
+
assert_equal new_name, user.name
|
354
397
|
end
|
355
398
|
|
356
|
-
it
|
399
|
+
it 'prevent gsub! on non-encrypted value of encrypted_field' do
|
357
400
|
# can't modify frozen String
|
358
401
|
assert_raises RuntimeError do
|
359
|
-
|
402
|
+
user.bank_account_number.tr!('5', '4')
|
360
403
|
end
|
361
404
|
end
|
362
405
|
|
363
406
|
describe '#reload' do
|
364
407
|
it 'reverts changes' do
|
365
408
|
new_bank_account_number = '444444444'
|
366
|
-
|
367
|
-
assert_equal new_bank_account_number,
|
409
|
+
user.bank_account_number = new_bank_account_number
|
410
|
+
assert_equal new_bank_account_number, user.bank_account_number
|
368
411
|
|
369
412
|
# Reload User model from the database
|
370
|
-
|
371
|
-
assert_equal
|
372
|
-
assert_equal
|
413
|
+
user.reload
|
414
|
+
assert_equal bank_account_number_encrypted, user.encrypted_bank_account_number
|
415
|
+
assert_equal bank_account_number, user.bank_account_number
|
373
416
|
end
|
374
417
|
|
375
418
|
it 'reverts changes to encrypted field' do
|
376
419
|
new_bank_account_number = '111111111'
|
377
420
|
new_encrypted_bank_account_number = SymmetricEncryption.encrypt(new_bank_account_number)
|
378
|
-
|
379
|
-
assert_equal new_encrypted_bank_account_number,
|
380
|
-
assert_equal new_bank_account_number,
|
421
|
+
user.encrypted_bank_account_number = new_encrypted_bank_account_number
|
422
|
+
assert_equal new_encrypted_bank_account_number, user.encrypted_bank_account_number
|
423
|
+
assert_equal new_bank_account_number, user.bank_account_number
|
381
424
|
|
382
425
|
# Reload User model from the database
|
383
|
-
|
384
|
-
assert_equal
|
385
|
-
assert_equal
|
426
|
+
user.reload
|
427
|
+
assert_equal bank_account_number_encrypted, user.encrypted_bank_account_number
|
428
|
+
assert_equal bank_account_number, user.bank_account_number
|
386
429
|
end
|
387
430
|
end
|
388
431
|
|
389
432
|
describe 'data types' do
|
390
433
|
before do
|
391
|
-
@user_clone = User.find(
|
434
|
+
@user_clone = User.find(user.id)
|
392
435
|
end
|
393
436
|
|
394
437
|
[
|
395
|
-
|
438
|
+
# @formatter:off
|
396
439
|
{attribute: :integer_value, klass: Integer, value: INTEGER_VALUE, new_value: 98},
|
397
440
|
{attribute: :float_value, klass: Float, value: FLOAT_VALUE, new_value: 45.4321},
|
398
|
-
{attribute: :decimal_value, klass: BigDecimal, value: DECIMAL_VALUE, new_value: BigDecimal
|
441
|
+
{attribute: :decimal_value, klass: BigDecimal, value: DECIMAL_VALUE, new_value: BigDecimal('99.95'), coercible: '22.51'},
|
399
442
|
{attribute: :datetime_value, klass: DateTime, value: DATETIME_VALUE, new_value: DateTime.new(1998, 10, 21, 8, 33, 28, '+5'), coercible: DATETIME_VALUE.to_time},
|
400
|
-
{attribute: :time_value, klass: Time, value: TIME_VALUE, new_value: Time.new(2000,
|
401
|
-
{attribute: :date_value, klass: Date, value: DATE_VALUE, new_value: Date.new(2027,
|
443
|
+
{attribute: :time_value, klass: Time, value: TIME_VALUE, new_value: Time.new(2000, 1, 1, 22, 30, 0, '-04:00')},
|
444
|
+
{attribute: :date_value, klass: Date, value: DATE_VALUE, new_value: Date.new(2027, 4, 2), coercible: DATE_VALUE.to_time},
|
402
445
|
{attribute: :true_value, klass: TrueClass, value: true, new_value: false},
|
403
446
|
{attribute: :false_value, klass: FalseClass, value: false, new_value: true},
|
404
447
|
{attribute: :string_value, klass: String, value: STRING_VALUE, new_value: 'Hello World'},
|
405
448
|
{attribute: :long_string_value, klass: String, value: LONG_STRING_VALUE, new_value: 'A Really long Hello World'},
|
406
449
|
{attribute: :binary_string_value, klass: String, value: BINARY_STRING_VALUE, new_value: "A new Non-UTF8 Binary \x92 string".force_encoding('BINARY')},
|
407
|
-
|
450
|
+
# @formatter:on
|
408
451
|
].each do |value_test|
|
409
452
|
describe "#{value_test[:klass]} values" do
|
410
453
|
before do
|
@@ -420,39 +463,39 @@ class ActiveRecordTest < Minitest::Test
|
|
420
463
|
# Need to dup since minitest attempts to modify the decrypted value which is frozen
|
421
464
|
val = val.dup if val.duplicable?
|
422
465
|
assert_equal @value, val, @user_clone.attributes.ai
|
423
|
-
assert
|
466
|
+
assert user.send(@attribute).is_a?(@klass)
|
424
467
|
end
|
425
468
|
|
426
469
|
it 'coerce data type before save' do
|
427
470
|
u = User.new(@attribute => @value)
|
428
471
|
assert_equal @value, u.send(@attribute)
|
429
|
-
assert u.send(@attribute).
|
472
|
+
assert u.send(@attribute).is_a?(@klass), "Value supposed to be coerced into #{@klass}, but is #{u.send(@attribute).class.name}"
|
430
473
|
end
|
431
474
|
|
432
475
|
it 'permit replacing value with nil' do
|
433
476
|
@user_clone.send("#{@attribute}=".to_sym, nil)
|
434
477
|
@user_clone.save!
|
435
478
|
|
436
|
-
|
437
|
-
assert_nil
|
438
|
-
assert_nil
|
479
|
+
user.reload
|
480
|
+
assert_nil user.send(@attribute)
|
481
|
+
assert_nil user.send("encrypted_#{@attribute}".to_sym)
|
439
482
|
end
|
440
483
|
|
441
484
|
it 'permit replacing value with an empty string' do
|
442
485
|
@user_clone.send("#{@attribute}=".to_sym, '')
|
443
486
|
@user_clone.save!
|
444
487
|
|
445
|
-
|
446
|
-
assert_equal '',
|
447
|
-
assert_equal '',
|
488
|
+
user.reload
|
489
|
+
assert_equal '', user.send(@attribute)
|
490
|
+
assert_equal '', user.send("encrypted_#{@attribute}".to_sym)
|
448
491
|
end
|
449
492
|
|
450
493
|
it 'permit replacing value' do
|
451
494
|
@user_clone.send("#{@attribute}=".to_sym, @new_value)
|
452
495
|
@user_clone.save!
|
453
496
|
|
454
|
-
|
455
|
-
val =
|
497
|
+
user.reload
|
498
|
+
val = user.send(@attribute)
|
456
499
|
# Need to dup since minitest attempts to modify the decrypted value which is frozen
|
457
500
|
val = val.dup if val.duplicable?
|
458
501
|
assert_equal @new_value, val
|
@@ -461,89 +504,84 @@ class ActiveRecordTest < Minitest::Test
|
|
461
504
|
end
|
462
505
|
|
463
506
|
describe 'JSON Serialization' do
|
464
|
-
|
465
|
-
|
466
|
-
# Convert symbols to string in the test
|
467
|
-
@h.keys.each do |k|
|
468
|
-
@h[k.to_s] = @h[k]
|
469
|
-
@h.delete(k)
|
470
|
-
end
|
507
|
+
let :hash_data do
|
508
|
+
{'a' => 'A', 'b' => 'B'}
|
471
509
|
end
|
472
510
|
|
473
511
|
it 'return correct data type' do
|
474
|
-
assert_equal
|
475
|
-
assert
|
512
|
+
assert_equal hash_data, @user_clone.data_json
|
513
|
+
assert user.clone.data_json.is_a?(Hash)
|
476
514
|
end
|
477
515
|
|
478
516
|
it 'not coerce data type (leaves as hash) before save' do
|
479
|
-
u = User.new(data_json:
|
480
|
-
assert_equal
|
481
|
-
assert u.data_json.
|
517
|
+
u = User.new(data_json: hash_data)
|
518
|
+
assert_equal hash_data, u.data_json
|
519
|
+
assert u.data_json.is_a?(Hash)
|
482
520
|
end
|
483
521
|
|
484
522
|
it 'permit replacing value with nil' do
|
485
523
|
@user_clone.data_json = nil
|
486
524
|
@user_clone.save!
|
487
525
|
|
488
|
-
|
489
|
-
assert_nil
|
490
|
-
assert_nil
|
526
|
+
user.reload
|
527
|
+
assert_nil user.data_json
|
528
|
+
assert_nil user.encrypted_data_json
|
491
529
|
end
|
492
530
|
|
493
531
|
it 'permit replacing value' do
|
494
|
-
new_value =
|
532
|
+
new_value = hash_data.clone
|
495
533
|
new_value['c'] = 'C'
|
496
534
|
@user_clone.data_json = new_value
|
497
535
|
@user_clone.save!
|
498
536
|
|
499
|
-
|
500
|
-
assert_equal new_value,
|
537
|
+
user.reload
|
538
|
+
assert_equal new_value, user.data_json
|
501
539
|
end
|
502
540
|
end
|
503
541
|
|
504
542
|
describe 'YAML Serialization' do
|
505
543
|
it 'return correct data type' do
|
506
|
-
assert_equal
|
507
|
-
assert
|
544
|
+
assert_equal hash_data, @user_clone.data_yaml
|
545
|
+
assert user.clone.data_yaml.is_a?(Hash)
|
508
546
|
end
|
509
547
|
|
510
548
|
it 'not coerce data type (leaves as hash) before save' do
|
511
|
-
u = User.new(data_yaml:
|
512
|
-
assert_equal
|
513
|
-
assert u.data_yaml.
|
549
|
+
u = User.new(data_yaml: hash_data)
|
550
|
+
assert_equal hash_data, u.data_yaml
|
551
|
+
assert u.data_yaml.is_a?(Hash)
|
514
552
|
end
|
515
553
|
|
516
554
|
it 'permit replacing value with nil' do
|
517
555
|
@user_clone.data_yaml = nil
|
518
556
|
@user_clone.save!
|
519
557
|
|
520
|
-
|
521
|
-
assert_nil
|
522
|
-
assert_nil
|
558
|
+
user.reload
|
559
|
+
assert_nil user.data_yaml
|
560
|
+
assert_nil user.encrypted_data_yaml
|
523
561
|
end
|
524
562
|
|
525
563
|
it 'permit replacing value' do
|
526
|
-
new_value =
|
564
|
+
new_value = hash_data.clone
|
527
565
|
new_value[:c] = 'C'
|
528
566
|
@user_clone.data_yaml = new_value
|
529
567
|
@user_clone.save!
|
530
568
|
|
531
|
-
|
532
|
-
assert_equal new_value,
|
569
|
+
user.reload
|
570
|
+
assert_equal new_value, user.data_yaml
|
533
571
|
end
|
534
572
|
end
|
535
573
|
end
|
536
574
|
|
537
575
|
describe 'changed?' do
|
538
576
|
it 'return false if it was not changed' do
|
539
|
-
assert_equal false,
|
540
|
-
assert_equal false,
|
577
|
+
assert_equal false, user.encrypted_bank_account_number_changed?
|
578
|
+
assert_equal false, user.bank_account_number_changed?
|
541
579
|
end
|
542
580
|
|
543
581
|
it 'return true if it was changed' do
|
544
|
-
|
545
|
-
assert
|
546
|
-
assert
|
582
|
+
user.bank_account_number = '15424623'
|
583
|
+
assert user.encrypted_bank_account_number_changed?
|
584
|
+
assert user.bank_account_number_changed?
|
547
585
|
end
|
548
586
|
end
|
549
587
|
end
|
@@ -553,7 +591,7 @@ class ActiveRecordTest < Minitest::Test
|
|
553
591
|
UniqueUser.destroy_all
|
554
592
|
@email = 'whatever@not-unique.com'
|
555
593
|
@username = 'gibby007'
|
556
|
-
|
594
|
+
user = UniqueUser.create!(email: @email)
|
557
595
|
@email_user = UniqueUser.create!(username: @username)
|
558
596
|
end
|
559
597
|
|
@@ -563,6 +601,5 @@ class ActiveRecordTest < Minitest::Test
|
|
563
601
|
assert_equal 'has already been taken', duplicate.errors.messages[:encrypted_email].first
|
564
602
|
end
|
565
603
|
end
|
566
|
-
|
567
604
|
end
|
568
605
|
end
|