Skip to content

Commit 92de0c4

Browse files
authored
Merge pull request #181 from patchlevel/handle-types-in-personal-data-payload-cryptographer
handle null and other types in old cryptographer
2 parents 06a2250 + 4821515 commit 92de0c4

3 files changed

Lines changed: 104 additions & 7 deletions

File tree

phpstan-baseline.neon

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,6 @@ parameters:
1212
count: 1
1313
path: src/Cryptography/Cipher/OpensslCipherKeyFactory.php
1414

15-
-
16-
message: '#^Parameter \#2 \$data of method Patchlevel\\Hydrator\\Cryptography\\Cipher\\Cipher\:\:decrypt\(\) expects string, mixed given\.$#'
17-
identifier: argument.type
18-
count: 1
19-
path: src/Cryptography/PersonalDataPayloadCryptographer.php
20-
2115
-
2216
message: '#^Offset ''k'' on array\{v\: 1, a\: non\-empty\-string, k\: non\-empty\-string, n\?\: non\-empty\-string, d\: non\-empty\-string, t\?\: non\-empty\-string\} on left side of \?\? always exists and is not nullable\.$#'
2317
identifier: nullCoalesce.offset

src/Cryptography/PersonalDataPayloadCryptographer.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public function __construct(
2727
private readonly Cipher $cipher,
2828
private readonly bool $useEncryptedFieldName = false,
2929
private readonly bool $fallbackToFieldName = false,
30+
private readonly bool $encryptNull = true,
3031
) {
3132
}
3233

@@ -55,13 +56,19 @@ public function encrypt(ClassMetadata $metadata, array $data): array
5556
continue;
5657
}
5758

59+
$value = $data[$propertyMetadata->fieldName()] ?? null;
60+
61+
if (!$this->encryptNull && $value === null) {
62+
continue;
63+
}
64+
5865
$targetFieldName = $this->useEncryptedFieldName
5966
? $propertyMetadata->encryptedFieldName()
6067
: $propertyMetadata->fieldName();
6168

6269
$data[$targetFieldName] = $this->cipher->encrypt(
6370
$cipherKey,
64-
$data[$propertyMetadata->fieldName()],
71+
$value,
6572
);
6673

6774
if (!$this->useEncryptedFieldName) {
@@ -107,6 +114,10 @@ public function decrypt(ClassMetadata $metadata, array $data): array
107114
continue;
108115
}
109116

117+
if (!is_string($rawData)) {
118+
continue;
119+
}
120+
110121
if (!$cipherKey) {
111122
$data[$propertyMetadata->fieldName()] = $this->fallback($propertyMetadata, $subjectId, $rawData);
112123
continue;

tests/Unit/Cryptography/PersonalDataPayloadCryptographerTest.php

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,74 @@ public function testEncryptWithExistingKeyEncryptedFieldName(): void
144144
self::assertEquals(['id' => 'foo', '!email' => 'encrypted'], $result);
145145
}
146146

147+
public function testEncryptSkipNullValueIfEncryptNullDisabled(): void
148+
{
149+
$cipherKey = new CipherKey(
150+
'foo',
151+
'bar',
152+
'baz',
153+
);
154+
155+
$cipherKeyStore = $this->createMock(CipherKeyStore::class);
156+
$cipherKeyStore->method('get')->with('foo')->willReturn($cipherKey);
157+
$cipherKeyStore->expects($this->never())->method('store');
158+
159+
$cipherKeyFactory = $this->createMock(CipherKeyFactory::class);
160+
$cipherKeyFactory->expects($this->never())->method('__invoke');
161+
162+
$cipher = $this->createMock(Cipher::class);
163+
$cipher->expects($this->never())->method('encrypt');
164+
165+
$cryptographer = new PersonalDataPayloadCryptographer(
166+
$cipherKeyStore,
167+
$cipherKeyFactory,
168+
$cipher,
169+
false,
170+
false,
171+
false,
172+
);
173+
174+
$result = $cryptographer->encrypt(
175+
$this->metadata(PersonalDataProfileCreated::class),
176+
['id' => 'foo', 'email' => null],
177+
);
178+
179+
self::assertSame(['id' => 'foo', 'email' => null], $result);
180+
}
181+
182+
public function testEncryptNullValueIfEncryptNullEnabled(): void
183+
{
184+
$cipherKey = new CipherKey(
185+
'foo',
186+
'bar',
187+
'baz',
188+
);
189+
190+
$cipherKeyStore = $this->createMock(CipherKeyStore::class);
191+
$cipherKeyStore->method('get')->with('foo')->willReturn($cipherKey);
192+
$cipherKeyStore->expects($this->never())->method('store');
193+
194+
$cipherKeyFactory = $this->createMock(CipherKeyFactory::class);
195+
$cipherKeyFactory->expects($this->never())->method('__invoke');
196+
197+
$cipher = $this->createMock(Cipher::class);
198+
$cipher->expects($this->once())->method('encrypt')->with($cipherKey, null)
199+
->willReturn('encrypted-null');
200+
201+
$cryptographer = new PersonalDataPayloadCryptographer(
202+
$cipherKeyStore,
203+
$cipherKeyFactory,
204+
$cipher,
205+
);
206+
207+
$result = $cryptographer->encrypt(
208+
$this->metadata(PersonalDataProfileCreated::class),
209+
['id' => 'foo', 'email' => null],
210+
);
211+
212+
self::assertSame(['id' => 'foo', 'email' => 'encrypted-null'], $result);
213+
}
214+
147215
public function testSkipDecrypt(): void
148216
{
149217
$cipherKeyStore = $this->createMock(CipherKeyStore::class);
@@ -373,6 +441,30 @@ public function testDecryptWithValidKeyAndEncryptedFieldNameAndFallbackFieldName
373441
self::assertEquals(['id' => 'foo', 'email' => 'info@patchlevel.de'], $result);
374442
}
375443

444+
public function testDecryptSkipNonStringValue(): void
445+
{
446+
$cipherKeyStore = $this->createMock(CipherKeyStore::class);
447+
$cipherKeyStore->method('get')->with('foo')->willThrowException(new CipherKeyNotExists('foo'));
448+
449+
$cipherKeyFactory = $this->createMock(CipherKeyFactory::class);
450+
$cipher = $this->createMock(Cipher::class);
451+
$cipher->expects($this->never())->method('decrypt');
452+
453+
$cryptographer = new PersonalDataPayloadCryptographer(
454+
$cipherKeyStore,
455+
$cipherKeyFactory,
456+
$cipher,
457+
);
458+
459+
$email = new Email('info@patchlevel.de');
460+
$result = $cryptographer->decrypt(
461+
$this->metadata(PersonalDataProfileCreated::class),
462+
['id' => 'foo', 'email' => $email],
463+
);
464+
465+
self::assertSame(['id' => 'foo', 'email' => $email], $result);
466+
}
467+
376468
public function testUnsupportedSubjectId(): void
377469
{
378470
$this->expectException(UnsupportedSubjectId::class);

0 commit comments

Comments
 (0)