Passed
Push — master ( fced76...618096 )
by Thomas
02:41
created

EncryptTest::testEncryptedJsonField()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 39
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 22
c 0
b 0
f 0
dl 0
loc 39
rs 9.568
cc 1
nc 1
nop 0
1
<?php
2
3
namespace LeKoala\Encrypt\Test;
4
5
use Exception;
6
use SilverStripe\ORM\DB;
7
use SilverStripe\Assets\File;
8
use SilverStripe\ORM\DataList;
9
use ParagonIE\ConstantTime\Hex;
10
use SilverStripe\ORM\ArrayList;
11
use SilverStripe\ORM\DataObject;
12
use SilverStripe\Security\Member;
13
use LeKoala\Encrypt\EncryptedFile;
14
use LeKoala\Encrypt\EncryptHelper;
15
use SilverStripe\Core\Environment;
16
use SilverStripe\Dev\SapphireTest;
17
use Symfony\Component\Yaml\Parser;
18
use SilverStripe\Security\Security;
19
use LeKoala\Encrypt\EncryptedDBJson;
20
use LeKoala\Encrypt\EncryptedDBField;
21
use LeKoala\Encrypt\MemberKeyProvider;
22
use ParagonIE\CipherSweet\CipherSweet;
23
use LeKoala\Encrypt\HasEncryptedFields;
24
use ParagonIE\CipherSweet\JsonFieldMap;
25
use SilverStripe\ORM\Queries\SQLSelect;
26
use SilverStripe\ORM\Queries\SQLUpdate;
27
use ParagonIE\CipherSweet\KeyProvider\StringProvider;
28
use ParagonIE\CipherSweet\Contract\MultiTenantSafeBackendInterface;
29
30
/**
31
 * Test for Encrypt
32
 *;
33
 * Run with the following command : ./vendor/bin/phpunit ./encrypt/tests/EncryptTest.php
34
 *
35
 * You may need to run:
36
 * php ./framework/cli-script.php dev/build ?flush=all
37
 * before (remember manifest for cli is not the same...)
38
 *
39
 * @group Encrypt
40
 */
41
class EncryptTest extends SapphireTest
42
{
43
    /**
44
     * Defines the fixture file to use for this test class
45
     * @var string
46
     */
47
    protected static $fixture_file = 'EncryptTest.yml';
48
49
    protected static $extra_dataobjects = [
50
        Test_EncryptedModel::class,
51
        Test_EncryptionKey::class,
52
    ];
53
54
    public function setUp(): void
55
    {
56
        // We need to disable automatic decryption to avoid fixtures being re encrypted with the wrong keys
57
        EncryptHelper::setAutomaticDecryption(false);
58
        Environment::setEnv('ENCRYPTION_KEY', '502370dfc69fd6179e1911707e8a5fb798c915900655dea16370d64404be04e5');
59
        Environment::setEnv('OLD_ENCRYPTION_KEY', '502370dfc69fd6179e1911707e8a5fb798c915900655dea16370d64404be04e4');
60
        parent::setUp();
61
        EncryptHelper::setAutomaticDecryption(true);
62
63
        // test extension is available
64
        if (!extension_loaded('sodium')) {
65
            throw new Exception("You must load sodium extension for this");
66
        }
67
68
        // Generate our test data from scratch
69
        // Use some old engine first
70
        // $this->generateData();
71
72
        // $this->showRowsFromDb();
73
        // $this->writeDataFromYml();
74
    }
75
76
    public function tearDown(): void
77
    {
78
        parent::tearDown();
79
    }
80
81
    protected function generateData()
82
    {
83
        EncryptHelper::clearCipherSweet();
84
        EncryptHelper::setForcedEncryption("nacl");
85
        $someText = 'some text';
86
        $data = [
87
            'MyText' => $someText . ' text',
88
            'MyHTMLText' => '<p>' . $someText . ' html</p>',
89
            'MyVarchar' => 'encrypted varchar value',
90
            'MyIndexedVarchar' => "some_searchable_value",
91
            'MyNumber' => "0123456789",
92
        ];
93
        $record = Test_EncryptedModel::get()->filter('Name', 'demo')->first();
94
        foreach ($data as $k => $v) {
95
            $record->$k = $v;
96
        }
97
        $record->write();
98
        EncryptHelper::clearCipherSweet();
99
        $record = Test_EncryptedModel::get()->filter('Name', 'demo3')->first();
100
        foreach ($data as $k => $v) {
101
            $record->$k = $v;
102
        }
103
        $record->write();
104
        // use regular engine
105
        EncryptHelper::clearCipherSweet();
106
        EncryptHelper::setForcedEncryption(null);
107
        $record = Test_EncryptedModel::get()->filter('Name', 'demo2')->first();
108
        foreach ($data as $k => $v) {
109
            $record->$k = $v;
110
        }
111
        $record->write();
112
    }
113
114
    protected function showRowsFromDb()
115
    {
116
        $result = DB::query("SELECT * FROM EncryptedModel");
117
        echo '<pre>' . "\n";
118
        // print_r(iterator_to_array($result));
119
        foreach ($result as $row) {
120
            $this->showAsYml($row);
121
        }
122
        die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
123
    }
124
125
    protected function writeDataFromYml()
126
    {
127
        $ymlParser = new Parser;
128
        $ymlData = $ymlParser->parseFile(__DIR__ . '/EncryptTest.yml');
129
130
        foreach ($ymlData["LeKoala\\Encrypt\\Test\\Test_EncryptedModel"] as $name => $data) {
131
            unset($data['Member']);
132
            $update = new SQLUpdate("EncryptedModel", $data, ["Name" => $data['Name']]);
133
            $update->execute();
134
        }
135
    }
136
137
    protected function showAsYml($row)
138
    {
139
        $fields = [
140
            'Name', 'MyText', 'MyHTMLText', 'MyVarchar',
141
            'MyNumberValue', 'MyNumberBlindIndex', 'MyNumberLastFourBlindIndex',
142
            'MyIndexedVarcharValue', 'MyIndexedVarcharBlindIndex'
143
        ];
144
        echo "  " . $row['Name'] . ":\n";
145
        foreach ($row as $k => $v) {
146
            if (!in_array($k, $fields)) {
147
                continue;
148
            }
149
            echo "    $k: '$v'\n";
150
        }
151
    }
152
153
    /**
154
     * @return Test_EncryptedModel
155
     */
156
    public function getTestModel()
157
    {
158
        return $this->objFromFixture(Test_EncryptedModel::class, 'demo');
159
    }
160
161
    /**
162
     * @return Test_EncryptedModel
163
     */
164
    public function getTestModel2()
165
    {
166
        return $this->objFromFixture(Test_EncryptedModel::class, 'demo2');
167
    }
168
169
    /**
170
     * @return Test_EncryptedModel
171
     */
172
    public function getTestModel3()
173
    {
174
        return $this->objFromFixture(Test_EncryptedModel::class, 'demo3');
175
    }
176
177
    /**
178
     * @return Test_EncryptedModel
179
     */
180
    public function getAdminTestModel()
181
    {
182
        return $this->objFromFixture(Test_EncryptedModel::class, 'admin_record');
183
    }
184
185
    /**
186
     * @return Test_EncryptedModel
187
     */
188
    public function getUser1TestModel()
189
    {
190
        return $this->objFromFixture(Test_EncryptedModel::class, 'user1_record');
191
    }
192
193
    /**
194
     * @return Test_EncryptedModel
195
     */
196
    public function getUser2TestModel()
197
    {
198
        return $this->objFromFixture(Test_EncryptedModel::class, 'user2_record');
199
    }
200
201
    /**
202
     * @return Member
203
     */
204
    public function getAdminMember()
205
    {
206
        return $this->objFromFixture(Member::class, 'admin');
207
    }
208
209
    /**
210
     * @return Member
211
     */
212
    public function getUser1Member()
213
    {
214
        return $this->objFromFixture(Member::class, 'user1');
215
    }
216
217
    /**
218
     * @return Member
219
     */
220
    public function getUser2Member()
221
    {
222
        return $this->objFromFixture(Member::class, 'user2');
223
    }
224
225
    /**
226
     * @return DataList|Member[]
227
     */
228
    public function getAllMembers()
229
    {
230
        return new ArrayList([
0 ignored issues
show
Bug Best Practice introduced by
The expression return new SilverStripe\...his->getUser2Member())) returns the type SilverStripe\ORM\ArrayList which is incompatible with the documented return type SilverStripe\Security\Me...lverStripe\ORM\DataList.
Loading history...
231
            $this->getAdminMember(),
232
            $this->getUser1Member(),
233
            $this->getUser2Member(),
234
        ]);
