Skip to content

Commit 75eb24a

Browse files
jmikolaGromNaN
andauthored
PHPLIB-1702: Always consult server encryptedFieldsMap when dropping collections with autoEncryption enabled (#1745)
* PHPLIB-1702: Always consult server encryptedFieldsMap when dropping collections * Bump tests/drivers-evergreen-tools to a332144 Necessary to fix PyMongo compat with MongoDB 4.0 * Detect metadata collections only when auto encryption is enabled on the client * Bump tests/specifications to 0aee4aa Necessary to fix change stream tests for server 8.2+ --------- Co-authored-by: Jérôme Tamarelle <[email protected]>
1 parent ae3821a commit 75eb24a

File tree

7 files changed

+130
-14
lines changed

7 files changed

+130
-14
lines changed

src/Client.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ class Client
8383

8484
private WriteConcern $writeConcern;
8585

86+
private bool $autoEncryptionEnabled;
87+
8688
/**
8789
* Constructs a new Client instance.
8890
*
@@ -134,6 +136,7 @@ public function __construct(?string $uri = null, array $uriOptions = [], array $
134136
$this->uri = $uri ?? self::DEFAULT_URI;
135137
$this->builderEncoder = $driverOptions['builderEncoder'] ?? new BuilderEncoder();
136138
$this->typeMap = $driverOptions['typeMap'];
139+
$this->autoEncryptionEnabled = isset($driverOptions['autoEncryption']['keyVaultNamespace']);
137140

138141
$driverOptions = array_diff_key($driverOptions, ['builderEncoder' => 1, 'typeMap' => 1]);
139142

@@ -258,7 +261,7 @@ public function dropDatabase(string $databaseName, array $options = [])
258261
*/
259262
public function getCollection(string $databaseName, string $collectionName, array $options = []): Collection
260263
{
261-
$options += ['typeMap' => $this->typeMap, 'builderEncoder' => $this->builderEncoder];
264+
$options += ['typeMap' => $this->typeMap, 'builderEncoder' => $this->builderEncoder, 'autoEncryptionEnabled' => $this->autoEncryptionEnabled];
262265

263266
return new Collection($this->manager, $databaseName, $collectionName, $options);
264267
}
@@ -273,7 +276,7 @@ public function getCollection(string $databaseName, string $collectionName, arra
273276
*/
274277
public function getDatabase(string $databaseName, array $options = []): Database
275278
{
276-
$options += ['typeMap' => $this->typeMap, 'builderEncoder' => $this->builderEncoder];
279+
$options += ['typeMap' => $this->typeMap, 'builderEncoder' => $this->builderEncoder, 'autoEncryptionEnabled' => $this->autoEncryptionEnabled];
277280

278281
return new Database($this->manager, $databaseName, $options);
279282
}

src/Collection.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
use function array_key_exists;
7878
use function current;
7979
use function is_array;
80+
use function is_bool;
8081
use function sprintf;
8182
use function strlen;
8283
use function trigger_error;
@@ -106,6 +107,8 @@ class Collection
106107

107108
private WriteConcern $writeConcern;
108109

110+
private bool $autoEncryptionEnabled;
111+
109112
/**
110113
* Constructs new Collection instance.
111114
*
@@ -173,12 +176,17 @@ public function __construct(private Manager $manager, private string $databaseNa
173176
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
174177
}
175178

179+
if (isset($options['autoEncryptionEnabled']) && ! is_bool($options['autoEncryptionEnabled'])) {
180+
throw InvalidArgumentException::invalidType('"autoEncryptionEnabled" option', $options['autoEncryptionEnabled'], 'boolean');
181+
}
182+
176183
$this->builderEncoder = $options['builderEncoder'] ?? new BuilderEncoder();
177184
$this->codec = $options['codec'] ?? null;
178185
$this->readConcern = $options['readConcern'] ?? $this->manager->getReadConcern();
179186
$this->readPreference = $options['readPreference'] ?? $this->manager->getReadPreference();
180187
$this->typeMap = $options['typeMap'] ?? self::DEFAULT_TYPE_MAP;
181188
$this->writeConcern = $options['writeConcern'] ?? $this->manager->getWriteConcern();
189+
$this->autoEncryptionEnabled = $options['autoEncryptionEnabled'] ?? false;
182190
}
183191

184192
/**
@@ -528,9 +536,9 @@ public function drop(array $options = [])
528536

529537
$server = select_server_for_write($this->manager, $options);
530538

531-
if (! isset($options['encryptedFields'])) {
539+
if ($this->autoEncryptionEnabled && ! isset($options['encryptedFields'])) {
532540
$options['encryptedFields'] = get_encrypted_fields_from_driver($this->databaseName, $this->collectionName, $this->manager)
533-
?? get_encrypted_fields_from_server($this->databaseName, $this->collectionName, $this->manager, $server);
541+
?? get_encrypted_fields_from_server($this->databaseName, $this->collectionName, $server);
534542
}
535543

536544
$operation = isset($options['encryptedFields'])

src/Database.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
use Traversable;
5656

5757
use function is_array;
58+
use function is_bool;
5859
use function sprintf;
5960
use function strlen;
6061
use function trigger_error;
@@ -82,6 +83,8 @@ class Database
8283

8384
private WriteConcern $writeConcern;
8485

86+
private bool $autoEncryptionEnabled;
87+
8588
/**
8689
* Constructs new Database instance.
8790
*
@@ -138,11 +141,16 @@ public function __construct(private Manager $manager, private string $databaseNa
138141
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
139142
}
140143

144+
if (isset($options['autoEncryptionEnabled']) && ! is_bool($options['autoEncryptionEnabled'])) {
145+
throw InvalidArgumentException::invalidType('"autoEncryptionEnabled" option', $options['autoEncryptionEnabled'], 'boolean');
146+
}
147+
141148
$this->builderEncoder = $options['builderEncoder'] ?? new BuilderEncoder();
142149
$this->readConcern = $options['readConcern'] ?? $this->manager->getReadConcern();
143150
$this->readPreference = $options['readPreference'] ?? $this->manager->getReadPreference();
144151
$this->typeMap = $options['typeMap'] ?? self::DEFAULT_TYPE_MAP;
145152
$this->writeConcern = $options['writeConcern'] ?? $this->manager->getWriteConcern();
153+
$this->autoEncryptionEnabled = $options['autoEncryptionEnabled'] ?? false;
146154
}
147155

148156
/**
@@ -410,9 +418,9 @@ public function dropCollection(string $collectionName, array $options = [])
410418
$options['writeConcern'] = $this->writeConcern;
411419
}
412420

413-
if (! isset($options['encryptedFields'])) {
421+
if ($this->autoEncryptionEnabled && ! isset($options['encryptedFields'])) {
414422
$options['encryptedFields'] = get_encrypted_fields_from_driver($this->databaseName, $collectionName, $this->manager)
415-
?? get_encrypted_fields_from_server($this->databaseName, $collectionName, $this->manager, $server);
423+
?? get_encrypted_fields_from_server($this->databaseName, $collectionName, $server);
416424
}
417425

418426
$operation = isset($options['encryptedFields'])
@@ -439,6 +447,7 @@ public function getCollection(string $collectionName, array $options = []): Coll
439447
'readPreference' => $this->readPreference,
440448
'typeMap' => $this->typeMap,
441449
'writeConcern' => $this->writeConcern,
450+
'autoEncryptionEnabled' => $this->autoEncryptionEnabled,
442451
];
443452

444453
return new Collection($this->manager, $this->databaseName, $collectionName, $options);

src/functions.php

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -201,13 +201,8 @@ function get_encrypted_fields_from_driver(string $databaseName, string $collecti
201201
* @see Database::dropCollection()
202202
* @return array|object|null
203203
*/
204-
function get_encrypted_fields_from_server(string $databaseName, string $collectionName, Manager $manager, Server $server)
204+
function get_encrypted_fields_from_server(string $databaseName, string $collectionName, Server $server)
205205
{
206-
// No-op if the encryptedFieldsMap autoEncryption driver option was omitted
207-
if ($manager->getEncryptedFieldsMap() === null) {
208-
return null;
209-
}
210-
211206
$collectionInfoIterator = (new ListCollections($databaseName, ['filter' => ['name' => $collectionName]]))->execute($server);
212207

213208
foreach ($collectionInfoIterator as $collectionInfo) {
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
3+
namespace MongoDB\Tests\Functions;
4+
5+
use MongoDB\BSON\Binary;
6+
use MongoDB\BSON\Regex;
7+
use MongoDB\Collection;
8+
use MongoDB\Database;
9+
use MongoDB\Driver\ClientEncryption;
10+
use MongoDB\Driver\WriteConcern;
11+
use MongoDB\Tests\FunctionalTestCase;
12+
13+
use function preg_quote;
14+
use function str_repeat;
15+
16+
class GetEncryptedFieldsFromServerFunctionalTest extends FunctionalTestCase
17+
{
18+
private ClientEncryption $clientEncryption;
19+
private Collection $keyVaultCollection;
20+
private Database $database;
21+
22+
public function setUp(): void
23+
{
24+
parent::setUp();
25+
26+
$this->skipIfClientSideEncryptionIsNotSupported();
27+
28+
if ($this->isStandalone()) {
29+
$this->markTestSkipped('Queryable encryption requires replica sets');
30+
}
31+
32+
$this->skipIfServerVersion('<', '7.0.0', 'Queryable encryption requires MongoDB 7.0 or later');
33+
34+
$encryptionOptions = [
35+
'keyVaultNamespace' => 'keyvault.datakeys',
36+
'kmsProviders' => [
37+
'local' => [
38+
'key' => new Binary(str_repeat("\0", 96)), // 96-byte local master key
39+
],
40+
],
41+
];
42+
$client = static::createTestClient(driverOptions: ['autoEncryption' => $encryptionOptions]);
43+
44+
// Ensure the key vault collection is dropped before each test
45+
$this->keyVaultCollection = $client->getCollection('keyvault', 'datakeys', ['writeConcern' => new WriteConcern(WriteConcern::MAJORITY)]);
46+
$this->keyVaultCollection->drop();
47+
48+
$this->clientEncryption = $client->createClientEncryption($encryptionOptions);
49+
50+
$this->database = $client->getDatabase($this->getDatabaseName());
51+
}
52+
53+
public function tearDown(): void
54+
{
55+
$this->keyVaultCollection?->drop();
56+
}
57+
58+
/** @see https://jira.mongodb.org/browse/PHPLIB-1702 */
59+
public function testDatabaseDropCollectionConsultsEncryptedFieldsFromServer(): void
60+
{
61+
$this->database->createEncryptedCollection(
62+
$this->getCollectionName(),
63+
$this->clientEncryption,
64+
'local',
65+
null,
66+
['encryptedFields' => ['fields' => []]],
67+
);
68+
69+
$this->assertCountCollections(3, $this->getCollectionName(), 'createEncryptedCollection should create three collections');
70+
71+
$this->database->dropCollection($this->getCollectionName());
72+
73+
$this->assertCountCollections(0, $this->getCollectionName());
74+
}
75+
76+
/** @see https://jira.mongodb.org/browse/PHPLIB-1702 */
77+
public function testCollectionDropConsultsEncryptedFieldsFromServer(): void
78+
{
79+
$this->database->createEncryptedCollection(
80+
$this->getCollectionName(),
81+
$this->clientEncryption,
82+
'local',
83+
null,
84+
['encryptedFields' => ['fields' => []]],
85+
);
86+
87+
$this->assertCountCollections(3, $this->getCollectionName(), 'createEncryptedCollection should create three collections');
88+
89+
$this->database->getCollection($this->getCollectionName())->drop();
90+
91+
$this->assertCountCollections(0, $this->getCollectionName());
92+
}
93+
94+
private function assertCountCollections(int $expected, $collectionName, string $message = ''): void
95+
{
96+
$collectionNames = $this->database->listCollectionNames([
97+
'filter' => ['name' => new Regex(preg_quote($collectionName))],
98+
]);
99+
$this->assertCount($expected, $collectionNames, $message);
100+
}
101+
}

tests/drivers-evergreen-tools

tests/specifications

Submodule specifications updated 71 files

0 commit comments

Comments
 (0)