pod4 0.9.3 → 0.10.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.
@@ -0,0 +1,379 @@
1
+ require "date"
2
+ require "openssl"
3
+ require "octothorpe"
4
+
5
+ require "pod4"
6
+ require "pod4/encrypting"
7
+ require "pod4/null_interface"
8
+ require "pod4/errors"
9
+
10
+
11
+ describe "(Model with Encryption)" do
12
+
13
+ ##
14
+ # Encrypt / decrypt
15
+ #
16
+ def encrypt(key, iv=nil, plaintext)
17
+ cipher = OpenSSL::Cipher.new(iv ? Pod4::Encrypting::CIPHER_IV : Pod4::Encrypting::CIPHER_NO_IV)
18
+ cipher.encrypt
19
+ cipher.key = key
20
+ cipher.iv = iv if iv
21
+ cipher.update(plaintext) + cipher.final
22
+ end
23
+
24
+ let(:encryption_key) { "dflkasdgklajndgnalkghlgasdgasdghaalsdg" }
25
+
26
+ let(:medical_model_class) do # model with an IV column
27
+ Class.new Pod4::Model do
28
+ include Pod4::Encrypting
29
+ attr_columns :id, :nhs_no # note, we don't bother to name encrypted columns
30
+ encrypted_columns :name, :ailment, :prescription
31
+ set_key "dflkasdgklajndgnalkghlgasdgasdghaalsdg"
32
+ set_iv_column :nonce
33
+ set_interface NullInterface.new(:id, :nhs_no, :name, :ailment, :prescription, :nonce, [])
34
+ end
35
+ end
36
+
37
+ let(:medical_model_bad_class) do # model with an IV column and a very very short key
38
+ Class.new Pod4::Model do
39
+ include Pod4::Encrypting
40
+ attr_columns :id, :nhs_no # note, we don't bother to name encrypted columns
41
+ encrypted_columns :name, :ailment, :prescription
42
+ set_key "d"
43
+ set_iv_column :nonce
44
+ set_interface NullInterface.new(:id, :nhs_no, :name, :ailment, :prescription, :nonce, [])
45
+ end
46
+ end
47
+
48
+ let(:diary_model_class) do # model without an IV column
49
+ Class.new Pod4::Model do
50
+ include Pod4::Encrypting
51
+ attr_columns :id, :date, :heading, :text
52
+ encrypted_columns :heading, :text
53
+ set_key "dflkasdgklajndgnalkghlgasdgasdghaalsdg"
54
+ set_interface NullInterface.new(:id, :date, :heading, :text, [])
55
+ end
56
+ end
57
+
58
+ let(:m40) do
59
+ m = medical_model_class.new
60
+ m.id = 40
61
+ m.nhs_no = "123456"
62
+ m.name = "fred"
63
+ m.ailment = "sore toe"
64
+ m.prescription = "suck thumb"
65
+ m
66
+ end
67
+
68
+ let(:d44) do
69
+ d = diary_model_class.new
70
+ d.id = 44
71
+ d.date = Date.new(2018,4,14)
72
+ d.heading = "fred"
73
+ d.text = "sore toe"
74
+ d
75
+ end
76
+
77
+ let(:m40_record) { m40.interface.read(40) }
78
+ let(:d44_record) { d44.interface.read(44) }
79
+
80
+
81
+ describe "Model::CIPHER_IV" do
82
+
83
+ it "should be a valid string to initialize OpenSSL::Cipher" do
84
+ expect{ OpenSSL::Cipher.new(medical_model_class::CIPHER_IV) }.not_to raise_exception
85
+ end
86
+
87
+ end # of Model::CIPHER_IV
88
+
89
+
90
+ describe "Model::CIPHER_NO_IV" do
91
+
92
+ it "should be a valid string to initialize OpenSSL::Cipher" do
93
+ expect{ OpenSSL::Cipher.new(medical_model_class::CIPHER_NO_IV) }.not_to raise_exception
94
+ end
95
+
96
+ end # of Model::CIPHER_NO_IV
97
+
98
+
99
+ describe "Model.set_key" do
100
+
101
+ it "requires a string" do
102
+ expect( medical_model_class ).to respond_to(:set_key).with(1).argument
103
+ end
104
+
105
+ end # of Model.set_key
106
+
107
+
108
+ describe "Model.encryption_key" do
109
+
110
+ it "returns the key passed to set_key" do
111
+ expect( medical_model_class.encryption_key ).to eq encryption_key
112
+ end
113
+
114
+ end # of Model.encryption_key
115
+
116
+
117
+ describe "Model.set_iv_column" do
118
+
119
+ it "requires an IV column" do
120
+ expect( medical_model_class ).to respond_to(:set_iv_column).with(1).argument
121
+ end
122
+
123
+ it "exposes the encryption_iv attribute, just like attr_accessor" do
124
+ m20 = medical_model_class.new(20)
125
+ expect( m20 ).to respond_to(:nonce)
126
+ expect( m20 ).to respond_to(:nonce=)
127
+ end
128
+
129
+ # See also: Model#encryption_iv
130
+
131
+ end # of Model.set_iv_column
132
+
133
+
134
+ describe "Model.encryption_iv_column" do
135
+
136
+ it "returns the column passed to set_iv_column" do
137
+ expect( medical_model_class.encryption_iv_column ).to eq :nonce
138
+ end
139
+
140
+ end # of Model.encryption_iv_column
141
+
142
+
143
+ describe "Model.encrypted_columns" do
144
+
145
+ it "requires a list of columns" do
146
+ expect( medical_model_class ).to respond_to(:encrypted_columns).with(1).argument
147
+ end
148
+
149
+ it "adds the columns to the model's column list, just like attr_columns" do
150
+ expect( medical_model_class.columns ).
151
+ to match_array( %i|id nonce nhs_no name ailment prescription| )
152
+
153
+ expect( diary_model_class.columns ).to match_array( %i|id date heading text| )
154
+ end
155
+
156
+ it "exposes the columns just like attr_accessor" do
157
+ m20 = medical_model_class.new(20)
158
+
159
+ expect( m20 ).to respond_to(:name)
160
+ expect( m20 ).to respond_to(:ailment)
161
+ expect( m20 ).to respond_to(:prescription)
162
+ expect( m20 ).to respond_to(:name=)
163
+ expect( m20 ).to respond_to(:ailment=)
164
+ expect( m20 ).to respond_to(:prescription=)
165
+ end
166
+
167
+ end # of Model.encrypted_columns
168
+
169
+
170
+ describe "Model.encryption_columns" do
171
+
172
+ it "exposes the columns given in encrypted_columns" do
173
+ expect( medical_model_class.encryption_columns ).
174
+ to match_array( %i|name ailment prescription| )
175
+
176
+ end
177
+
178
+ end # of Model.encryption_columns
179
+
180
+
181
+ describe "(Creating a record)" do
182
+
183
+ context "when we don't have an IV column" do
184
+
185
+ it "scrambles the encrypted columns and leaves the others alone" do
186
+ d44.create
187
+ expect( d44_record.>>.date ).to eq d44.date
188
+ expect( d44_record.>>.heading ).not_to eq d44.heading
189
+ expect( d44_record.>>.text ).not_to eq d44.text
190
+ end
191
+
192
+ end
193
+
194
+ context "when we have an IV column" do
195
+
196
+ it "writes an IV to the nonce field" do
197
+ m40.create
198
+ expect( m40_record.>>.nonce ).not_to be_nil
199
+ expect( m40_record.>>.nonce ).to eq m40.nonce
200
+ expect( m40_record.>>.nonce ).to eq m40.encryption_iv
201
+ end
202
+
203
+ it "scrambles the encrypted columns and leaves the others alone" do
204
+ m40.create
205
+ expect( m40_record.>>.nhs_no ).to eq m40.nhs_no
206
+ expect( m40_record.>>.name ).not_to eq m40.name
207
+ expect( m40_record.>>.ailment ).not_to eq m40.ailment
208
+ expect( m40_record.>>.prescription ).not_to eq m40.prescription
209
+ end
210
+
211
+ end
212
+
213
+ end # of (Creating a record)
214
+
215
+
216
+ describe "(reading a record)" do
217
+
218
+ context "when we have no IV column" do
219
+ before(:each) { d44.create }
220
+
221
+ it "decrypts the columns" do
222
+ d = diary_model_class.new(44).read
223
+ expect( d.date ).to eq Date.new(2018,4,14)
224
+ expect( d.heading ).to eq "fred"
225
+ expect( d.text ).to eq "sore toe"
226
+ end
227
+
228
+ end
229
+
230
+ context "when we have an IV column" do
231
+ before(:each) { m40.create }
232
+
233
+ it "returns the IV field as encryption_iv" do
234
+ m = medical_model_class.new(40).read
235
+ expect( m.encryption_iv ).not_to be_nil
236
+ expect( m.encryption_iv ).to eq m.nonce
237
+ end
238
+
239
+ it "decrypts the columns" do
240
+ m = medical_model_class.new(40).read
241
+ expect( m.nhs_no ).to eq "123456"
242
+ expect( m.name ).to eq "fred"
243
+ expect( m.ailment ).to eq "sore toe"
244
+ expect( m.prescription ).to eq "suck thumb"
245
+ end
246
+
247
+ end
248
+
249
+ end # of (reading a record)
250
+
251
+
252
+ describe "Model#(encryption_iv field)" do
253
+
254
+ it "returns nil for a new model object" do
255
+ m = medical_model_class.new
256
+ m.id = 50
257
+ expect( m.nonce ).to be_nil
258
+ end
259
+
260
+ it "returns the nonce for an existing record" do
261
+ expect( m40.nonce ).to eq m40_record.>>.nonce
262
+ end
263
+
264
+ end # of Model#(encryption_iv field)
265
+
266
+
267
+ describe "Model#encryption_iv" do
268
+
269
+ it "returns nil for a new model object" do
270
+ m = medical_model_class.new
271
+ m.id = 50
272
+
273
+ expect( m.encryption_iv ).to be_nil
274
+ end
275
+
276
+ it "returns nil if we don't have an IV set" do
277
+ d44.create
278
+ expect( d44.encryption_iv ).to be_nil
279
+ end
280
+
281
+ it "returns the nonce for an existing record" do
282
+ expect( m40.encryption_iv ).to eq m40_record.>>.nonce
283
+ end
284
+
285
+ it "returns the same as the actual IV field" do
286
+ expect( m40.encryption_iv ).to eq m40.nonce
287
+ end
288
+
289
+ end # of Model#encryption_iv
290
+
291
+
292
+ describe "Model#map_to_interface" do
293
+
294
+ it "raises Pod4Error if there is an encryption problem, eg, key too short" do
295
+ bad = medical_model_bad_class.new
296
+ bad.id = 999
297
+ bad.nhs_no = "12345"
298
+ bad.name = "alice"
299
+ bad.ailment = "tiny key"
300
+ bad.prescription = "raise an exception"
301
+
302
+ expect{ bad.map_to_interface }.to raise_exception Pod4::Pod4Error
303
+ end
304
+
305
+ context "when we don't have an IV column" do
306
+
307
+ it "encrypts only the encryptable columns for the interface" do
308
+ ot = d44.map_to_interface
309
+ expect( ot.>>.date ).to eq Date.new(2018,4,14)
310
+ expect( ot.>>.heading ).to eq encrypt(encryption_key, "fred")
311
+ expect( ot.>>.text ).to eq encrypt(encryption_key, "sore toe")
312
+ end
313
+
314
+ end
315
+
316
+ context "when we have an IV column" do
317
+ before(:each) { m40.create }
318
+
319
+ it "sets the IV column on create" do
320
+ expect( m40_record.>>.nonce ).not_to be_nil
321
+ end
322
+
323
+ it "encrypts only the encryptable columns for the interface" do
324
+ ot = m40.map_to_interface
325
+ iv = m40.nonce
326
+ expect( ot.>>.nhs_no ).to eq "123456"
327
+ expect( ot.>>.name ).to eq encrypt(encryption_key, iv, "fred")
328
+ expect( ot.>>.ailment ).to eq encrypt(encryption_key, iv, "sore toe")
329
+ expect( ot.>>.prescription ).to eq encrypt(encryption_key, iv, "suck thumb")
330
+ end
331
+
332
+ end
333
+
334
+ end # of Model#map_to_interface
335
+
336
+
337
+ describe "Model#map_to_model" do
338
+
339
+ context "when we don't have an IV column" do
340
+
341
+ it "decrypts only the encryptable columns for the model" do
342
+ d44.create
343
+
344
+ d = diary_model_class.new(44)
345
+ expect( d44_record.>>.heading ).not_to eq "fred"
346
+ expect( d44_record.>>.text ).not_to eq "sore toe"
347
+
348
+ d.read
349
+ expect( d.date ).to eq Date.new(2018,4,14)
350
+ expect( d.heading ).to eq "fred"
351
+ expect( d.text ).to eq "sore toe"
352
+ end
353
+
354
+ end
355
+
356
+ context "when we have an IV column" do
357
+
358
+ it "decrypts only the encryptable columns for the model" do
359
+ m40.create
360
+
361
+ m = medical_model_class.new(40)
362
+ expect( m40_record.>>.name ).not_to eq "fred"
363
+ expect( m40_record.>>.ailment ).not_to eq "sore toe"
364
+ expect( m40_record.>>.prescription ).not_to eq "suck thumb"
365
+
366
+ m.read
367
+ expect( m.nhs_no ).to eq "123456"
368
+ expect( m.name ).to eq "fred"
369
+ expect( m.ailment ).to eq "sore toe"
370
+ expect( m.prescription ).to eq "suck thumb"
371
+ end
372
+
373
+ end
374
+
375
+ end # of Model#map_to_model
376
+
377
+
378
+ end
379
+