Passed
Push — master ( 45a3bb...2cf7c0 )
by Thomas
03:37
created

EncryptTest::testRecordIsEncrypted()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 58
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 21
c 0
b 0
f 0
dl 0
loc 58
rs 9.584
cc 2
nc 2
nop 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace LeKoala\Encrypt\Test;
4
5
use Exception;
6
use SilverStripe\Assets\File;
7
use SilverStripe\ORM\DataObject;
8
use SilverStripe\Security\Member;
9
use LeKoala\Encrypt\EncryptHelper;
10
use SilverStripe\Core\Environment;
11
use SilverStripe\Dev\SapphireTest;
12
use LeKoala\Encrypt\EncryptedDBField;
13
use LeKoala\Encrypt\HasEncryptedFields;
14
use SilverStripe\ORM\Queries\SQLSelect;
15
16
/**
17
 * Test for Encrypt
18
 *
19
 * Run with the following command : ./vendor/bin/phpunit ./encrypt/tests/EncryptTest.php
20
 *
21
 * You may need to run:
22
 * php ./framework/cli-script.php dev/build ?flush=all
23
 * before (remember manifest for cli is not the same...)
24
 *
25
 * @group Encrypt
26
 */
27
class EncryptTest extends SapphireTest
28
{
29
    /**
30
     * Defines the fixture file to use for this test class
31
     * @var string
32
     */
33
    protected static $fixture_file = 'EncryptTest.yml';
34
35
    protected static $extra_dataobjects = [
36
        Test_EncryptedModel::class,
37
    ];
38
39
    public function setUp()
40
    {
41
        Environment::setEnv('ENCRYPTION_KEY', '502370dfc69fd6179e1911707e8a5fb798c915900655dea16370d64404be04e5');
42
        parent::setUp();
43
44
        // test extension is available
45
        if (!extension_loaded('sodium')) {
46
            throw new Exception("You must load sodium extension for this");
47
        }
48
    }
49
50
    public function tearDown()
51
    {
52
        parent::tearDown();
53
    }
54
55
    /**
56
     * @return Test_EncryptedModel
57
     */
58
    public function getTestModel()
59
    {
60
        return $this->objFromFixture(Test_EncryptedModel::class, 'demo');
61
    }
62
63
    /**
64
     * @return Member
65
     */
66
    public function getAdminMember()
67
    {
68
        return $this->objFromFixture(Member::class, 'admin');
69
    }
70
71
    /**
72
     * @return File
73
     */
74
    public function getRegularFile()
75
    {
76
        return $this->objFromFixture(File::class, 'regular');
77
    }
78
79
    /**
80
     * @return File
81
     */
82
    public function getEncryptedFile()
83
    {
84
        return $this->objFromFixture(File::class, 'encrypted');
85
    }
86
87
    /**
88
     * @param string $class
89
     * @param int $id
90
     * @return array
91
     */
92
    protected function fetchRawData($class, $id)
93
    {
94
        $tableName = DataObject::getSchema()->tableName($class);
95
        $columnIdentifier = DataObject::getSchema()->sqlColumnForField($class, 'ID');
96
        $sql = new SQLSelect('*', [$tableName], [$columnIdentifier => $id]);
97
        $dbRecord = $sql->firstRow()->execute()->first();
98
        return $dbRecord;
99
    }
100
101
    public function testEncryption()
102
    {
103
        $someText = 'some text';
104
        $encrypt = EncryptHelper::encrypt($someText);
105
        $decryptedValue = EncryptHelper::decrypt($encrypt);
106
107
        $this->assertEquals($someText, $decryptedValue);
108
    }
109
110
    public function testIndexes()
111
    {
112
        $indexes = DataObject::getSchema()->databaseIndexes(Test_EncryptedModel::class);
113
        $keys = array_keys($indexes);
114
        $this->assertContains('MyIndexedVarcharBlindIndex', $keys, "Index is not defined in : " . implode(", ", $keys));
115
        $this->assertContains('MyNumberLastFourBlindIndex', $keys, "Index is not defined in : " . implode(", ", $keys));
116
    }
117
118
    public function testSearch()
119
    {
120
        $singl = singleton(Test_EncryptedModel::class);
121
        $obj = $singl->dbObject('MyIndexedVarchar');
122
        $record = $obj->fetchRecord('some_searchable_value');
123
124
        $this->assertNotEmpty($record);
125
        $this->assertEquals(1, $record->ID);
126
        $this->assertNotEquals(2, $record->ID);
127
128
        $record = $obj->fetchRecord('some_unset_value');
129
        $this->assertEmpty($record);
130
131
        // Let's try our four digits index
132
        $obj = $singl->dbObject('MyNumber');
133
        $record = $obj->fetchRecord('6789', 'LastFourBlindIndex');
134
        $searchValue = $obj->getSearchValue('6789', 'LastFourBlindIndex');
135
        // $searchParams = $obj->getSearchParams('6789', 'LastFourBlindIndex');
136
        // print_r($searchParams);
137
        $this->assertNotEmpty($record, "Nothing found for $searchValue");
138
        $this->assertEquals(1, $record->ID);
139
    }
140
141
    public function testSearchFilter()
142
    {
143
        $record = Test_EncryptedModel::get()->filter('MyIndexedVarchar:Encrypted', 'some_searchable_value')->first();
144
        $this->assertNotEmpty($record);
145
        $this->assertEquals(1, $record->ID);
146
        $this->assertNotEquals(2, $record->ID);
147
148
        $record = Test_EncryptedModel::get()->filter('MyIndexedVarchar:Encrypted', 'some_unset_value')->first();
149
        $this->assertEmpty($record);
150
    }
151
152
    public function testFixture()
153
    {
154
        $model = $this->getTestModel();
155
156
        // Ensure we have our blind indexes
157
        $this->assertTrue($model->hasDatabaseField('MyIndexedVarcharValue'));
158
        $this->assertTrue($model->hasDatabaseField('MyIndexedVarcharBlindIndex'));
159
        $this->assertTrue($model->hasDatabaseField('MyNumberValue'));
160
        $this->assertTrue($model->hasDatabaseField('MyNumberBlindIndex'));
161
        $this->assertTrue($model->hasDatabaseField('MyNumberLastFourBlindIndex'));
162
163
        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

163
        if (class_uses($model, /** @scrutinizer ignore-type */ HasEncryptedFields::class)) {
Loading history...
164
            $this->assertTrue($model->hasEncryptedField('MyVarchar'));
165
            $this->assertTrue($model->hasEncryptedField('MyIndexedVarchar'));
166
        }
167
168
169
        // print_r($model);
170
        /*
171
         [record:protected] => Array
172
        (
173
            [ClassName] => LeKoala\Encrypt\Test\Test_EncryptedModel
174
            [LastEdited] => 2020-12-15 10:09:47
175
            [Created] => 2020-12-15 10:09:47
176
            [Name] => demo
177
            [MyText] => nacl:mQ1g5ugjYSWjFd-erM6-xlB_EbWp1bOAUPbL4fa3Ce5SX6LP7sFCczkFx_lRABvZioWJXx-L
178
            [MyHTMLText] => nacl:836In6YCaEf3_mRJR7NOC_s0P8gIFESgmPnHCefTe6ycY_6CLKVmT0_9KWHgnin-WGXMJawkS1hS87xwQw==
179
            [MyVarchar] => nacl:ZeOw8-dcBdFemtGm-MRJ5pCSipOtAO5-zBRms8F5Elex08GuoL_JKbdN-CiOP-u009MJfvGZUkx9Ru5Zn0_y
180
            [RegularFileID] => 2
181
            [EncryptedFileID] => 3
182
            [MyIndexedVarcharBlindIndex] => 04bb6edd
183
            [ID] => 1
184
            [RecordClassName] => LeKoala\Encrypt\Test\Test_EncryptedModel
185
        )
186
        */
187
188
        $varcharValue = 'encrypted varchar value';
189
        $varcharWithIndexValue = 'some_searchable_value';
190
        // regular fields are not affected
191
        $this->assertEquals('demo', $model->Name);
192
193
        // get value
194
        $this->assertEquals($varcharValue, $model->dbObject('MyVarchar')->getValue());
195
        // encrypted fields work transparently when using trait
196
        $this->assertEquals($varcharValue, $model->MyVarchar);
197
198
199
        $this->assertTrue($model->dbObject('MyIndexedVarchar') instanceof EncryptedDBField);
200
        $this->assertTrue($model->dbObject('MyIndexedVarchar')->hasField('Value'));
201
202
        $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...
203
        $model->write();
204
        $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...
205
206
        $dbRecord = $this->fetchRawData(get_class($model), $model->ID);
207
        // print_r($dbRecord);
208
        /*
209
        Array
210
(
211
    [ID] => 1
212
    [ClassName] => LeKoala\Encrypt\Test\Test_EncryptedModel
213
    [LastEdited] => 2020-12-15 10:10:27
214
    [Created] => 2020-12-15 10:10:27
215
    [Name] => demo
216
    [MyText] => nacl:aDplmA9hs7naqiPwWdNRMcYNUltf4mOs8KslRQZ4vCdnJylnbjAJYChtVH7wiiygsAHWqbM6
217
    [MyHTMLText] => nacl:dMvk5Miux0bsSP1SjaXQRlbGogNTu7UD3p6AlNHFMAEGXOQz03hkBx43C-WelCS0KUdAN9ewuwuXZqMmRA==
218
    [MyVarchar] => nacl:sZRenCG6En7Sg_HmsUHkNy_1MXOstly7eHm0i2iq83kTFH40UsQj-HTqxxYfx0ghuWSKbcqHQ7_OAEy4pcPm
219
    [RegularFileID] => 2
220
    [EncryptedFileID] => 3
221
    [MyNumberValue] =>
222
    [MyNumberBlindIndex] =>
223
    [MyNumberLastFourBlindIndex] =>
224
    [MyIndexedVarcharValue] =>
225
    [MyIndexedVarcharBlindIndex] => 04bb6edd
226
)
227
*/
228
        $this->assertNotEquals($varcharValue, $dbRecord['MyVarchar']);
229
        $this->assertNotEmpty($dbRecord['MyVarchar']);
230
        $this->assertTrue(EncryptHelper::isEncrypted($dbRecord['MyVarchar']));
231
    }
232
233
    public function testRecordIsEncrypted()
234
    {
235
        $model = new Test_EncryptedModel();
236
237
        // Let's write some stuff
238
        $someText = 'some text';
239
        $model->MyText = $someText . ' text';
240
        $model->MyHTMLText = '<p>' . $someText . ' html</p>';
241
        $model->MyVarchar = 'encrypted varchar value';
242
        $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...
243
        $model->MyNumber = "0123456789";
244
        // echo '<pre>';
245
        // print_r(array_keys($model->getChangedFields()));
246
        // die();
247
        $id = $model->write();
248
249
        $this->assertNotEmpty($id);
250
251
        // For the model, its the same
252
        $this->assertEquals($someText . ' text', $model->MyText);
253
        $this->assertEquals('<p>' . $someText . ' html</p>', $model->MyHTMLText);
254
255
        // In the db, it's not the same
256
        $dbRecord = $this->fetchRawData(get_class($model), $model->ID);
257
        // print_r($dbRecord);
258
        /*
259
(
260
    [ID] => 2
261
    [ClassName] => LeKoala\Encrypt\Test\Test_EncryptedModel
262
    [LastEdited] => 2020-12-15 10:20:39
263
    [Created] => 2020-12-15 10:20:39
264
    [Name] =>
265
    [MyText] => nacl:yA3XhjUpxE6cS3VMOVI4eqpolP1vRZDYjFySULZiazi9V3HSugC3t8KgImnGV5jP1VzEytVX
266
    [MyHTMLText] => nacl:F3D33dZ2O7qtlmkX-fiaYwSjAo6RC03aiAWRTkfSJOZikcSfezjwmi9DPJ4EO0hYeVc9faRgA3RmTDajRA==
267
    [MyVarchar] => nacl:POmdt3mTUSgPJw3ttfi2G9HgHAE4FRX4FQ5CSBicj4JsEwyPwrP-JKYGcs5drFYLId3cMVf6m8daUY7Ao4Cz
268
    [RegularFileID] => 0
269
    [EncryptedFileID] => 0
270
    [MyNumberValue] => nacl:2wFOX_qahm-HmzQPXvcBFhWCG1TaGQgeM7vkebLxRXDfMpzAxhxkExVgBi8caPYrwvA=
271
    [MyNumberBlindIndex] => 5e0bd888
272
    [MyNumberLastFourBlindIndex] => 276b
273
    [MyIndexedVarcharValue] => nacl:BLi-zF02t0Zet-ADP3RT8v5RTsM11WKIyjlJ1EVHIai2HwjxCIq92gfsay5zqiLic14dXtwigb1kI179QQ==
274
    [MyIndexedVarcharBlindIndex] => 04bb6edd
275
)
276
        */
277
        $text = isset($dbRecord['MyText']) ? $dbRecord['MyText'] : null;
278
        $this->assertNotEmpty($text);
279
        $this->assertNotEquals($someText, $text, "Data is not encrypted in the database");
280
        // Composite fields should work as well
281
        $this->assertNotEmpty($dbRecord['MyIndexedVarcharValue']);
282
        $this->assertNotEmpty($dbRecord['MyIndexedVarcharBlindIndex']);
283
        $this->assertNotEquals("some_searchable_value", $dbRecord['MyIndexedVarcharValue'], "Data is not encrypted in the database");
284
285
        // if we load again ?
286
        // it should work thanks to our trait
287
        // by default, data will be loaded encrypted if we don't use the trait and call getField directly
288
        $model2 = $model::get()->byID($model->ID);
289
        $this->assertEquals($someText . ' text', $model2->MyText, "Data does not load properly");
290
        $this->assertEquals('<p>' . $someText . ' html</p>', $model2->MyHTMLText, "Data does not load properly");
291
    }
292
293
    public function testFileEncryption()
294
    {
295
        $regularFile = $this->getRegularFile();
296
        $encryptedFile = $this->getEncryptedFile();
297
298
        $this->assertEquals(0, $regularFile->Encrypted);
299
        $this->assertEquals(1, $encryptedFile->Encrypted);
300
301
        // test encryption
302
303
        $string = 'Some content';
304
305
        $stream = fopen('php://memory', 'r+');
306
        fwrite($stream, $string);
0 ignored issues
show
Bug introduced by
It seems like $stream can also be of type false; however, parameter $handle of fwrite() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

306
        fwrite(/** @scrutinizer ignore-type */ $stream, $string);
Loading history...
307
        rewind($stream);
0 ignored issues
show
Bug introduced by
It seems like $stream can also be of type false; however, parameter $handle of rewind() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

307
        rewind(/** @scrutinizer ignore-type */ $stream);
Loading history...
308
309
        $encryptedFile->setFromStream($stream, 'secret.doc');
310
        $encryptedFile->write();
311
312
        $this->assertFalse($encryptedFile->isEncrypted());
313
314
        $encryptedFile->encryptFileIfNeeded();
315
316
        $this->assertTrue($encryptedFile->isEncrypted());
317
    }
318
}
319