Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Extension/Cryptography/BaseCryptographer.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public function encrypt(string $subjectId, mixed $value): array
$cipherKey = $this->cipherKeyStore->currentKeyFor($subjectId);
} catch (CipherKeyNotExists) {
$cipherKey = ($this->cipherKeyFactory)($subjectId);
$this->cipherKeyStore->store($cipherKey->id, $cipherKey);
$this->cipherKeyStore->store($cipherKey);
}

$parameter = $this->cipher->encrypt($cipherKey, $value);
Expand Down
4 changes: 2 additions & 2 deletions src/Extension/Cryptography/Store/CipherKeyStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ interface CipherKeyStore
public function currentKeyFor(string $subjectId): CipherKey;

/** @throws CipherKeyNotExists */
public function get(string $keyId): CipherKey;
public function get(string $id): CipherKey;

public function store(string $id, CipherKey $key): void;
public function store(CipherKey $key): void;

public function remove(string $id): void;

Expand Down
42 changes: 15 additions & 27 deletions src/Extension/Cryptography/Store/InMemoryCipherKeyStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
/** @var array<string, CipherKey> */
private array $indexById = [];

/** @var array<string, list<CipherKey>> */
/** @var array<string, array<string, CipherKey>> */
private array $indexBySubjectId = [];

public function currentKeyFor(string $subjectId): CipherKey
Expand All @@ -32,43 +32,31 @@
return $this->indexBySubjectId[$subjectId][$lastKey];
}

public function get(string $keyId): CipherKey
public function get(string $id): CipherKey
{
return $this->indexById[$keyId] ?? throw CipherKeyNotExists::forKeyId($keyId);
return $this->indexById[$id] ?? throw CipherKeyNotExists::forKeyId($id);
}