235
    }
236
237
    /**
238
     * @return File
239
     */
240
    public function getRegularFile()
241
    {
242
        return $this->objFromFixture(File::class, 'regular');
243
    }
244
245
    /**
246
     * @return File
247
     */
248
    public function getEncryptedFile()
249
    {
250
        return $this->objFromFixture(File::class, 'encrypted');
251
    }
252
253
    /**
254
     * @return EncryptedFile
255
     */
256
    public function getEncryptedFile2()
257
    {
258
        // Figure out how to do this properly in yml
259
        $record = $this->objFromFixture(File::class, 'encrypted2');
260
        $file = new EncryptedFile($record->toMap());
261
        return $file;
262
    }
263
264
    /**
265
     * @param string $class
266
     * @param int $id
267
     * @return array
268
     */
269
    protected function fetchRawData($class, $id)
270
    {
271
        $tableName = DataObject::getSchema()->tableName($class);
272
        $columnIdentifier = DataObject::getSchema()->sqlColumnForField($class, 'ID');
273
        $sql = new SQLSelect('*', [$tableName], [$columnIdentifier => $id]);
274
        $dbRecord = $sql->firstRow()->execute()->first();
275
        return $dbRecord;
276
    }
277
278
    public function testEncryption()
279
    {
280
        $someText = 'some text';
281
        $encrypt = EncryptHelper::encrypt($someText);
282
        $decryptedValue = EncryptHelper::decrypt($encrypt);
283
284
        $this->assertEquals($someText, $decryptedValue);
285
    }
286
287
    public function testIndexes()
288
    {
289
        $indexes = DataObject::getSchema()->databaseIndexes(Test_EncryptedModel::class);
290
        $keys = array_keys($indexes);
291
        $this->assertContains('MyIndexedVarcharBlindIndex', $keys, "Index is not defined in : " . implode(", ", $keys));
292
        $this->assertContains('MyNumberLastFourBlindIndex', $keys, "Index is not defined in : " . implode(", ", $keys));
293
    }
294
295
    public function testSearch()
296
    {
297
        $singl = singleton(Test_EncryptedModel::class);
298
299
        /** @var EncryptedDBField $obj  */
300
        $obj = $singl->dbObject('MyIndexedVarchar');
301
        $record = $obj->fetchRecord('some_searchable_value');
302
303
        // echo '<pre>';print_r("From test: " . $record->MyIndexedVarchar);die();
304
        $this->assertNotEmpty($record);
305
        $this->assertEquals("some text text", $record->MyText);
306
        $this->assertEquals("some_searchable_value", $record->MyIndexedVarchar);
307
        $this->assertEquals("some_searchable_value", $record->dbObject('MyIndexedVarchar')->getValue());
308
309
        // Also search our super getter method
310
        $recordAlt = Test_EncryptedModel::getByBlindIndex('MyIndexedVarchar', 'some_searchable_value');
311
        $this->assertNotEmpty($record);
312
        $this->assertEquals($recordAlt->ID, $record->ID);
313
314
        // Can we get a list ?
315
        $list = Test_EncryptedModel::getAllByBlindIndex('MyIndexedVarchar', 'some_searchable_value');
316
        $this->assertInstanceOf(DataList::class, $list);
317
318
        $record = $obj->fetchRecord('some_unset_value');
319
        $this->assertEmpty($record);
320
321
        // Let's try our four digits index
322
        $obj = $singl->dbObject('MyNumber');
323
        $record = $obj->fetchRecord('6789', 'LastFourBlindIndex');
324
        $searchValue = $obj->getSearchValue('6789', 'LastFourBlindIndex');
325
        // $searchParams = $obj->getSearchParams('6789', 'LastFourBlindIndex');
326
        // print_r($searchParams);
327
        $this->assertNotEmpty($record, "Nothing found for $searchValue");
328
        $this->assertEquals("0123456789", $record->MyNumber);
329
    }
330
331
    public function testSearchFilter()
332
    {
333
        $record = Test_EncryptedModel::get()->filter('MyIndexedVarchar:Encrypted', 'some_searchable_value')->first();
334
        $this->assertNotEmpty($record);
335
        $this->assertEquals(1, $record->ID);
336
        $this->assertNotEquals(2, $record->ID);
337
338
        $record = Test_EncryptedModel::get()->filter('MyIndexedVarchar:Encrypted', 'some_unset_value')->first();
339
        $this->assertEmpty($record);
340
    }
341
342
    public function testRotation()
