Issues (84)

tests/EncryptTest.php (18 issues)

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\DataObject;
11
use SilverStripe\Security\Member;
12
use LeKoala\Encrypt\EncryptedFile;
13
use LeKoala\Encrypt\EncryptHelper;
14
use SilverStripe\Core\Environment;
15
use SilverStripe\Dev\SapphireTest;
16
use Symfony\Component\Yaml\Parser;
17
use SilverStripe\Security\Security;
18
use LeKoala\Encrypt\EncryptedDBJson;
19
use LeKoala\Encrypt\EncryptedDBField;
20
use LeKoala\Encrypt\MemberKeyProvider;
21
use LeKoala\Encrypt\HasEncryptedFields;
22
use ParagonIE\CipherSweet\JsonFieldMap;
23
use SilverStripe\ORM\Queries\SQLSelect;
24
use SilverStripe\ORM\Queries\SQLUpdate;
25
use ParagonIE\CipherSweet\KeyProvider\StringProvider;
26
use ParagonIE\CipherSweet\Contract\MultiTenantSafeBackendInterface;
27
use PHPUnit\Framework\Attributes\Group;
0 ignored issues
show
The type PHPUnit\Framework\Attributes\Group was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
28
use SilverStripe\Model\List\ArrayList;
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
class EncryptTest extends SapphireTest
40
{
41
    /**
42
     * Defines the fixture file to use for this test class
43
     * @var string
44
     */
45
    protected static $fixture_file = 'EncryptTest.yml';
46
47
    protected static $extra_dataobjects = [
48
        Test_EncryptedModel::class,
49
        Test_EncryptionKey::class,
50
    ];
51
52
    public function setUp(): void
53
    {
54
        // We need to disable automatic decryption to avoid fixtures being re encrypted with the wrong keys
55
        EncryptHelper::setAutomaticDecryption(false);
56
        Environment::setEnv('ENCRYPTION_KEY', '502370dfc69fd6179e1911707e8a5fb798c915900655dea16370d64404be04e5');
57
        Environment::setEnv('OLD_ENCRYPTION_KEY', '502370dfc69fd6179e1911707e8a5fb798c915900655dea16370d64404be04e4');
58
        parent::setUp();
59
        EncryptHelper::setAutomaticDecryption(true);
60
61
        // test extension is available
62
        if (!extension_loaded('sodium')) {
63
            throw new Exception("You must load sodium extension for this");
64
        }
65
66
        // Generate our test data from scratch
67
        // Use some old engine first
68
        // $this->generateData();
69
70
        // $this->showRowsFromDb();
71
        // $this->writeDataFromYml();
72
    }
73
74
    public function tearDown(): void
75
    {
76
        parent::tearDown();
77
    }
78
79
    protected function generateData()
80
    {
81
        EncryptHelper::clearCipherSweet();
82
        EncryptHelper::setForcedEncryption("nacl");
83
        $someText = 'some text';
84
        $data = [
85
            'MyText' => $someText . ' text',
86
            'MyHTMLText' => '<p>' . $someText . ' html</p>',
87
            'MyVarchar' => 'encrypted varchar value',
88
            'MyIndexedVarchar' => "some_searchable_value",
89
            'MyNullIndexedVarchar' => null,
90
            'MyNumber' => "0123456789",
91
        ];
92
        $record = Test_EncryptedModel::get()->filter('Name', 'demo')->first();
93
        foreach ($data as $k => $v) {
94
            $record->$k = $v;
95
        }
96
        $record->write();
97
        EncryptHelper::clearCipherSweet();
98
        $record = Test_EncryptedModel::get()->filter('Name', 'demo3')->first();
99
        foreach ($data as $k => $v) {
100
            $record->$k = $v;
101
        }
102
        $record->write();
103
        // use regular engine
104
        EncryptHelper::clearCipherSweet();
105
        EncryptHelper::setForcedEncryption(null);
106
        $record = Test_EncryptedModel::get()->filter('Name', 'demo2')->first();
107
        foreach ($data as $k => $v) {
108
            $record->$k = $v;
109
        }
110
        $record->write();
111
    }
112
113
    protected function showRowsFromDb()
114
    {
115
        $result = DB::query("SELECT * FROM EncryptedModel");
116
        echo '<pre>' . "\n";
117
        // print_r(iterator_to_array($result));
118
        foreach ($result as $row) {
119
            $this->showAsYml($row);
120
        }
121
        die();
0 ignored issues
show
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...
122
    }
123
124
    protected function writeDataFromYml()
125
    {
126
        $ymlParser = new Parser();
127
        $ymlData = $ymlParser->parseFile(__DIR__ . '/EncryptTest.yml');
128
129
        foreach ($ymlData["LeKoala\\Encrypt\\Test\\Test_EncryptedModel"] as $name => $data) {
130
            unset($data['Member']);
131
            $update = new SQLUpdate("EncryptedModel", $data, ["Name" => $data['Name']]);
132
            $update->execute();
133
        }
134
    }
135
136
    protected function showAsYml($row)
137
    {
138
        $fields = [
139
            'Name',
140
            'MyText',
141
            'MyHTMLText',
142
            'MyVarchar',
143
            'MyNumberValue',
144
            'MyNumberBlindIndex',
145
            'MyNumberLastFourBlindIndex',
146
            'MyIndexedVarcharValue',
147
            'MyIndexedVarcharBlindIndex'
148
        ];
149
        echo "  " . $row['Name'] . ":\n";
150
        foreach ($row as $k => $v) {
151
            if (!in_array($k, $fields)) {
152
                continue;
153
            }
154
            echo "    $k: '$v'\n";
155
        }
156
    }
157
158
    /**
159
     * @return Test_EncryptedModel
160
     */
161
    public function getTestModel()
162
    {
163
        return $this->objFromFixture(Test_EncryptedModel::class, 'demo');
164
    }
165
166
    /**
167
     * @return Test_EncryptedModel
168
     */
169
    public function getTestModel2()
170
    {
171
        return $this->objFromFixture(Test_EncryptedModel::class, 'demo2');
172
    }
173
174
    /**
175
     * @return Test_EncryptedModel
176
     */
177
    public function getTestModel3()
178
    {
179
        return $this->objFromFixture(Test_EncryptedModel::class, 'demo3');
180
    }
181
182
    /**
183
     * @return Test_EncryptedModel
184
     */
185
    public function getAdminTestModel()
186
    {
187
        return $this->objFromFixture(Test_EncryptedModel::class, 'admin_record');
188
    }
189
190
    /**
191
     * @return Test_EncryptedModel
192
     */
193
    public function getUser1TestModel()
194
    {
195
        return $this->objFromFixture(Test_EncryptedModel::class, 'user1_record');
196
    }
197
198
    /**
199
     * @return Test_EncryptedModel
200
     */
201
    public function getUser2TestModel()
202
    {
203
        return $this->objFromFixture(Test_EncryptedModel::class, 'user2_record');
204
    }
205
206
    /**
207
     * @return Test_EncryptedModel
208
     */
209
    public function getNewTestModel()
210
    {
211
        return new Test_EncryptedModel();
212
    }
213
214
    /**
215
     * @return Member
216
     */
217
    public function getAdminMember()
218
    {
219
        return $this->objFromFixture(Member::class, 'admin');
220
    }
221
222
    /**
223
     * @return Member
224
     */
225
    public function getUser1Member()
226
    {
227
        return $this->objFromFixture(Member::class, 'user1');
228
    }
229
230
    /**
231
     * @return Member
232
     */
233
    public function getUser2Member()
234
    {
235
        return $this->objFromFixture(Member::class, 'user2');
236
    }
237
238
    /**
239
     * @return ArrayList<Member>
240
     */
241
    public function getAllMembers()
242
    {
243
        return new ArrayList([
244
            $this->getAdminMember(),
245
            $this->getUser1Member(),
246
            $this->getUser2Member(),
247
        ]);
248
    }
249
250
    /**
251
     * @return File
252
     */
253
    public function getRegularFile()
254
    {
255
        return $this->objFromFixture(File::class, 'regular');
256
    }
257
258
    /**
259
     * @return File
260
     */
261
    public function getEncryptedFile()
262
    {
263
        return $this->objFromFixture(File::class, 'encrypted');
264
    }
265
266
    /**
267
     * @return EncryptedFile
268
     */
269
    public function getEncryptedFile2()
270
    {
271
        // Figure out how to do this properly in yml
272
        $record = $this->objFromFixture(File::class, 'encrypted2');
273
        $file = new EncryptedFile($record->toMap());
274
        return $file;
275
    }
276
277
    /**
278
     * @param string $class
279
     * @param int $id
280
     * @return array
281
     */
282
    protected function fetchRawData($class, $id)
283
    {
284
        $tableName = DataObject::getSchema()->tableName($class);
285
        $columnIdentifier = DataObject::getSchema()->sqlColumnForField($class, 'ID');
286
        $sql = new SQLSelect('*', [$tableName], [$columnIdentifier => $id]);
287
        $dbRecord = $sql->firstRow()->execute()->record();
288
        return $dbRecord;
289
    }
290
291
    public function testEncryption()
292
    {
293
        $someText = 'some text';
294
        $encrypt = EncryptHelper::encrypt($someText);
295
        $decryptedValue = EncryptHelper::decrypt($encrypt);
296
297
        $this->assertEquals($someText, $decryptedValue);
298
    }
299
300
    public function testIndexes()
301
    {
302
        $indexes = DataObject::getSchema()->databaseIndexes(Test_EncryptedModel::class);
303
        $keys = array_keys($indexes);
304
        $this->assertContains('MyIndexedVarcharBlindIndex', $keys, "Index is not defined in : " . implode(", ", $keys));
305
        $this->assertContains('MyNumberLastFourBlindIndex', $keys, "Index is not defined in : " . implode(", ", $keys));
306
    }
307
308
    public function testSearch()
309
    {
310
        $singl = singleton(Test_EncryptedModel::class);
311
312
        /** @var EncryptedDBField $obj  */
313
        $obj = $singl->dbObject('MyIndexedVarchar');
314
        $record = $obj->fetchRecord('some_searchable_value');
315
316
        // echo '<pre>';print_r("From test: " . $record->MyIndexedVarchar);die();
317
        $this->assertNotEmpty($record);
318
        $this->assertEquals("some text text", $record->MyText);
319
        $this->assertEquals("some_searchable_value", $record->MyIndexedVarchar);
320
        $this->assertEquals("some_searchable_value", $record->dbObject('MyIndexedVarchar')->getValue());
321
322
        // Also search our super getter method
323
        $recordAlt = Test_EncryptedModel::getByBlindIndex('MyIndexedVarchar', 'some_searchable_value');
324
        $this->assertNotEmpty($record);
325
        $this->assertEquals($recordAlt->ID, $record->ID);
0 ignored issues
show
The property ID does not exist on false.
Loading history...
326
327
        // Can we get a list ?
328
        $list = Test_EncryptedModel::getAllByBlindIndex('MyIndexedVarchar', 'some_searchable_value');
329
        $this->assertInstanceOf(DataList::class, $list);
330
331
        $record = $obj->fetchRecord('some_unset_value');
332
        $this->assertEmpty($record);
333
334
        // Let's try our four digits index
335
        $obj = $singl->dbObject('MyNumber');
336
        $record = $obj->fetchRecord('6789', 'LastFourBlindIndex');
337
        $searchValue = $obj->getSearchValue('6789', 'LastFourBlindIndex');
338
        // $searchParams = $obj->getSearchParams('6789', 'LastFourBlindIndex');
339
        // print_r($searchParams);
340
        $this->assertNotEmpty($record, "Nothing found for $searchValue");
341
        $this->assertEquals("0123456789", $record->MyNumber);
342
    }
343
344
    public function testSearchFilter()
345
    {
346
        $record = Test_EncryptedModel::get()->filter('MyIndexedVarchar:Encrypted', 'some_searchable_value')->first();
347
        $this->assertNotEmpty($record);
348
        $this->assertEquals(1, $record->ID);
349
        $this->assertNotEquals(2, $record->ID);
350
351
        $record = Test_EncryptedModel::get()->filter('MyIndexedVarchar:Encrypted', 'some_unset_value')->first();
352
        $this->assertEmpty($record);
353
    }
354
355
    public function testRotation()
356
    {
357
        $model = $this->getTestModel3();
358
        $data = $this->fetchRawData(Test_EncryptedModel::class, $model->ID);
359
360
        $old = EncryptHelper::getEngineForEncryption("nacl");
361
        $result = $model->needsToRotateEncryption($old);
362
        $this->assertTrue($result);
363
364
        $result = $model->rotateEncryption($old);
365
        $this->assertTrue($result);
366
    }
367
368
    public function testCompositeOptions()
369
    {
370
        $model = $this->getTestModel();
371
372
        /** @var EncryptedDBField $myNumber */
373
        $myNumber = $model->dbObject('MyNumber');
374
375
        $this->assertEquals(10, $myNumber->getDomainSize());
376
        $this->assertEquals(4, $myNumber->getOutputSize());
377
        $this->assertEquals(EncryptedDBField::LARGE_INDEX_SIZE, $myNumber->getIndexSize());
378
379
        /** @var EncryptedDBField $MyIndexedVarchar */
380
        $MyIndexedVarchar = $model->dbObject('MyIndexedVarchar');
381
382
        // Default config values
383
        $this->assertEquals(EncryptHelper::DEFAULT_DOMAIN_SIZE, $MyIndexedVarchar->getDomainSize());
384
        $this->assertEquals(EncryptHelper::DEFAULT_OUTPUT_SIZE, $MyIndexedVarchar->getOutputSize());
385
        $this->assertEquals(EncryptedDBField::LARGE_INDEX_SIZE, $MyIndexedVarchar->getIndexSize());
386
    }
387
388
    public function testIndexPlanner()
389
    {
390
        $sizes = EncryptHelper::planIndexSizesForClass(Test_EncryptedModel::class);
391
        $this->assertNotEmpty($sizes);
392
        $this->assertArrayHasKey("min", $sizes);
393
        $this->assertArrayHasKey("max", $sizes);
394
        $this->assertArrayHasKey("indexes", $sizes);
395
        $this->assertArrayHasKey("estimated_population", $sizes);
396
        $this->assertArrayHasKey("coincidence_count", $sizes);
397
    }
398
399
    public function testFixture()
400
    {
401
        // this one use nacl encryption and will be rotated transparently
402
        $model = $this->getTestModel();
403
404
        $result = $model->needsToRotateEncryption(EncryptHelper::getEngineForEncryption("nacl"));
405
        $this->assertTrue($result);
406
407
        // Ensure we have our blind indexes
408
        $this->assertTrue($model->hasDatabaseField('MyIndexedVarcharValue'));
409
        $this->assertTrue($model->hasDatabaseField('MyIndexedVarcharBlindIndex'));
410
        $this->assertTrue($model->hasDatabaseField('MyNumberValue'));
411
        $this->assertTrue($model->hasDatabaseField('MyNumberBlindIndex'));
412
        $this->assertTrue($model->hasDatabaseField('MyNumberLastFourBlindIndex'));
413
414
        if (class_uses($model, HasEncryptedFields::class)) {
415
            $this->assertTrue($model->hasEncryptedField('MyVarchar'));
416
            $this->assertTrue($model->hasEncryptedField('MyIndexedVarchar'));
417
        }
418
419
        // print_r($model);
420
        /*
421
         [record:protected] => Array
422
        (
423
            [ClassName] => LeKoala\Encrypt\Test\Test_EncryptedModel
424
            [LastEdited] => 2020-12-15 10:09:47
425
            [Created] => 2020-12-15 10:09:47
426
            [Name] => demo
427
            [MyText] => nacl:mQ1g5ugjYSWjFd-erM6-xlB_EbWp1bOAUPbL4fa3Ce5SX6LP7sFCczkFx_lRABvZioWJXx-L
428
            [MyHTMLText] => nacl:836In6YCaEf3_mRJR7NOC_s0P8gIFESgmPnHCefTe6ycY_6CLKVmT0_9KWHgnin-WGXMJawkS1hS87xwQw==
429
            [MyVarchar] => nacl:ZeOw8-dcBdFemtGm-MRJ5pCSipOtAO5-zBRms8F5Elex08GuoL_JKbdN-CiOP-u009MJfvGZUkx9Ru5Zn0_y
430
            [RegularFileID] => 2
431
            [EncryptedFileID] => 3
432
            [MyIndexedVarcharBlindIndex] => 04bb6edd
433
            [ID] => 1
434
            [RecordClassName] => LeKoala\Encrypt\Test\Test_EncryptedModel
435
        )
436
        */
437
438
        $varcharValue = 'encrypted varchar value';
439
        $varcharWithIndexValue = 'some_searchable_value';
440
        // regular fields are not affected
441
        $this->assertEquals('demo', $model->Name);
442
443
        // automatically rotated fields store an exception
444
        $this->assertNotEmpty($model->dbObject("MyVarchar")->getEncryptionException());
445
446
        // get value
447
        $this->assertEquals($varcharValue, $model->dbObject('MyVarchar')->getValue());
448
        // encrypted fields work transparently when using trait
449
        $this->assertEquals($varcharValue, $model->MyVarchar);
450
451
        // since dbobject cache can be cleared, exception is gone
452
        $this->assertEmpty($model->dbObject("MyVarchar")->getEncryptionException());
453
454
455
        $this->assertTrue($model->dbObject('MyIndexedVarchar') instanceof EncryptedDBField);
456
        $this->assertTrue($model->dbObject('MyIndexedVarchar')->hasField('Value'));
457
458
        $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...
459
        $model->write();
460
        $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...
461
462
        $dbRecord = $this->fetchRawData(get_class($model), $model->ID);
463
        // print_r($dbRecord);
464
        /*
465
        Array
466
(
467
    [ID] => 1
468
    [ClassName] => LeKoala\Encrypt\Test\Test_EncryptedModel
469
    [LastEdited] => 2020-12-15 10:10:27
470
    [Created] => 2020-12-15 10:10:27
471
    [Name] => demo
472
    [MyText] => nacl:aDplmA9hs7naqiPwWdNRMcYNUltf4mOs8KslRQZ4vCdnJylnbjAJYChtVH7wiiygsAHWqbM6
473
    [MyHTMLText] => nacl:dMvk5Miux0bsSP1SjaXQRlbGogNTu7UD3p6AlNHFMAEGXOQz03hkBx43C-WelCS0KUdAN9ewuwuXZqMmRA==
474
    [MyVarchar] => nacl:sZRenCG6En7Sg_HmsUHkNy_1MXOstly7eHm0i2iq83kTFH40UsQj-HTqxxYfx0ghuWSKbcqHQ7_OAEy4pcPm
475
    [RegularFileID] => 2
476
    [EncryptedFileID] => 3
477
    [MyNumberValue] =>
478
    [MyNumberBlindIndex] =>
479
    [MyNumberLastFourBlindIndex] =>
480
    [MyIndexedVarcharValue] =>
481
    [MyIndexedVarcharBlindIndex] => 04bb6edd
482
)
483
*/
484
        $this->assertNotEquals($varcharValue, $dbRecord['MyVarchar']);
485
        $this->assertNotEmpty($dbRecord['MyVarchar']);
486
        $this->assertTrue(EncryptHelper::isEncrypted($dbRecord['MyVarchar']));
487
    }
488
489
    public function testFixture2()
490
    {
491
        // this one has only brng encryption
492
        $model = $this->getTestModel2();
493
494
        $result = $model->needsToRotateEncryption(EncryptHelper::getCipherSweet());
495
        $this->assertFalse($result);
496
497
        // Ensure we have our blind indexes
498
        $this->assertTrue($model->hasDatabaseField('MyIndexedVarcharValue'));
499
        $this->assertTrue($model->hasDatabaseField('MyIndexedVarcharBlindIndex'));
500
        $this->assertTrue($model->hasDatabaseField('MyNumberValue'));
501
        $this->assertTrue($model->hasDatabaseField('MyNumberBlindIndex'));
502
        $this->assertTrue($model->hasDatabaseField('MyNumberLastFourBlindIndex'));
503
504
        if (class_uses($model, HasEncryptedFields::class)) {
505
            $this->assertTrue($model->hasEncryptedField('MyVarchar'));
506
            $this->assertTrue($model->hasEncryptedField('MyIndexedVarchar'));
507
        }
508
509
        // print_r($model);
510
        /*
511
[record:protected] => Array
512
(
513
    [ClassName] => LeKoala\Encrypt\Test\Test_EncryptedModel
514
    [LastEdited] => 2021-07-07 13:38:48
515
    [Created] => 2021-07-07 13:38:48
516
    [Name] => demo2
517
    [MyText] => brng:XLzehy47IgENco4DcZj75u9D2p53UjDMCmTFGPNdmzYYxVVbDsaVWuZP1dTvIDaYagVggNAxT8S9fUTXw55VyIv6OxYJrQ==
518
    [MyHTMLText] => brng:bJ-6iGa-gjl9M6-UaNvtSrRuFLwDTLC6SIekrPHTcN_nmIUaK_VEFNAGVd3q__siN...
519
    [MyVarchar] => brng:qNEVUW3TS6eACSS4v1_NK0FOiG5JnbihmOHR1DU4L8Pt63OXQIJr_Kpd34J1IHaJXZWt....
520
    [RegularFileID] => 0
521
    [EncryptedFileID] => 0
522
    [MyNumberValue] => brng:pKYd8mXDduwhudwWeoE_ByO6IkvVlykVa6h09DTYFdHcb52yA1R5yhTEqQQjz1ndADFRa9WLLM3_e1U8PfPTiP4E
523
    [MyNumberBlindIndex] => a1de44f9
524
    [MyNumberLastFourBlindIndex] => addb
525
    [MyIndexedVarcharValue] => brng:TBD63tu-P9PluzI_zKTZ17P-4bhFvhbW7eOeSOOnDEf7n3Ytv2_52rl...
526
    [MyIndexedVarcharBlindIndex] => 216d113a
527
    [ID] => 2
528
    [RecordClassName] => LeKoala\Encrypt\Test\Test_EncryptedModel
529
)
530
        */
531
532
        $varcharValue = 'encrypted varchar value';
533
        $varcharWithIndexValue = 'some_searchable_value';
534
        // regular fields are not affected
535
        $this->assertEquals('demo2', $model->Name);
536
537
        // get value
538
        $this->assertEquals($varcharValue, $model->dbObject('MyVarchar')->getValue());
539
        // encrypted fields work transparently when using trait
540
        $this->assertEquals($varcharValue, $model->MyVarchar);
541
542
543
        $this->assertTrue($model->dbObject('MyIndexedVarchar') instanceof EncryptedDBField);
544
        $this->assertTrue($model->dbObject('MyIndexedVarchar')->hasField('Value'));
545
546
        $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...
547
        $model->write();
548
        $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...
549
550
        $dbRecord = $this->fetchRawData(get_class($model), $model->ID);
551
        // print_r($dbRecord);
552
        /*
553
        Array
554
(
555
    [ID] => 2
556
    [ClassName] => LeKoala\Encrypt\Test\Test_EncryptedModel
557
    [LastEdited] => 2021-07-07 13:52:10
558
    [Created] => 2021-07-07 13:52:08
559
    [Name] => demo2
560
    [MyText] => brng:IQ-6VoXJedlAGdoCPFUVTSnipUPR4k9YSi3Ik8_oPfUmMVDhA1kgTBFdG_6k08xLhD39G0ksVD_nMtUF4Opo6Zxgkc5Qww==
561
    [MyHTMLText] => brng:ATmS8Tooc0j2FN5zB8ojmhgNHD-vncvm1ljX8aF7rR6bbsD8pEwyX7BJ3mPg6WEz...
562
    [MyVarchar] => brng:zxu2RFNjqDGV0JmxF1WPMtxDKTyfOtvVztXfbnV3aYJAzro7RwHhSs8HhasHvdPOQ...
563
    [RegularFileID] => 0
564
    [EncryptedFileID] => 0
565
    [MyNumberValue] => brng:pKYd8mXDduwhudwWeoE_ByO6IkvVlykVa6h09DTYFdHcb52yA1R5yhTEqQQjz1ndADFRa9WLLM3_e1U8PfPTiP4E
566
    [MyNumberBlindIndex] => a1de44f9
567
    [MyNumberLastFourBlindIndex] => addb
568
    [MyIndexedVarcharValue] => brng:0ow_r7UD3FXYXxq7kjVzA3uY1ThFYfAWxZFAHA0aRoohLfQW_ZBa0Q8w...
569
    [MyIndexedVarcharBlindIndex] => 216d113a
570
)
571
*/
572
        $this->assertNotEquals($varcharValue, $dbRecord['MyVarchar']);
573
        $this->assertNotEmpty($dbRecord['MyVarchar']);
574
        $this->assertTrue(EncryptHelper::isEncrypted($dbRecord['MyVarchar']));
575
    }
576
577
    public function testRecordIsEncrypted()
578
    {
579
        $model = new Test_EncryptedModel();
580
581
        // echo "*** start \n";
582
        // Let's write some stuff
583
        $someText = 'some text';
584
        $model->MyText = $someText . ' text';
585
        $model->MyHTMLText = '<p>' . $someText . ' html</p>';
586
        $model->MyVarchar = 'encrypted varchar value';
587
        $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...
588
        $model->MyNumber = "0123456789";
589
        // All fields are marked as changed, including "hidden" fields
590
        // MyNumber will mark as changed MyNumber, MyNumberValue, MuNumberBlindIndex, MyNumberLastFourBlindIndex
591
        // echo '<pre>';
592
        // print_r(array_keys($model->getChangedFields()));
593
        // die();
594
        $id = $model->write();
595
596
        $this->assertNotEmpty($id);
597
598
        // For the model, its the same
599
        $this->assertEquals($someText . ' text', $model->MyText);
600
        $this->assertEquals($someText . ' text', $model->dbObject('MyText')->getValue());
601
        $this->assertEquals($someText . ' text', $model->getField('MyText'));
602
        $this->assertEquals('<p>' . $someText . ' html</p>', $model->MyHTMLText);
603
604
        // In the db, it's not the same
605
        $dbRecord = $this->fetchRawData(get_class($model), $model->ID);
606
607
        if (!EncryptHelper::isEncrypted($dbRecord['MyIndexedVarcharValue'])) {
608
            print_r($dbRecord);
609
        }
610
611
        /*
612
(
613
    [ID] => 2
614
    [ClassName] => LeKoala\Encrypt\Test\Test_EncryptedModel
615
    [LastEdited] => 2020-12-15 10:20:39
616
    [Created] => 2020-12-15 10:20:39
617
    [Name] =>
618
    [MyText] => nacl:yA3XhjUpxE6cS3VMOVI4eqpolP1vRZDYjFySULZiazi9V3HSugC3t8KgImnGV5jP1VzEytVX
619
    [MyHTMLText] => nacl:F3D33dZ2O7qtlmkX-fiaYwSjAo6RC03aiAWRTkfSJOZikcSfezjwmi9DPJ4EO0hYeVc9faRgA3RmTDajRA==
620
    [MyVarchar] => nacl:POmdt3mTUSgPJw3ttfi2G9HgHAE4FRX4FQ5CSBicj4JsEwyPwrP-JKYGcs5drFYLId3cMVf6m8daUY7Ao4Cz
621
    [RegularFileID] => 0
622
    [EncryptedFileID] => 0
623
    [MyNumberValue] => nacl:2wFOX_qahm-HmzQPXvcBFhWCG1TaGQgeM7vkebLxRXDfMpzAxhxkExVgBi8caPYrwvA=
624
    [MyNumberBlindIndex] => 5e0bd888
625
    [MyNumberLastFourBlindIndex] => 276b
626
    [MyIndexedVarcharValue] => nacl:BLi-zF02t0Zet-ADP3RT8v5RTsM11WKIyjlJ1EVHIai2HwjxCIq92gfsay5zqiLic14dXtwigb1kI179QQ==
627
    [MyIndexedVarcharBlindIndex] => 04bb6edd
628
)
629
        */
630
        $text = isset($dbRecord['MyText']) ? $dbRecord['MyText'] : null;
631
        $this->assertNotEmpty($text);
632
        $this->assertNotEquals($someText, $text, "Data is not encrypted in the database");
633
        // Composite fields should work as well
634
        $this->assertNotEmpty($dbRecord['MyIndexedVarcharValue']);
635
        $this->assertNotEmpty($dbRecord['MyIndexedVarcharBlindIndex']);
636
637
        // Test save into
638
        $modelFieldsBefore = $model->getQueriedDatabaseFields();
639
        /** @var EncryptedDBField $beforeValue */
640
        $beforeValue = $modelFieldsBefore['MyIndexedVarchar'];
641
        $model->MyIndexedVarchar = 'new_value';
642
        $dbObj = $model->dbObject('MyIndexedVarchar');
643
        // $dbObj->setValue('new_value', $model);
644
        // $dbObj->saveInto($model);
645
        $modelFields = $model->getQueriedDatabaseFields();
646
        /** @var EncryptedDBField $afterValue */
647
        $afterValue = $modelFieldsBefore['MyIndexedVarchar'];
648
        // print_r($modelFields);
649
        $this->assertTrue($dbObj->isChanged());
650
        $changed = implode(", ", array_keys($model->getChangedFields()));
651
652
        // Note : sometimes we keep the same dbObject, but sometimes not,
653
        // therefore it's hard to compare before and after using dbObject ref
654
        // $this->assertNotEquals($beforeValue->getValue(), $afterValue->getValue(),
655
        // "It should not have the same value internally anymore");
656
        $this->assertTrue(
657
            $model->isChanged('MyIndexedVarchar'),
658
            "Field is not properly marked as changed, only have : " . $changed
659
        );
660
        $this->assertEquals('new_value', $dbObj->getValue());
661
        $this->assertNotEquals(
662
            'new_value',
663
            $modelFields['MyIndexedVarcharValue'] ?? "",
664
            "Unencrypted value is not set on value field"
665
        );
666
667
        // Somehow this is not working on travis? composite fields don't save encrypted data although it works locally
668
        $this->assertNotEquals(
669
            "some_searchable_value",
670
            $dbRecord['MyIndexedVarcharValue'],
671
            "Data is not encrypted in the database"
672
        );
673
674
        // if we load again ?
675
        // it should work thanks to our trait
676
        // by default, data will be loaded encrypted if we don't use the trait and call getField directly
677
        $model2 = $model::get()->byID($model->ID);
678
        $this->assertEquals($someText . ' text', $model2->MyText, "Data does not load properly");
679
        $this->assertEquals('<p>' . $someText . ' html</p>', $model2->MyHTMLText, "Data does not load properly");
680
    }
681
682
    public function testFileEncryption()
683
    {
684
        $regularFile = $this->getRegularFile();
685
        $encryptedFile = $this->getEncryptedFile();
686
        $encryptedFile2 = $this->getEncryptedFile2();
687
688
        $this->assertEquals(0, $regularFile->Encrypted);
689
690
        // Even if we marked it as 1 in the yml, reflect actual value
691
        $encryptedFile->updateEncryptionStatus();
692
693
        $this->assertEquals(0, $encryptedFile->Encrypted, "The encrypted flag was not reset");
694
        $this->assertEquals(0, $encryptedFile2->Encrypted);
695
696
        // test encryption
697
        $string = 'Some content';
698
        $stream = fopen('php://memory', 'r+');
699
        fwrite($stream, $string);
700
        rewind($stream);
701
        $encryptedFile->setFromStream($stream, 'secret.doc');
702
        $encryptedFile->write();
703
        $encryptedFile2->setFromStream($stream, 'secret.doc');
704
        $encryptedFile2->write();
705
706
        $this->assertFalse($encryptedFile->isEncrypted());
707
        // It is automatically encrypted
708
        $this->assertTrue($encryptedFile2->isEncrypted());
0 ignored issues
show
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

708
        $this->assertTrue($encryptedFile2->/** @scrutinizer ignore-call */ isEncrypted());
Loading history...
709
710
        $encryptedFile->encryptFileIfNeeded();
711
712
        $this->assertTrue($encryptedFile->isEncrypted());
713
        $this->assertTrue($encryptedFile->Encrypted);
714
715
        // still encrypted?
716
        $encryptedFile->encryptFileIfNeeded();
717
        $this->assertTrue($encryptedFile->isEncrypted());
718
        $this->assertTrue($encryptedFile->Encrypted);
719
720
        // set something new
721
        $string = 'Some content';
722
        $stream = fopen('php://memory', 'r+');
723
        fwrite($stream, $string);
724
        rewind($stream);
725
        $encryptedFile->setFromStream($stream, 'secret.doc');
726
        $encryptedFile->write();
727
        $encryptedFile2->setFromStream($stream, 'secret.doc');
728
        $encryptedFile2->write();
729
730
        // we need to update manually
731
        $encryptedFile->updateEncryptionStatus();
732
733
        // It is not encrypted nor marked as such
734
        $this->assertFalse($encryptedFile->isEncrypted());
735
        $this->assertFalse($encryptedFile->Encrypted);
736
        // Ir was automatically encrypted again
737
        $this->assertTrue($encryptedFile2->isEncrypted());
738
        $this->assertTrue($encryptedFile2->Encrypted);
739
740
        // No file => no encryption
741
        $encryptedFile2->deleteFile();
742
        $this->assertFalse($encryptedFile->isEncrypted());
743
    }
744
745
    public function testMessageEncryption()
746
    {
747
        $admin = $this->getAdminMember();
748
        $user1 = $this->getUser1Member();
749
750
        $adminKeys = Test_EncryptionKey::getKeyPair($admin->ID);
751
        $user1Keys = Test_EncryptionKey::getKeyPair($user1->ID);
752
753
        $this->assertArrayHasKey("public", $adminKeys);
754
        $this->assertArrayHasKey("secret", $adminKeys);
755
        $this->assertArrayHasKey("public", $user1Keys);
756
        $this->assertArrayHasKey("secret", $user1Keys);
757
758
        // $pairs = sodium_crypto_box_keypair();
759
        // $adminKeys['secret'] = sodium_crypto_box_secretkey($pairs);
760
        // $adminKeys['public'] = sodium_crypto_box_publickey($pairs);
761
762
        // $pairs = sodium_crypto_box_keypair();
763
        // $user1Keys['secret'] = sodium_crypto_box_secretkey($pairs);
764
        // $user1Keys['public'] = sodium_crypto_box_publickey($pairs);
765
766
        // $adminKeys['secret'] = Hex::encode($adminKeys['secret']);
767
        // $adminKeys['public'] = Hex::encode($adminKeys['public']);
768
        // $user1Keys['secret'] = Hex::encode($user1Keys['secret']);
769
        // $user1Keys['public'] = Hex::encode($user1Keys['public']);
770
771
        // $adminKeys['secret'] = Hex::decode($adminKeys['secret']);
772
        // $adminKeys['public'] = Hex::decode($adminKeys['public']);
773
        // $user1Keys['secret'] = Hex::decode($user1Keys['secret']);
774
        // $user1Keys['public'] = Hex::decode($user1Keys['public']);
775
776
        $message = 'hello';
777
        // 24
778
        $nonce = random_bytes(SODIUM_CRYPTO_BOX_NONCEBYTES);
779
        $encryption_key = sodium_crypto_box_keypair_from_secretkey_and_publickey(
780
            $adminKeys['secret'],
781
            $user1Keys['public']
782
        );
783
        $encrypted = sodium_crypto_box($message, $nonce, $encryption_key);
784
        $this->assertNotEmpty($encrypted);
785
        $this->assertNotEquals($message, $encrypted);
786
787
        // Revert keys to decrypt
788
        $decryption_key = sodium_crypto_box_keypair_from_secretkey_and_publickey(
789
            $user1Keys['secret'],
790
            $adminKeys['public']
791
        );
792
        $decrypted = sodium_crypto_box_open($encrypted, $nonce, $decryption_key);
793
        $this->assertNotEmpty($decrypted);
794
        $this->assertEquals($message, $decrypted);
795
    }
796
797
    protected function getMultiTenantProvider()
798
    {
799
        $members = $this->getAllMembers();
800
        $tenants = [];
801
        foreach ($members as $member) {
802
            // You can also use the secret key from a keypair
803
            // $key = Test_EncryptionKey::getForMember($member->ID);
804
            $keyPair = Test_EncryptionKey::getKeyPair($member->ID);
805
            if ($keyPair) {
806
                $tenants[$member->ID] = new StringProvider($keyPair['secret']);
807
                // $tenants[$member->ID] = new StringProvider($key);
808
            }
809
        }
810
        $provider = new MemberKeyProvider($tenants);
811
        return $provider;
812
    }
813
814
    #[Group('multi-tenant')]
815
    public function testMultiTenantProvider()
816
    {
817
        // echo '<pre>';
818
        // print_r(EncryptHelper::generateKeyPair());
819
        // die();
820
        $admin = $this->getAdminMember();
821
        $user1 = $this->getUser1Member();
822
        $user2 = $this->getUser2Member();
823
824
        $adminModel = $this->getAdminTestModel();
825
        $user1Model = $this->getUser1TestModel();
826
        $user2Model = $this->getUser2TestModel();
827
828
        $provider = $this->getMultiTenantProvider();
829
830
        Security::setCurrentUser($admin);
831
        EncryptHelper::clearCipherSweet();
832
        $cs = EncryptHelper::getCipherSweet($provider);
833
834
        $this->assertInstanceOf(MultiTenantSafeBackendInterface::class, $cs->getBackend());
835
836
        $string = "my content";
837
        $record = new Test_EncryptedModel();
838
        // $record = Test_EncryptedModel::get()->filter('ID', $user2Model->ID)->first();
839
        $record->MyText = $string;
840
        // We need to set active tenant ourselves because orm records fields one by one
841
        // it doesn't go through injectMetadata
842
        $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...
843
        $record->write();
844
845
        // echo '<pre>';
846
        // print_r($this->fetchRawData(Test_EncryptedModel::class, $record->ID));
847
        // die();
848
849
        $freshRecord = Test_EncryptedModel::get()->filter('ID', $record->ID)->first();
850
851
        $this->assertEquals($admin->ID, Security::getCurrentUser()->ID, "Make sure the right member is logged in");
852
        // He can decode
853
        $this->assertEquals($string, $freshRecord->MyText);
854
855
        // He can also decode his content from the db
856
        $adminRecord = Test_EncryptedModel::get()->filter('ID', $adminModel->ID)->first();
857
        // echo '<pre>';print_r($adminRecord);die();
858
        $this->assertEquals($string, $adminRecord->MyText);
859
860
        // He cannot decode
861
        Security::setCurrentUser($user1);
862
        // We don't need to set active tenant because our MemberKeyProvider reads currentUser automatically
863
        // $provider->setActiveTenant($user1->ID);
864
        $freshRecord = Test_EncryptedModel::get()->filter('ID', $record->ID)->first();
865
        $this->assertNotEquals($string, $freshRecord->MyText);
866
867
        // Test tenant from row
868
        $this->assertEquals($admin->ID, $cs->getTenantFromRow($adminModel->toMap()));
869
        $this->assertEquals($user1->ID, $cs->getTenantFromRow($user1Model->toMap()));
870
        $this->assertEquals($user2->ID, $cs->getTenantFromRow($user2Model->toMap()));
871
872
        // Current user can decode what he can
873
        Security::setCurrentUser($admin);
874
        $freshRecord = Test_EncryptedModel::get()->filter('ID', $adminModel->ID)->first();
875
        $this->assertEquals($string, $freshRecord->MyText, "Invalid content for admin model #{$adminModel->ID}");
876
        $freshRecord = Test_EncryptedModel::get()->filter('ID', $user2Model->ID)->first();
877
        $this->assertNotEquals($string, $freshRecord->MyText, "Invalid content for user2 model #{$user2Model->ID}");
878
879
        // Thanks to getTenantFromRow we should be able to rotate encryption
880
        // rotate from admin to user2
881
        Security::setCurrentUser($user2);
882
        $freshRecord = Test_EncryptedModel::get()->filter('ID', $adminModel->ID)->first();
883
        $freshRecord->MemberID = $user2->ID;
884
        $freshRecord->write();
885
        $this->assertNotEquals($string, $freshRecord->MyText);
886
        // We can keep the same provider but we need to clone it and change the active tenant
887
        $cs->setActiveTenant($user2->ID);
888
889
        // clone will not deep clone the key provider with the active tenant
890
        // $old = clone $cs;
891
        $clonedProvider = clone $provider;
892
        $clonedProvider->setForcedTenant($admin->ID);
893
        $old = EncryptHelper::getEngineWithProvider(EncryptHelper::getBackendForEncryption("brng"), $clonedProvider);
894
895
        $freshRecord->rotateEncryption($old);
896
        $freshRecord = Test_EncryptedModel::get()->filter('ID', $adminModel->ID)->first();
897
        $this->assertEquals($string, $freshRecord->MyText);
898
899
        // Admin can't read anymore, don't forget to refresh record from db
900
        Security::setCurrentUser($admin);
901
        $freshRecord = Test_EncryptedModel::get()->filter('ID', $adminModel->ID)->first();
902
        $this->assertNotEquals($string, $freshRecord->MyText);
903
904
        // Cleanup
905
        EncryptHelper::clearCipherSweet();
906
    }
907
908
    #[Group('aad')]
909
    public function testAad()
910
    {
911
        $new = $this->getNewTestModel();
912
913
        // Without AAD, it should work fine
914
        EncryptHelper::setAadSource("");
915
916
        $new->MyText = "TEST";
917
        $new->MyIndexedVarchar = "TEST";
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...
918
        $new->write();
919
920
        $this->assertEquals("TEST", $new->dbObject("MyText")->getValue());
921
        $this->assertEquals("TEST", $new->dbObject("MyIndexedVarchar")->getValue());
922
923
        // With AAD, it should work fine as well
924
        // var_dump("*** CREATE ***");
925
        $new = $this->getNewTestModel();
926
927
        EncryptHelper::setAadSource("ID");
928
929
        // var_dump("*** ASSIGN ***");
930
        $new->MyText = "TEST";
931
        $new->MyIndexedVarchar = "TEST";
932
933
        // var_dump("*** WRITE ***");
934
        $new->write();
935
        // On first write, we need to make sure we pick up the ID from the writeBaseRecord operation
936
937
        // The second write should NOT be needed
938
        // $new->MyIndexedVarchar = "TEST";
939
        // $new->write();
940
941
        $dbObj = $new->dbObject("MyIndexedVarchar");
942
943
        $this->assertEquals("TEST", $new->dbObject("MyText")->getValue());
944
        $this->assertEquals("TEST", $dbObj->getValue());
945
        $this->assertNull($dbObj->getEncryptionException());
946
    }
947
948
    public function testJsonField()
949
    {
950
        $model = $this->getTestModel();
951
952
        $longstring = str_repeat("lorem ipsum loquor", 100);
953
        $array = [];
954
        foreach (range(1, 100) as $i) {
955
            $array["key_$i"] = $longstring . $i;
956
        }
957
958
        $model->MyJson = $array;
959
        $model->write();
960
961
        $freshRecord = Test_EncryptedModel::get()->filter('ID', $model->ID)->first();
962
963
        $this->assertEquals(json_encode($array), $freshRecord->MyJson);
964
        $this->assertEquals(json_decode(json_encode($array)), $freshRecord->dbObject('MyJson')->decode());
965
        $this->assertEquals($array, $freshRecord->dbObject('MyJson')->toArray());
966
        $this->assertEquals($array, $freshRecord->dbObject('MyJson')->decodeArray());
967
        $this->assertEquals($model->dbObject('MyJson')->toArray(), $freshRecord->dbObject('MyJson')->toArray());
968
    }
969
970
    public function testEncryptedJsonField()
971
    {
972
        $model = $this->getTestModel();
973
974
        /** @var EncryptedDBJson $field */
975
        $field = $model->dbObject('MyEncryptedJson');
976
977
        $map = (new JsonFieldMap())
978
            ->addTextField('name')
979
            ->addBooleanField('active')
980
            ->addIntegerField('age');
981
982
        $definition = EncryptHelper::convertJsonMapToDefinition($map);
983
        $this->assertIsString($definition);
984
985
        $encryptedJsonField = $field->getEncryptedJsonField();
986
987
        $data = [
988
            'name' => 'test name',
989
            'active' => true,
990
            'age' => 42,
991
            'not_encrypted' => "this is not encrypted"
992
        ];
993
994
        $aad = (string)$model->ID;
995
        $encryptedJsonData = $encryptedJsonField->encryptJson($data, $aad);
996
997
        $this->assertFalse(EncryptHelper::isJsonEncrypted($data));
998
        $this->assertTrue(EncryptHelper::isJsonEncrypted($encryptedJsonData));
999
1000
        $decoded = json_decode($encryptedJsonData, JSON_OBJECT_AS_ARRAY);
1001
1002
        // it is properly encrypted if required
1003
        $this->assertEquals($data['not_encrypted'], $decoded['not_encrypted']);
1004
        $this->assertNotEquals($data['name'], $decoded['name']);
1005
1006
        // we can write
1007
        $model->MyEncryptedJson = $data;
1008
        $model->write();
1009
1010
        $dbData = DB::query("SELECT MyEncryptedJson FROM EncryptedModel WHERE ID = " . $model->ID)->value();
1011
        $decodedDbData = json_decode($dbData, JSON_OBJECT_AS_ARRAY);
1012
1013
        // data is properly stored with partially encrypted json
1014
        $this->assertNotNull($decodedDbData, "got $dbData");
1015
        $this->assertEquals($data['not_encrypted'], $decodedDbData['not_encrypted']);
1016
        $this->assertNotEquals($data['name'], $decodedDbData['name']);
1017
1018
        $freshRecord = Test_EncryptedModel::get()->filter('ID', $model->ID)->first();
1019
        $freshValue = $freshRecord->dbObject('MyEncryptedJson')->toArray();
1020
1021
        // It is decoded transparently
1022
        $this->assertEquals($data, $freshValue);
1023
    }
1024
1025
    public function testFashHash()
1026
    {
1027
        $model = $this->getTestModel();
1028
1029
        /** @var EncryptedDBField $encrField */
1030
        $encrField = $model->dbObject('MyIndexedVarchar');
1031
1032
        $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...
1033
        $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...
1034
1035
        $aad = '';
1036
1037
        $t = microtime(true);
1038
        $slowBi = $encrField->getEncryptedField(null, false)->prepareForStorage($value, $aad);
1039
        $slowBi2 = $encrField->getEncryptedField(null, false)->prepareForStorage($value, $aad);
1040
        $et = microtime(true) - $t;
1041
1042
        $t2 = microtime(true);
1043
        $fastBi = $encrField->getEncryptedField(null, true)->prepareForStorage($value, $aad);
1044
        $fastBi2 = $encrField->getEncryptedField(null, true)->prepareForStorage($value, $aad);
1045
        $et2 = microtime(true) - $t2;
1046
1047
        // Values are not equals, but blind indexes are
1048
        $this->assertNotEquals($slowBi2[0], $slowBi[0]);
1049
        $this->assertEquals($slowBi2[1]['MyIndexedVarcharBlindIndex'], $slowBi[1]['MyIndexedVarcharBlindIndex']);
1050
        $this->assertEquals($bi, $slowBi[1]['MyIndexedVarcharBlindIndex']);
1051
        $this->assertNotEquals($fastBi2[0], $fastBi[0]);
1052
        $this->assertEquals($fastBi2[1]['MyIndexedVarcharBlindIndex'], $fastBi[1]['MyIndexedVarcharBlindIndex']);
1053
1054
        // Slow indexes are not the same as fast indexes
1055
        $this->assertNotEquals($fastBi[1]['MyIndexedVarcharBlindIndex'], $slowBi[1]['MyIndexedVarcharBlindIndex']);
1056
        $this->assertNotEquals($fastBi2[1]['MyIndexedVarcharBlindIndex'], $slowBi2[1]['MyIndexedVarcharBlindIndex']);
1057
1058
        // It is faster to generate fast indexes
1059
        // $et2 = 0.0004119873046875
1060
        // $et = 0.0683131217956543
1061
1062
        $this->assertTrue($et2 <= $et);
1063
1064
        // We can convert and keep stored values readable
1065
1066
        $result = EncryptHelper::convertHashType($model, 'MyIndexedVarchar');
1067
        $this->assertTrue($result);
1068
1069
        $freshRecord = Test_EncryptedModel::get()->filter('ID', $model->ID)->first();
1070
        $freshValue = (string)$freshRecord->MyIndexedVarchar;
1071
        $this->assertEquals($value, $freshValue);
1072
1073
        // We can find it using new hash
1074
        /** @var EncryptedDBField $freshEncrField */
1075
        $freshEncrField = $freshRecord->dbObject('MyIndexedVarchar');
1076
1077
        $blindIndex = $freshEncrField->getEncryptedField(null, true)->getBlindIndex($freshValue, 'MyIndexedVarcharBlindIndex'); // phpcs:ignore
1078
        $freshRecord2 = Test_EncryptedModel::get()->filter('MyIndexedVarcharBlindIndex', $blindIndex)->first();
1079
        $this->assertEquals($freshRecord2->ID, $freshRecord->ID);
1080
    }
1081
1082
    public function testNullValue()
1083
    {
1084
        $model = $this->getTestModel();
1085
1086
        /** @var EncryptedDBField $field */
1087
        $field = $model->dbObject('MyNullIndexedVarchar');
1088
1089
        $e = null;
1090
        try {
1091
            $record = $field->fetchRecord(null);
1092
        } catch (Exception $e) {
1093
            $this->assertStringContainsString('Cannot search an empty value', $e->getMessage());
1094
        }
1095
        $this->assertNotEmpty($e);
1096
    }
1097
1098
    public function testIgnoreID()
1099
    {
1100
        $model = $this->getTestModel();
1101
1102
        /** @var EncryptedDBField $field */
1103
        $field = $model->dbObject('MyNumber');
1104
1105
        $record = $field->fetchRecord($model->MyNumber);
1106
        $this->assertNotEmpty($record);
1107
1108
        $record2 = $field->fetchRecord($model->MyNumber, null, $model->ID);
1109
        $this->assertNotEquals($model->ID, $record2->ID);
0 ignored issues
show
The property ID does not exist on false.
Loading history...
1110
        $this->assertNotEquals($record->ID, $record2->ID);
1111
1112
        $record3 = $field->fetchRecord($model->MyNumber, null, [$model->ID, $record2->ID, "ZZZ"]);
1113
        $this->assertNotEquals($model->ID, $record3->ID);
1114
        $this->assertNotEquals($record->ID, $record3->ID);
1115
        $this->assertNotEquals($record2->ID, $record3->ID);
1116
        $this->assertEquals($model->MyNumber, $record3->MyNumber);
0 ignored issues
show
The property MyNumber does not exist on false.
Loading history...
1117
    }
1118
1119
    public function testWhere()
1120
    {
1121
        $model = $this->getTestModel();
1122
1123
        /** @var EncryptedDBField $field */
1124
        $field = $model->dbObject('MyNumber');
1125
1126
        $record = $field->fetchRecord($model->MyNumber);
1127
        $this->assertNotEmpty($record);
1128
1129
        $record2 = $field->fetchRecord($model->MyNumber, null, null, ["ID != ?" => $model->ID]);
1130
        $this->assertNotEquals($model->ID, $record2->ID);
0 ignored issues
show
The property ID does not exist on false.
Loading history...
1131
        $this->assertNotEquals($record->ID, $record2->ID);
1132
    }
1133
1134
    public function testNullIndex()
1135
    {
1136
        $model = $this->getTestModel();
1137
1138
        // $table = iterator_to_array(DB::query('SELECT * FROM EncryptedModel'));
1139
        /*
1140
[0] => Array
1141
(
1142
    [ID] => 1
1143
    [ClassName] => LeKoala\Encrypt\Test\Test_EncryptedModel
1144
    [LastEdited] => 2024-08-06 09:38:08
1145
    [Created] => 2024-08-06 09:38:08
1146
    [Name] => demo
1147
    [MyText] => nacl:J0MSRR_3SiS-EJ5MTknr4EsdYuJfPelqlOnfc86ZLRB2dAF36E73AfFI1JnQljpLKbm4z0RJ
1148
    [MyHTMLText] => nacl:suuecD8nfDe9znFkX9oFJgBOMNqV-WZ6E4xmmqt0hOq1HLxaZLSuNfQgKKKLl0p0mAx06JdrAW2uMBFjdA==
1149
    [MyVarchar] => nacl:-v5urTD97S09NPHetURc_mby2NLUq1YlJk8xfMVgzi9j6OO9vWpB9miNAegSLs_ynJByp_xFOT8jkwWovfft
1150
    [MyJson] =>
1151
    [MyEncryptedJson] =>
1152
    [RegularFileID] => 2
1153
    [EncryptedFileID] => 3
1154
    [EncryptedFileClassID] => 0
1155
    [MemberID] => 0
1156
    [MyNumberValue] => nacl:moucwmvVb9gABA1YfLyHQpLf_CrjJ_oH3nevNMCNI1klxu8A7B9PGTJgWfgHTctpyBQ=
1157
    [MyNumberBlindIndex] => 9cb2dcc9
1158
    [MyNumberLastFourBlindIndex] => 95a3
1159
    [MyIndexedVarcharValue] => nacl:UjIFz41sx7MOcUm47gX1VArpj9PbAMjytcKHA-mW_PVlc1RsOO5Sqq3d9rzpsNLHPPrdwoA169SzPIeLkw==
1160
    [MyIndexedVarcharBlindIndex] => f6f6771c
1161
    [MyNullIndexedVarcharValue] =>
1162
    [MyNullIndexedVarcharBlindIndex] =>
1163
)
1164
*/
1165
1166
        $rec = DB::query('SELECT MyIndexedVarcharValue, MyIndexedVarcharBlindIndex FROM EncryptedModel WHERE ID = ' . $model->ID)->record(); // phpcs:ignore
1167
        $indexValue = $rec['MyIndexedVarcharBlindIndex'];
1168
        $fieldValue = $rec['MyIndexedVarcharValue'];
1169
        $this->assertNotEmpty($indexValue);
1170
1171
        // some_searchable_value
1172
        $realValue = (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...
1173
1174
        // echo "\n*** BEFORE WRITE ***\n";
1175
        $model->MyIndexedVarchar = null;
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...
1176
        // $model->dbObject('MyIndexedVarchar')->setValue(null);
1177
        $model->write();
1178
        // echo "\n*** AFTER WRITE ***\n";
1179
1180
        $rec2 = DB::query('SELECT MyIndexedVarcharValue, MyIndexedVarcharBlindIndex FROM EncryptedModel WHERE ID = ' . $model->ID)->record(); // phpcs:ignore
1181
        $indexValue2 = $rec2['MyIndexedVarcharBlindIndex'];
1182
        $fieldValue2 = $rec2['MyIndexedVarcharValue'];
1183
        $this->assertEmpty($indexValue2, "It is $indexValue2 instead of null and value is $fieldValue2. It was $indexValue with value $fieldValue."); // phpcs:ignore
1184
1185
        $model->MyIndexedVarchar = 'some_new_value';
1186
        // $model->dbObject('MyIndexedVarchar')->setValue(null);
1187
        $model->write();
1188
1189
        $rec3 = DB::query('SELECT MyIndexedVarcharValue, MyIndexedVarcharBlindIndex FROM EncryptedModel WHERE ID = ' . $model->ID)->record(); // phpcs:ignore
1190
        $indexValue3 = $rec3['MyIndexedVarcharBlindIndex'];
1191
        $fieldValue3 = $rec3['MyIndexedVarcharValue'];
1192
        $this->assertNotEmpty($indexValue3);
1193
1194
        // Make null through object
1195
        $model->dbObject('MyIndexedVarchar')->setValue(null);
1196
        $model->write();
1197
1198
        $rec4 = DB::query('SELECT MyIndexedVarcharValue, MyIndexedVarcharBlindIndex FROM EncryptedModel WHERE ID = ' . $model->ID)->record(); // phpcs:ignore
1199
        $indexValue4 = $rec4['MyIndexedVarcharBlindIndex'];
1200
        $fieldValue4 = $rec4['MyIndexedVarcharValue'];
1201
        $this->assertEmpty($indexValue4);
1202
    }
1203
}
1204