public function store(string $id, CipherKey $key): void
public function store(CipherKey $key): void
{
$this->indexById[$id] = $key;
$this->remove($key->id);

Check warning on line 42 in src/Extension/Cryptography/Store/InMemoryCipherKeyStore.php

View workflow job for this annotation

GitHub Actions / Mutation tests on diff (locked, 8.5, ubuntu-latest)

Escaped Mutant for Mutator "MethodCallRemoval": @@ @@ public function store(CipherKey $key): void { - $this->remove($key->id); + $this->indexById[$key->id] = $key; $this->indexBySubjectId[$key->subjectId][$key->id] = $key;

if (!isset($this->indexBySubjectId[$key->subjectId])) {
$this->indexBySubjectId[$key->subjectId] = [];
}

$this->indexBySubjectId[$key->subjectId][] = $key;
$this->indexById[$key->id] = $key;
$this->indexBySubjectId[$key->subjectId][$key->id] = $key;
}

public function remove(string $id): void
{
unset($this->indexById[$id]);

foreach ($this->indexBySubjectId as $subjectId => $keys) {
$filtered = [];

foreach ($keys as $key) {
if ($key->id === $id) {
continue;
}
$key = $this->indexById[$id] ?? null;

$filtered[] = $key;
}

if ($filtered === []) {
unset($this->indexBySubjectId[$subjectId]);
} else {
$this->indexBySubjectId[$subjectId] = $filtered;
}
if (!$key) {
return;
}

unset(
$this->indexBySubjectId[$key->subjectId][$id],
$this->indexById[$id],
);
}

public function clear(): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public function testEncryptWithoutKey(): void
$cipherKeyStore
->expects($this->once())
->method('store')
->with('key-456', $cipherKey);
->with($cipherKey);

$cipher = $this->createMock(Cipher::class);
$cipher->expects($this->once())->method('encrypt')->with($cipherKey, 'info@patchlevel.de')
Expand Down
203 changes: 184 additions & 19 deletions tests/Unit/Extension/Cryptography/Store/InMemoryCipherKeyStoreTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,68 +17,233 @@ final class InMemoryCipherKeyStoreTest extends TestCase
public function testStoreAndLoad(): void
{
$key = new CipherKey(
'foo',
'bar',
'baz',
'key-1',
'subject-1',
'secret',
'aes-256-gcm',
new DateTimeImmutable(),
);

$store = new InMemoryCipherKeyStore();
$store->store('foo', $key);
$store->store($key);

self::assertSame($key, $store->get('foo'));
self::assertSame($key, $store->get('key-1'));
}

public function testLoadFailed(): void
{
$this->expectException(CipherKeyNotExists::class);

$store = new InMemoryCipherKeyStore();
$store->get('foo');
$store->get('non-existent');
}

public function testRemove(): void
{
$key = new CipherKey(
'foo',
'bar',
'baz',
'key-1',
'subject-1',
'secret',
'aes-256-gcm',
new DateTimeImmutable(),
);

$store = new InMemoryCipherKeyStore();
$store->store('foo', $key);
$store->store($key);

self::assertSame($key, $store->get('foo'));
self::assertSame($key, $store->get('key-1'));

$store->remove('foo');
$store->remove('key-1');

$this->expectException(CipherKeyNotExists::class);

$store->get('foo');
$store->get('key-1');
}

public function testClear(): void
{
$key = new CipherKey(
'foo',
'bar',
'baz',
'key-1',
'subject-1',
'secret',
'aes-256-gcm',
new DateTimeImmutable(),
);

$store = new InMemoryCipherKeyStore();
$store->store('foo', $key);
$store->store($key);

self::assertSame($key, $store->get('foo'));
self::assertSame($key, $store->get('key-1'));

$store->clear();

$this->expectException(CipherKeyNotExists::class);

$store->get('foo');
$store->get('key-1');
}

public function testCurrentKeyFor(): void
{
$key1 = new CipherKey(
'key-1',
'subject-1',
'secret-1',
'aes-256-gcm',
new DateTimeImmutable(),
);

$key2 = new CipherKey(
'key-2',
'subject-1',
'secret-2',
'aes-256-gcm',
new DateTimeImmutable(),
);

$store = new InMemoryCipherKeyStore();
$store->store($key1);
$store->store($key2);

self::assertSame($key2, $store->currentKeyFor('subject-1'));
}

public function testCurrentKeyForNotExists(): void
{
$this->expectException(CipherKeyNotExists::class);

$store = new InMemoryCipherKeyStore();
$store->currentKeyFor('non-existent');
}

public function testRemoveWithSubjectId(): void
{
$key1 = new CipherKey(
'key-1',
'subject-1',
'secret-1',
'aes-256-gcm',
new DateTimeImmutable(),
);

$key2 = new CipherKey(
'key-2',
'subject-1',
'secret-2',
'aes-256-gcm',
new DateTimeImmutable(),
);

$key3 = new CipherKey(
'key-3',
'subject-2',
'secret-3',
'aes-256-gcm',
new DateTimeImmutable(),
);

$store = new InMemoryCipherKeyStore();
$store->store($key1);
$store->store($key2);
$store->store($key3);

$store->removeWithSubjectId('subject-1');

$this->expectException(CipherKeyNotExists::class);
$store->get('key-1');
}

public function testRemoveWithSubjectIdDoesNotAffectOtherSubjects(): void
{
$key1 = new CipherKey(
'key-1',
'subject-1',
'secret-1',
'aes-256-gcm',
new DateTimeImmutable(),
);

$key2 = new CipherKey(
'key-2',
'subject-2',
'secret-2',
'aes-256-gcm',
new DateTimeImmutable(),
);

$store = new InMemoryCipherKeyStore();
$store->store($key1);
$store->store($key2);

$store->removeWithSubjectId('subject-1');

self::assertSame($key2, $store->get('key-2'));
}

public function testRemoveWithSubjectIdNonExistent(): void
{
$store = new InMemoryCipherKeyStore();
$store->removeWithSubjectId('non-existent');

$this->expectNotToPerformAssertions();
}

public function testRemoveNonExistent(): void
{
$store = new InMemoryCipherKeyStore();
$store->remove('non-existent');

$this->expectNotToPerformAssertions();
}

public function testStoreOverwritesExistingKey(): void
{
$key1 = new CipherKey(
'key-1',
'subject-1',
'secret-1',
'aes-256-gcm',
new DateTimeImmutable(),
);

$key2 = new CipherKey(
'key-1',
'subject-1',
'secret-2',
'aes-256-gcm',
new DateTimeImmutable(),
);

$store = new InMemoryCipherKeyStore();
$store->store($key1);
$store->store($key2);

self::assertSame($key2, $store->get('key-1'));
self::assertSame($key2, $store->currentKeyFor('subject-1'));
}

public function testMultipleSubjects(): void
{
$key1 = new CipherKey(
'key-1',
'subject-1',
'secret-1',
'aes-256-gcm',
new DateTimeImmutable(),
);

$key2 = new CipherKey(
'key-2',
'subject-2',
'secret-2',
'aes-256-gcm',
new DateTimeImmutable(),
);

$store = new InMemoryCipherKeyStore();
$store->store($key1);
$store->store($key2);

self::assertSame($key1, $store->currentKeyFor('subject-1'));
self::assertSame($key2, $store->currentKeyFor('subject-2'));
}
}
Loading