343
    {
344
        $model = $this->getTestModel3();
345
        $data = $this->fetchRawData(Test_EncryptedModel::class, $model->ID);
0 ignored issues
show
Unused Code introduced by
The assignment to $data is dead and can be removed.
Loading history...
346
347
        $old = EncryptHelper::getEngineForEncryption("nacl");
348
        $result = $model->needsToRotateEncryption($old);
349
        $this->assertTrue($result);
350
351
        $result = $model->rotateEncryption($old);
352
        $this->assertTrue($result);
353
    }
354
355
    public function testCompositeOptions()
356
    {
357
        $model = $this->getTestModel();
358
359
        /** @var EncryptedDBField $myNumber */
360
        $myNumber = $model->dbObject('MyNumber');
361
362
        $this->assertEquals(10, $myNumber->getDomainSize());
363
        $this->assertEquals(4, $myNumber->getOutputSize());
364
        $this->assertEquals(EncryptedDBField::LARGE_INDEX_SIZE, $myNumber->getIndexSize());
365
366
        /** @var EncryptedDBField $MyIndexedVarchar */
367
        $MyIndexedVarchar = $model->dbObject('MyIndexedVarchar');
368
369
        // Default config values
370
        $this->assertEquals(EncryptHelper::DEFAULT_DOMAIN_SIZE, $MyIndexedVarchar->getDomainSize());
371
        $this->assertEquals(EncryptHelper::DEFAULT_OUTPUT_SIZE, $MyIndexedVarchar->getOutputSize());
372
        $this->assertEquals(EncryptedDBField::LARGE_INDEX_SIZE, $MyIndexedVarchar->getIndexSize());
373
    }
374
375
    public function testIndexPlanner()
376
    {
377
        $sizes = EncryptHelper::planIndexSizesForClass(Test_EncryptedModel::class);
378
        $this->assertNotEmpty($sizes);
379
        $this->assertArrayHasKey("min", $sizes);
380
        $this->assertArrayHasKey("max", $sizes);
381
        $this->assertArrayHasKey("indexes", $sizes);
382
        $this->assertArrayHasKey("estimated_population", $sizes);
383
        $this->assertArrayHasKey("coincidence_count", $sizes);
384
    }
385
386
    public function testFixture()
387
    {
388
        // this one use nacl encryption and will be rotated transparently
389
        $model = $this->getTestModel();
390
391
        $result = $model->needsToRotateEncryption(EncryptHelper::getEngineForEncryption("nacl"));
392
        $this->assertTrue($result);
393
394
        // Ensure we have our blind indexes
395
        $this->assertTrue($model->hasDatabaseField('MyIndexedVarcharValue'));
396
        $this->assertTrue($model->hasDatabaseField('MyIndexedVarcharBlindIndex'));
397
        $this->assertTrue($model->hasDatabaseField('MyNumberValue'));
398
        $this->assertTrue($model->hasDatabaseField('MyNumberBlindIndex'));
399
        $this->assertTrue($model->hasDatabaseField('MyNumberLastFourBlindIndex'));
400
401
        if (class_uses($model, HasEncryptedFields::class)) {
0 ignored issues
show
Bug introduced by
LeKoala\Encrypt\HasEncryptedFields::class of type string is incompatible with the type boolean expected by parameter $autoload of class_uses(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

401
        if (class_uses($model, /** @scrutinizer ignore-type */ HasEncryptedFields::class)) {
Loading history...
402
            $this->assertTrue($model->hasEncryptedField('MyVarchar'));
403
            $this->assertTrue($model->hasEncryptedField('MyIndexedVarchar'));
404
        }
405
406
        // print_r($model);
407
        /*
408
         [record:protected] => Array
409
        (
410
            [ClassName] => LeKoala\Encrypt\Test\Test_EncryptedModel
411
            [LastEdited] => 2020-12-15 10:09:47
412
            [Created] => 2020-12-15 10:09:47
413
            [Name] => demo
414
            [MyText] => nacl:mQ1g5ugjYSWjFd-erM6-xlB_EbWp1bOAUPbL4fa3Ce5SX6LP7sFCczkFx_lRABvZioWJXx-L
415
            [MyHTMLText] => nacl:836In6YCaEf3_mRJR7NOC_s0P8gIFESgmPnHCefTe6ycY_6CLKVmT0_9KWHgnin-WGXMJawkS1hS87xwQw==
416
            [MyVarchar] => nacl:ZeOw8-dcBdFemtGm-MRJ5pCSipOtAO5-zBRms8F5Elex08GuoL_JKbdN-CiOP-u009MJfvGZUkx9Ru5Zn0_y
417
            [RegularFileID] => 2
418
            [EncryptedFileID] => 3
419
            [MyIndexedVarcharBlindIndex] => 04bb6edd
420
            [ID] => 1
421
            [RecordClassName] => LeKoala\Encrypt\Test\Test_EncryptedModel
422
        )
423
        */
424
425
        $varcharValue = 'encrypted varchar value';
426
        $varcharWithIndexValue = 'some_searchable_value';
427
        // regular fields are not affected
428
        $this->assertEquals('demo', $model->Name);
429
430
        // automatically rotated fields store an exception
431
        $this->assertNotEmpty($model->dbObject("MyVarchar")->getEncryptionException());
432
433
        // get value
434
        $this->assertEquals($varcharValue, $model->dbObject('MyVarchar')->getValue());
435
        // encrypted fields work transparently when using trait
436
        $this->assertEquals($varcharValue, $model->MyVarchar);
437
438
        // since dbobject cache can be cleared, exception is gone
439
        $this->assertEmpty($model->dbObject("MyVarchar")->getEncryptionException());
440
441
442
        $this->assertTrue($model->dbObject('MyIndexedVarchar') instanceof EncryptedDBField);
443
        $this->assertTrue($model->dbObject('MyIndexedVarchar')->hasField('Value'));
444
445
        $model->MyIndexedVarchar = $varcharWithIndexValue;
0 ignored issues
show
Bug Best Practice introduced by
The property MyIndexedVarchar does not exist on LeKoala\Encrypt\Test\Test_EncryptedModel. Since you implemented __set, consider adding a @property annotation.
Loading history...
446
        $model->write();
447
        $this->assertEquals($varcharWithIndexValue, $model->MyIndexedVarchar);
0 ignored issues
show
Bug Best Practice introduced by
The property MyIndexedVarchar does not exist on LeKoala\Encrypt\Test\Test_EncryptedModel. Since you implemented __get, consider adding a @property annotation.
Loading history...
448
449
        $dbRecord = $this->fetchRawData(get_class($model), $model->ID);
450
        // print_r($dbRecord);
451
        /*
452
        Array
453
(
454
    [ID] => 1
455
    [ClassName] => LeKoala\Encrypt\Test\Test_EncryptedModel
456
    [LastEdited] => 2020-12-15 10:10:27
457
    [Created] => 2020-12-15 10:10:27
458
    [Name] => demo
459
    [MyText] => nacl:aDplmA9hs7naqiPwWdNRMcYNUltf4mOs8KslRQZ4vCdnJylnbjAJYChtVH7wiiygsAHWqbM6
460
    [MyHTMLText] => nacl:dMvk5Miux0bsSP1SjaXQRlbGogNTu7UD3p6AlNHFMAEGXOQz03hkBx43C-WelCS0KUdAN9ewuwuXZqMmRA==
461
    [MyVarchar] => nacl:sZRenCG6En7Sg_HmsUHkNy_1MXOstly7eHm0i2iq83kTFH40UsQj-HTqxxYfx0ghuWSKbcqHQ7_OAEy4pcPm
462
    [RegularFileID] => 2
463
    [EncryptedFileID] => 3
464
    [MyNumberValue] =>
465
    [MyNumberBlindIndex] =>
466
    [MyNumberLastFourBlindIndex] =>
467
    [MyIndexedVarcharValue] =>
468
    [MyIndexedVarcharBlindIndex] => 04bb6edd
469
)
470
*/
471
        $this->assertNotEquals($varcharValue, $dbRecord['MyVarchar']);
472
        $this->assertNotEmpty($dbRecord['MyVarchar']);
473
        $this->assertTrue(EncryptHelper::isEncrypted($dbRecord['MyVarchar']));
474
    }
475
476
    public function testFixture2()
477
    {
478
        // this one has only brng encryption
479
        $model = $this->getTestModel2();
480
481
        $result = $model->needsToRotateEncryption(EncryptHelper::getCipherSweet());
482
        $this->assertFalse($result);
483
484
        // Ensure we have our blind indexes
485
        $this->assertTrue($model->hasDatabaseField('MyIndexedVarcharValue'));
486
        $this->assertTrue($model->hasDatabaseField('MyIndexedVarcharBlindIndex'));
487
        $this->assertTrue($model->hasDatabaseField('MyNumberValue'));
488
        $this->assertTrue($model->hasDatabaseField('MyNumberBlindIndex'));
489
        $this->assertTrue($model->hasDatabaseField('MyNumberLastFourBlindIndex'));
490
491
        if (class_uses($model, HasEncryptedFields::class)) {
0 ignored issues
show
Bug introduced by
LeKoala\Encrypt\HasEncryptedFields::class of type string is incompatible with the type boolean expected by parameter $autoload of class_uses(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

491
        if (class_uses($model, /** @scrutinizer ignore-type */ HasEncryptedFields::class)) {
Loading history...
492
            $this->assertTrue($model->hasEncryptedField('MyVarchar'));
493
            $this->assertTrue($model->hasEncryptedField('MyIndexedVarchar'));
494
        }
495
496
497
        // print_r($model);
498
        /*
499
        [record:protected] => Array
500
        (
501
            [ClassName] => LeKoala\Encrypt\Test\Test_EncryptedModel
502
            [LastEdited] => 2021-07-07 13:38:48
503
            [Created] => 2021-07-07 13:38:48
504
            [Name] => demo2
505
            [MyText] => brng:XLzehy47IgENco4DcZj75u9D2p53UjDMCmTFGPNdmzYYxVVbDsaVWuZP1dTvIDaYagVggNAxT8S9fUTXw55VyIv6OxYJrQ==
506
            [MyHTMLText] => brng:bJ-6iGa-gjl9M6-UaNvtSrRuFLwDTLC6SIekrPHTcN_nmIUaK_VEFNAGVd3q__siNsvVXLreSlunpSyJ4JmF8eyI12ltz_s-eV6WVXw=
507
            [MyVarchar] => brng:qNEVUW3TS6eACSS4v1_NK0FOiG5JnbihmOHR1DU4L8Pt63OXQIJr_Kpd34J1IHaJXZWt4uuk2SZgskmvf8FrfApag_sRypca87MegXg_wQ==
508
            [RegularFileID] => 0
509
            [EncryptedFileID] => 0
510
            [MyNumberValue] => brng:pKYd8mXDduwhudwWeoE_ByO6IkvVlykVa6h09DTYFdHcb52yA1R5yhTEqQQjz1ndADFRa9WLLM3_e1U8PfPTiP4E
511
            [MyNumberBlindIndex] => a1de44f9
512
            [MyNumberLastFourBlindIndex] => addb
513
            [MyIndexedVarcharValue] => brng:TBD63tu-P9PluzI_zKTZ17P-4bhFvhbW7eOeSOOnDEf7n3Ytv2_52rlvGTVSJeWr5f6Z5eqrxi-RL5B6V0PrUmEqhfE2TGt-IdH5hfU=
514
            [MyIndexedVarcharBlindIndex] => 216d113a
515
            [ID] => 2
516
            [RecordClassName] => LeKoala\Encrypt\Test\Test_EncryptedModel
517
        )
518
        */
519
520
        $varcharValue = 'encrypted varchar value';
521
        $varcharWithIndexValue = 'some_searchable_value';
522
        // regular fields are not affected
523
        $this->assertEquals('demo2', $model->Name);
524
525
        // get value
526
        $this->assertEquals($varcharValue, $model->dbObject('MyVarchar')->getValue());
527
        // encrypted fields work transparently when using trait
528
        $this->assertEquals($varcharValue, $model->MyVarchar);
529
530
531
        $this->assertTrue($model->dbObject('MyIndexedVarchar') instanceof EncryptedDBField);
532
        $this->assertTrue($model->dbObject('MyIndexedVarchar')->hasField('Value'));
533
534
        $model->MyIndexedVarchar = $varcharWithIndexValue;
0 ignored issues
show
Bug Best Practice introduced by
The property MyIndexedVarchar does not exist on LeKoala\Encrypt\Test\Test_EncryptedModel. Since you implemented __set, consider adding a @property annotation.
Loading history...
535
        $model->write();
536
        $this->assertEquals($varcharWithIndexValue, $model->MyIndexedVarchar);
0 ignored issues
show
Bug Best Practice introduced by
The property MyIndexedVarchar does not exist on LeKoala\Encrypt\Test\Test_EncryptedModel. Since you implemented __get, consider adding a @property annotation.
Loading history...
537
538
        $dbRecord = $this->fetchRawData(get_class($model), $model->ID);
539
        // print_r($dbRecord);
540
        /*
541
        Array
542
(
543
    [ID] => 2
544
    [ClassName] => LeKoala\Encrypt\Test\Test_EncryptedModel
545
    [LastEdited] => 2021-07-07 13:52:10
546
    [Created] => 2021-07-07 13:52:08
547
    [Name] => demo2
548
    [MyText] => brng:IQ-6VoXJedlAGdoCPFUVTSnipUPR4k9YSi3Ik8_oPfUmMVDhA1kgTBFdG_6k08xLhD39G0ksVD_nMtUF4Opo6Zxgkc5Qww==
549
    [MyHTMLText] => brng:ATmS8Tooc0j2FN5zB8ojmhgNHD-vncvm1ljX8aF7rR6bbsD8pEwyX7BJ3mPg6WEzwyye4uriGskFy30GL9LEKsGs1hs40JJgs6rgwKA=
550
    [MyVarchar] => brng:zxu2RFNjqDGV0JmxF1WPMtxDKTyfOtvVztXfbnV3aYJAzro7RwHhSs8HhasHvdPOQ2Vxi_oDieRgcE8XeP3nyoF3tYJrJp3Mo9XdYXj2tw==
551
    [RegularFileID] => 0
552
    [EncryptedFileID] => 0
553
    [MyNumberValue] => brng:pKYd8mXDduwhudwWeoE_ByO6IkvVlykVa6h09DTYFdHcb52yA1R5yhTEqQQjz1ndADFRa9WLLM3_e1U8PfPTiP4E
554
    [MyNumberBlindIndex] => a1de44f9
555
    [MyNumberLastFourBlindIndex] => addb
556
    [MyIndexedVarcharValue] => brng:0ow_r7UD3FXYXxq7kjVzA3uY1ThFYfAWxZFAHA0aRoohLfQW_ZBa0Q8w5A3hyLJhT6djM6xR43O_jeEfP-w_fRaH3nXRI5RW7tO78JY=
557
    [MyIndexedVarcharBlindIndex] => 216d113a
558
)
559
*/
560
        $this->assertNotEquals($varcharValue, $dbRecord['MyVarchar']);
561
        $this->assertNotEmpty($dbRecord['MyVarchar']);
562
        $this->assertTrue(EncryptHelper::isEncrypted($dbRecord['MyVarchar']));
563
    }
564
565
    public function testRecordIsEncrypted()
566
    {
567
        $model = new Test_EncryptedModel();
568
569
        // echo "*** start \n";
570
        // Let's write some stuff
571
        $someText = 'some text';
572
        $model->MyText = $someText . ' text';
573
        $model->MyHTMLText = '<p>' . $someText . ' html</p>';
574
        $model->MyVarchar = 'encrypted varchar value';
575
        $model->MyIndexedVarchar = "some_searchable_value";
0 ignored issues
show
Bug Best Practice introduced by
The property MyIndexedVarchar does not exist on LeKoala\Encrypt\Test\Test_EncryptedModel. Since you implemented __set, consider adding a @property annotation.
Loading history...
576
        $model->MyNumber = "0123456789";
577
        // All fields are marked as changed, including "hidden" fields
578
        // MyNumber will mark as changed MyNumber, MyNumberValue, MuNumberBlindIndex, MyNumberLastFourBlindIndex
579
        // echo '<pre>';
580
        // print_r(array_keys($model->getChangedFields()));
581
        // die();
582
        $id = $model->write();
583
584
        $this->assertNotEmpty($id);
585
586
        // For the model, its the same
587
        $this->assertEquals($someText . ' text', $model->MyText);
588
        $this->assertEquals($someText . ' text', $model->dbObject('MyText')->getValue());
589
        $this->assertEquals($someText . ' text', $model->getField('MyText'));
590
        $this->assertEquals('<p>' . $someText . ' html</p>', $model->MyHTMLText);
591
592
        // In the db, it's not the same
593
        $dbRecord = $this->fetchRawData(get_class($model), $model->ID);
594
595
        if (!EncryptHelper::isEncrypted($dbRecord['MyIndexedVarcharValue'])) {
596
            print_r($dbRecord);
597
        }
598
599
        /*
600
(
601
    [ID] => 2
602
    [ClassName] => LeKoala\Encrypt\Test\Test_EncryptedModel
603
    [LastEdited] => 2020-12-15 10:20:39
604
    [Created] => 2020-12-15 10:20:39
605
    [Name] =>
606
    [MyText] => nacl:yA3XhjUpxE6cS3VMOVI4eqpolP1vRZDYjFySULZiazi9V3HSugC3t8KgImnGV5jP1VzEytVX
607
    [MyHTMLText] => nacl:F3D33dZ2O7qtlmkX-fiaYwSjAo6RC03aiAWRTkfSJOZikcSfezjwmi9DPJ4EO0hYeVc9faRgA3RmTDajRA==
608
    [MyVarchar] => nacl:POmdt3mTUSgPJw3ttfi2G9HgHAE4FRX4FQ5CSBicj4JsEwyPwrP-JKYGcs5drFYLId3cMVf6m8daUY7Ao4Cz
609
    [RegularFileID] => 0
610
    [EncryptedFileID] => 0
611
    [MyNumberValue] => nacl:2wFOX_qahm-HmzQPXvcBFhWCG1TaGQgeM7vkebLxRXDfMpzAxhxkExVgBi8caPYrwvA=
612
    [MyNumberBlindIndex] => 5e0bd888
613
    [MyNumberLastFourBlindIndex] => 276b
614
    [MyIndexedVarcharValue] => nacl:BLi-zF02t0Zet-ADP3RT8v5RTsM11WKIyjlJ1EVHIai2HwjxCIq92gfsay5zqiLic14dXtwigb1kI179QQ==
615
    [MyIndexedVarcharBlindIndex] => 04bb6edd
616
)
617
        */
618
        $text = isset($dbRecord['MyText']) ? $dbRecord['MyText'] : null;
619
        $this->assertNotEmpty($text);
620
        $this->assertNotEquals($someText, $text, "Data is not encrypted in the database");
621
        // Composite fields should work as well
622
        $this->assertNotEmpty($dbRecord['MyIndexedVarcharValue']);
623
        $this->assertNotEmpty($dbRecord['MyIndexedVarcharBlindIndex']);
624
625
        // Test save into
626
        $modelFieldsBefore = $model->getQueriedDatabaseFields();
627
        $model->MyIndexedVarchar = 'new_value';
628
        $dbObj = $model->dbObject('MyIndexedVarchar');
629
        // $dbObj->setValue('new_value', $model);
630
        // $dbObj->saveInto($model);
631
        $modelFields = $model->getQueriedDatabaseFields();
632
        // print_r($modelFields);
633
        $this->assertTrue($dbObj->isChanged());
634
        $changed = implode(", ", array_keys($model->getChangedFields()));
635
        $this->assertNotEquals($modelFieldsBefore['MyIndexedVarchar'], $modelFields['MyIndexedVarchar'], "It should not have the same value internally anymore");
636
        $this->assertTrue($model->isChanged('MyIndexedVarchar'), "Field is not properly marked as changed, only have : " . $changed);
637
        $this->assertEquals('new_value', $dbObj->getValue());
638
        $this->assertNotEquals('new_value', $modelFields['MyIndexedVarcharValue'], "Unencrypted value is not set on value field");
639
640
        // Somehow this is not working on travis? composite fields don't save encrypted data although it works locally
641
        $this->assertNotEquals("some_searchable_value", $dbRecord['MyIndexedVarcharValue'], "Data is not encrypted in the database");
642
643
        // if we load again ?
644
        // it should work thanks to our trait
645
        // by default, data will be loaded encrypted if we don't use the trait and call getField directly
646
        $model2 = $model::get()->byID($model->ID);
647
        $this->assertEquals($someText . ' text', $model2->MyText, "Data does not load properly");
648
        $this->assertEquals('<p>' . $someText . ' html</p>', $model2->MyHTMLText, "Data does not load properly");
649
    }
650
651
    public function testFileEncryption()
652
    {
653
        $regularFile = $this->getRegularFile();
654
        $encryptedFile = $this->getEncryptedFile();
655
        $encryptedFile2 = $this->getEncryptedFile2();
656
657
        $this->assertEquals(0, $regularFile->Encrypted);
658
659
        // Even if we marked it as 1 in the yml, reflect actual value
660
        $encryptedFile->updateEncryptionStatus();
661
662
        $this->assertEquals(0, $encryptedFile->Encrypted, "The encrypted flag was not reset");
663
        $this->assertEquals(0, $encryptedFile2->Encrypted);
664
665
        // test encryption
666
        $string = 'Some content';
667
        $stream = fopen('php://memory', 'r+');
668
        fwrite($stream, $string);
669
        rewind($stream);
670
        $encryptedFile->setFromStream($stream, 'secret.doc');
671
        $encryptedFile->write();
672
        $encryptedFile2->setFromStream($stream, 'secret.doc');
673
        $encryptedFile2->write();
674
675
        $this->assertFalse($encryptedFile->isEncrypted());
676
        // It is automatically encrypted
677
        $this->assertTrue($encryptedFile2->isEncrypted());
0 ignored issues
show
Bug introduced by
The method isEncrypted() does not exist on LeKoala\Encrypt\EncryptedFile. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

677
        $this->assertTrue($encryptedFile2->/** @scrutinizer ignore-call */ isEncrypted());
Loading history...
678
679
        $encryptedFile->encryptFileIfNeeded();
680
681
        $this->assertTrue($encryptedFile->isEncrypted());
682
        $this->assertTrue($encryptedFile->Encrypted);
683
684
        // still encrypted?
685
        $encryptedFile->encryptFileIfNeeded();
686
        $this->assertTrue($encryptedFile->isEncrypted());
687
        $this->assertTrue($encryptedFile->Encrypted);
688
689
        // set something new
690
        $string = 'Some content';
691
        $stream = fopen('php://memory', 'r+');
692
        fwrite($stream, $string);
693
        rewind($stream);
694
        $encryptedFile->setFromStream($stream, 'secret.doc');
695
        $encryptedFile->write();
696
        $encryptedFile2->setFromStream($stream, 'secret.doc');
697
        $encryptedFile2->write();
698
699
        // we need to update manually
700
        $encryptedFile->updateEncryptionStatus();
701
702
        // It is not encrypted nor marked as such
703
        $this->assertFalse($encryptedFile->isEncrypted());
704
        $this->assertFalse($encryptedFile->Encrypted);
705
        // Ir was automatically encrypted again
706
        $this->assertTrue($encryptedFile2->isEncrypted());
707
        $this->assertTrue($encryptedFile2->Encrypted);
708
709
        // No file => no encryption
710
        $encryptedFile2->deleteFile();
711
        $this->assertFalse($encryptedFile->isEncrypted());
712
    }
713
714
    /**
715
     * @group only
716
     */
717
    public function testMessageEncryption()
718
    {
719
        $admin = $this->getAdminMember();
720
        $user1 = $this->getUser1Member();
721
722
        $adminKeys = Test_EncryptionKey::getKeyPair($admin->ID);
723
        $user1Keys = Test_EncryptionKey::getKeyPair($user1->ID);
724
725
        $this->assertArrayHasKey("public", $adminKeys);
726
        $this->assertArrayHasKey("secret", $adminKeys);
727
        $this->assertArrayHasKey("public", $user1Keys);
728
        $this->assertArrayHasKey("secret", $user1Keys);
729
730
        // $pairs = sodium_crypto_box_keypair();
731
        // $adminKeys['secret'] = sodium_crypto_box_secretkey($pairs);
732
        // $adminKeys['public'] = sodium_crypto_box_publickey($pairs);
733
734
        // $pairs = sodium_crypto_box_keypair();
735
        // $user1Keys['secret'] = sodium_crypto_box_secretkey($pairs);
736
        // $user1Keys['public'] = sodium_crypto_box_publickey($pairs);
737
738
        // $adminKeys['secret'] = Hex::encode($adminKeys['secret']);
739
        // $adminKeys['public'] = Hex::encode($adminKeys['public']);
740
        // $user1Keys['secret'] = Hex::encode($user1Keys['secret']);
741
        // $user1Keys['public'] = Hex::encode($user1Keys['public']);
742
743
        // $adminKeys['secret'] = Hex::decode($adminKeys['secret']);
744
        // $adminKeys['public'] = Hex::decode($adminKeys['public']);
745
        // $user1Keys['secret'] = Hex::decode($user1Keys['secret']);
746
        // $user1Keys['public'] = Hex::decode($user1Keys['public']);
747
748
        $message = 'hello';
749
        // 24
750
        $nonce = random_bytes(SODIUM_CRYPTO_BOX_NONCEBYTES);
751
        $encryption_key = sodium_crypto_box_keypair_from_secretkey_and_publickey($adminKeys['secret'], $user1Keys['public']);
752
        $encrypted = sodium_crypto_box($message, $nonce, $encryption_key);
753
        $this->assertNotEmpty($encrypted);
754
        $this->assertNotEquals($message, $encrypted);
755
756
        // Revert keys to decrypt
757
        $decryption_key = sodium_crypto_box_keypair_from_secretkey_and_publickey($user1Keys['secret'], $adminKeys['public']);
758
        $decrypted = sodium_crypto_box_open($encrypted, $nonce, $decryption_key);
759
        $this->assertNotEmpty($decrypted);
760
        $this->assertEquals($message, $decrypted);
761
    }
762
763
    protected function getMultiTenantProvider()
764
    {
765
        $members = $this->getAllMembers();
766
        $tenants = [];
767
        foreach ($members as $member) {
768
            // You can also use the secret key from a keypair
769
            // $key = Test_EncryptionKey::getForMember($member->ID);
770
            $keyPair = Test_EncryptionKey::getKeyPair($member->ID);
771
            if ($keyPair) {
772
                $tenants[$member->ID] = new StringProvider($keyPair['secret']);
773
                // $tenants[$member->ID] = new StringProvider($key);
774
            }
775
        }
776
        $provider = new MemberKeyProvider($tenants);
777
        return $provider;
778
    }
779
780
    /**
781
     * @group multi-tenant
782
     * @group only
783
     */
784
    public function testMultiTenantProvider()
785
    {
786
        // echo '<pre>';
787
        // print_r(EncryptHelper::generateKeyPair());
788
        // die();
789
        $admin = $this->getAdminMember();
790
        $user1 = $this->getUser1Member();
791
        $user2 = $this->getUser2Member();
792
793
        $adminModel = $this->getAdminTestModel();
794
        $user1Model = $this->getUser1TestModel();
795
        $user2Model = $this->getUser2TestModel();
796
797
        $provider = $this->getMultiTenantProvider();
798
799
        Security::setCurrentUser($admin);
800
        EncryptHelper::clearCipherSweet();
801
        $cs = EncryptHelper::getCipherSweet($provider);
802
803
        $this->assertInstanceOf(MultiTenantSafeBackendInterface::class, $cs->getBackend());
804
805
        $string = "my content";
806
        $record = new Test_EncryptedModel();
807
        // $record = Test_EncryptedModel::get()->filter('ID', $user2Model->ID)->first();
808
        $record->MyText = $string;
809
        // We need to set active tenant ourselves because orm records fields one by one
810
        // it doesn't go through injectMetadata
811
        $record->MemberID = Security::getCurrentUser()->ID ?? 0;
0 ignored issues
show
Bug Best Practice introduced by
The property MemberID does not exist on LeKoala\Encrypt\Test\Test_EncryptedModel. Since you implemented __set, consider adding a @property annotation.
Loading history...
812
        $record->write();
813
814
        // echo '<pre>';
815
        // print_r($this->fetchRawData(Test_EncryptedModel::class, $record->ID));
816
        // die();
817
818
        $freshRecord = Test_EncryptedModel::get()->filter('ID', $record->ID)->first();
819
820
        $this->assertEquals($admin->ID, Security::getCurrentUser()->ID, "Make sure the right member is logged in");
821
        // He can decode
822
        $this->assertEquals($string, $freshRecord->MyText);
823
824
        // He can also decode his content from the db
825
        $adminRecord = Test_EncryptedModel::get()->filter('ID', $adminModel->ID)->first();
826
        // echo '<pre>';print_r($adminRecord);die();
827
        $this->assertEquals($string, $adminRecord->MyText);
828
829
        // He cannot decode
830
        Security::setCurrentUser($user1);
831
        // We don't need to set active tenant because our MemberKeyProvider reads currentUser automatically
832
        // $provider->setActiveTenant($user1->ID);
833
        $freshRecord = Test_EncryptedModel::get()->filter('ID', $record->ID)->first();
834
        $this->assertNotEquals($string, $freshRecord->MyText);
835
836
        // Test tenant from row
837
        $this->assertEquals($admin->ID, $cs->getTenantFromRow($adminModel->toMap()));
838
        $this->assertEquals($user1->ID, $cs->getTenantFromRow($user1Model->toMap()));
839
        $this->assertEquals($user2->ID, $cs->getTenantFromRow($user2Model->toMap()));
840
841
        // Current user can decode what he can
842
        Security::setCurrentUser($admin);
843
        $freshRecord = Test_EncryptedModel::get()->filter('ID', $adminModel->ID)->first();
844
        $this->assertEquals($string, $freshRecord->MyText, "Invalid content for admin model #{$adminModel->ID}");
845
        $freshRecord = Test_EncryptedModel::get()->filter('ID', $user2Model->ID)->first();
846
        $this->assertNotEquals($string, $freshRecord->MyText, "Invalid content for user2 model #{$user2Model->ID}");
847
848
        // Thanks to getTenantFromRow we should be able to rotate encryption
849
        // rotate from admin to user2
850
        Security::setCurrentUser($user2);
851
        $freshRecord = Test_EncryptedModel::get()->filter('ID', $adminModel->ID)->first();
852
        $freshRecord->MemberID = $user2->ID;
853
        $freshRecord->write();
854
        $this->assertNotEquals($string, $freshRecord->MyText);
855
        // We can keep the same provider but we need to clone it and change the active tenant
856
        $cs->setActiveTenant($user2->ID);
857
858
        // clone will not deep clone the key provider with the active tenant
859
        // $old = clone $cs;
860
        $clonedProvider = clone $provider;
861
        $clonedProvider->setForcedTenant($admin->ID);
862
        $old = EncryptHelper::getEngineWithProvider(EncryptHelper::getBackendForEncryption("brng"), $clonedProvider);
863
864
        $freshRecord->rotateEncryption($old);
865
        $freshRecord = Test_EncryptedModel::get()->filter('ID', $adminModel->ID)->first();
866
        $this->assertEquals($string, $freshRecord->MyText);
867
868
        // Admin can't read anymore, don't forget to refresh record from db
869
        Security::setCurrentUser($admin);
870
        $freshRecord = Test_EncryptedModel::get()->filter('ID', $adminModel->ID)->first();
871
        $this->assertNotEquals($string, $freshRecord->MyText);
872
873
        // Cleanup
874
        EncryptHelper::clearCipherSweet();
875
    }
876
877
    public function testJsonField()
878
    {
879
        $model = $this->getTestModel();
880
881
        $longstring = str_repeat("lorem ipsum loquor", 100);
882
        $array = [];
883
        foreach (range(1, 100) as $i) {
884
            $array["key_$i"] = $longstring . $i;
885
        }
886
887
        $model->MyJson = $array;
0 ignored issues
show
Documentation Bug introduced by
It seems like $array of type array or array is incompatible with the declared type string of property $MyJson.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
888
        $model->write();
889
890
        $freshRecord = Test_EncryptedModel::get()->filter('ID', $model->ID)->first();
891
892
        $this->assertEquals(json_encode($array), $freshRecord->MyJson);
893
        $this->assertEquals(json_decode(json_encode($array)), $freshRecord->dbObject('MyJson')->decode());
894
        $this->assertEquals($array, $freshRecord->dbObject('MyJson')->toArray());
895
        $this->assertEquals($array, $freshRecord->dbObject('MyJson')->decodeArray());
896
        $this->assertEquals($model->dbObject('MyJson')->toArray(), $freshRecord->dbObject('MyJson')->toArray());
897
    }
898
899
    public function testEncryptedJsonField()
900
    {
901
        $model = $this->getTestModel();
902
903
        /** @var EncryptedDBJson $field */
904
        $field = $model->dbObject('MyEncryptedJson');
905
906
        $map = (new JsonFieldMap())
0 ignored issues
show
Unused Code introduced by
The assignment to $map is dead and can be removed.
Loading history...
907
            ->addTextField('name')
908
            ->addBooleanField('active')
909
            ->addIntegerField('age');
910
911
        // d(str_replace("\"", "\\\"", (string)$map));
912
913
        $encryptedJsonField = $field->getEncryptedJsonField();
914
915
        $data = [
916
            'name' => 'test name',
917
            'active' => true,
918
            'age' => 42,
919
            'not_encrypted' => "this is not encrypted"
920
        ];
921
922
        $encryptedJsonData = $encryptedJsonField->encryptJson($data);
923
924
        $decoded = json_decode($encryptedJsonData, JSON_OBJECT_AS_ARRAY);
0 ignored issues
show
Bug introduced by
LeKoala\Encrypt\Test\JSON_OBJECT_AS_ARRAY of type integer is incompatible with the type boolean|null expected by parameter $associative of json_decode(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

924
        $decoded = json_decode($encryptedJsonData, /** @scrutinizer ignore-type */ JSON_OBJECT_AS_ARRAY);
Loading history...
925
926
        // it is properly encrypted if required
927
        $this->assertEquals($data['not_encrypted'], $decoded['not_encrypted']);
928
        $this->assertNotEquals($data['name'], $decoded['name']);
929
930
        // we can write
931
        $model->MyEncryptedJson = $data;
0 ignored issues
show
Documentation Bug introduced by
It seems like $data of type array<string,integer|string|true> is incompatible with the declared type string of property $MyEncryptedJson.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
932
        $model->write();
933
934
        $dbData = DB::query("SELECT MyEncryptedJson FROM EncryptedModel WHERE ID = " . $model->ID)->value();
935
        $decodedDbData = json_decode($dbData, JSON_OBJECT_AS_ARRAY);
936
        $this->assertEquals($data['not_encrypted'], $decodedDbData['not_encrypted']);
937
        $this->assertNotEquals($data['name'], $decodedDbData['name']);
938
    }
939
940
    public function testFashHash()
941
    {
942
        $model = $this->getTestModel();
943
944
        /** @var EncryptedDBField $encrField */
945
        $encrField = $model->dbObject('MyIndexedVarchar');
946
947
        $value = (string)$model->MyIndexedVarchar;
0 ignored issues
show
Bug Best Practice introduced by
The property MyIndexedVarchar does not exist on LeKoala\Encrypt\Test\Test_EncryptedModel. Since you implemented __get, consider adding a @property annotation.
Loading history...
948
        $bi = $model->MyIndexedVarcharBlindIndex;
0 ignored issues
show
Bug Best Practice introduced by
The property MyIndexedVarcharBlindIndex does not exist on LeKoala\Encrypt\Test\Test_EncryptedModel. Since you implemented __get, consider adding a @property annotation.
Loading history...
949
950
        $aad = '';
951
952
        $t = microtime(true);
953
        $slowBi = $encrField->getEncryptedField(null, false)->prepareForStorage($value, $aad);
954
        $slowBi2 = $encrField->getEncryptedField(null, false)->prepareForStorage($value, $aad);
955
        $et = microtime(true) - $t;
956
957
        $t2 = microtime(true);
958
        $fastBi = $encrField->getEncryptedField(null, true)->prepareForStorage($value, $aad);
959
        $fastBi2 = $encrField->getEncryptedField(null, true)->prepareForStorage($value, $aad);
960
        $et2 = microtime(true) - $t2;
961
962
        // Values are not equals, but blind indexes are
963
        $this->assertNotEquals($slowBi2[0], $slowBi[0]);
964
        $this->assertEquals($slowBi2[1]['MyIndexedVarcharBlindIndex'], $slowBi[1]['MyIndexedVarcharBlindIndex']);
965
        $this->assertEquals($bi, $slowBi[1]['MyIndexedVarcharBlindIndex']);
966
        $this->assertNotEquals($fastBi2[0], $fastBi[0]);
967
        $this->assertEquals($fastBi2[1]['MyIndexedVarcharBlindIndex'], $fastBi[1]['MyIndexedVarcharBlindIndex']);
968
969
        // Slow indexes are not the same as fast indexes
970
        $this->assertNotEquals($fastBi[1]['MyIndexedVarcharBlindIndex'], $slowBi[1]['MyIndexedVarcharBlindIndex']);
971
        $this->assertNotEquals($fastBi2[1]['MyIndexedVarcharBlindIndex'], $slowBi2[1]['MyIndexedVarcharBlindIndex']);
972
973
        // It is faster to generate fast indexes
974
        // $et2 = 0.0004119873046875
975
        // $et = 0.0683131217956543
976
977
        $this->assertTrue($et2 <= $et);
978
979
        // We can convert and keep stored values readable
980
981
        $result = EncryptHelper::convertHashType($model, 'MyIndexedVarchar');
982
        $this->assertTrue($result);
983
984
        $freshRecord = Test_EncryptedModel::get()->filter('ID', $model->ID)->first();
985
        $freshValue = (string)$freshRecord->MyIndexedVarchar;
986
        $this->assertEquals($value, $freshValue);
987
988
        // We can find it using new hash
989
        /** @var EncryptedDBField $freshEncrField */
990
        $freshEncrField = $freshRecord->dbObject('MyIndexedVarchar');
991
992
        $blindIndex = $freshEncrField->getEncryptedField(null, true)->getBlindIndex($freshValue, 'MyIndexedVarcharBlindIndex');
993
        $freshRecord2 = Test_EncryptedModel::get()->filter('MyIndexedVarcharBlindIndex', $blindIndex)->first();
994
        $this->assertEquals($freshRecord2->ID, $freshRecord->ID);
995
    }
996
}
997