From 6906e31890afa41eb6d7e15b9485e8a67f6b77bc Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Fri, 21 Feb 2025 17:09:36 -0700 Subject: [PATCH 1/6] Support $lookup in CSFLE and QE --- .../lookup/key-doc.json | 30 ++ .../lookup/schema-csfle.json | 19 + .../lookup/schema-csfle2.json | 19 + .../lookup/schema-qe.json | 20 + .../lookup/schema-qe2.json | 20 + .../crypt/CollectionInfoRetriever.java | 6 +- .../client/internal/crypt/Crypt.java | 6 +- .../client/ClientSideEncryption25Lookup.java | 39 ++ .../internal/CollectionInfoRetriever.java | 8 +- .../com/mongodb/client/internal/Crypt.java | 9 +- .../client/ClientSideEncryption25Lookup.java | 433 ++++++++++++++++++ .../OidcAuthenticationProseTests.java | 2 +- mongodb-crypt/build.gradle.kts | 2 +- .../com/mongodb/internal/crypt/capi/CAPI.java | 8 + .../internal/crypt/capi/MongoCryptImpl.java | 3 + 15 files changed, 608 insertions(+), 16 deletions(-) create mode 100644 driver-core/src/test/resources/client-side-encryption-data/lookup/key-doc.json create mode 100644 driver-core/src/test/resources/client-side-encryption-data/lookup/schema-csfle.json create mode 100644 driver-core/src/test/resources/client-side-encryption-data/lookup/schema-csfle2.json create mode 100644 driver-core/src/test/resources/client-side-encryption-data/lookup/schema-qe.json create mode 100644 driver-core/src/test/resources/client-side-encryption-data/lookup/schema-qe2.json create mode 100644 driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideEncryption25Lookup.java create mode 100644 driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryption25Lookup.java diff --git a/driver-core/src/test/resources/client-side-encryption-data/lookup/key-doc.json b/driver-core/src/test/resources/client-side-encryption-data/lookup/key-doc.json new file mode 100644 index 00000000000..566b56c354f --- /dev/null +++ b/driver-core/src/test/resources/client-side-encryption-data/lookup/key-doc.json @@ -0,0 +1,30 @@ +{ + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } +} diff --git a/driver-core/src/test/resources/client-side-encryption-data/lookup/schema-csfle.json b/driver-core/src/test/resources/client-side-encryption-data/lookup/schema-csfle.json new file mode 100644 index 00000000000..29ac9ad5da4 --- /dev/null +++ b/driver-core/src/test/resources/client-side-encryption-data/lookup/schema-csfle.json @@ -0,0 +1,19 @@ +{ + "properties": { + "csfle": { + "encrypt": { + "keyId": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ], + "bsonType": "string", + "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" + } + } + }, + "bsonType": "object" +} diff --git a/driver-core/src/test/resources/client-side-encryption-data/lookup/schema-csfle2.json b/driver-core/src/test/resources/client-side-encryption-data/lookup/schema-csfle2.json new file mode 100644 index 00000000000..3f1c02781c5 --- /dev/null +++ b/driver-core/src/test/resources/client-side-encryption-data/lookup/schema-csfle2.json @@ -0,0 +1,19 @@ +{ + "properties": { + "csfle2": { + "encrypt": { + "keyId": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ], + "bsonType": "string", + "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" + } + } + }, + "bsonType": "object" +} diff --git a/driver-core/src/test/resources/client-side-encryption-data/lookup/schema-qe.json b/driver-core/src/test/resources/client-side-encryption-data/lookup/schema-qe.json new file mode 100644 index 00000000000..9428ea1b458 --- /dev/null +++ b/driver-core/src/test/resources/client-side-encryption-data/lookup/schema-qe.json @@ -0,0 +1,20 @@ +{ + "escCollection": "enxcol_.qe.esc", + "ecocCollection": "enxcol_.qe.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "qe", + "bsonType": "string", + "queries": { + "queryType": "equality", + "contention": 0 + } + } + ] +} diff --git a/driver-core/src/test/resources/client-side-encryption-data/lookup/schema-qe2.json b/driver-core/src/test/resources/client-side-encryption-data/lookup/schema-qe2.json new file mode 100644 index 00000000000..77d5bd37cbb --- /dev/null +++ b/driver-core/src/test/resources/client-side-encryption-data/lookup/schema-qe2.json @@ -0,0 +1,20 @@ +{ + "escCollection": "enxcol_.qe2.esc", + "ecocCollection": "enxcol_.qe2.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "qe2", + "bsonType": "string", + "queries": { + "queryType": "equality", + "contention": 0 + } + } + ] +} diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/crypt/CollectionInfoRetriever.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/crypt/CollectionInfoRetriever.java index 08df35c00f0..786055b1886 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/crypt/CollectionInfoRetriever.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/crypt/CollectionInfoRetriever.java @@ -20,7 +20,7 @@ import com.mongodb.lang.Nullable; import com.mongodb.reactivestreams.client.MongoClient; import org.bson.BsonDocument; -import reactor.core.publisher.Mono; +import reactor.core.publisher.Flux; import static com.mongodb.assertions.Assertions.notNull; import static com.mongodb.reactivestreams.client.internal.TimeoutHelper.databaseWithTimeoutDeferred; @@ -35,8 +35,8 @@ class CollectionInfoRetriever { this.client = notNull("client", client); } - public Mono filter(final String databaseName, final BsonDocument filter, @Nullable final Timeout operationTimeout) { + public Flux filter(final String databaseName, final BsonDocument filter, @Nullable final Timeout operationTimeout) { return databaseWithTimeoutDeferred(client.getDatabase(databaseName), TIMEOUT_ERROR_MESSAGE, operationTimeout) - .flatMap(database -> Mono.from(database.listCollections(BsonDocument.class).filter(filter).first())); + .flatMapMany(database -> Flux.from(database.listCollections(BsonDocument.class).filter(filter))); } } diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/crypt/Crypt.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/crypt/Crypt.java index 17d82e32c49..61ccaa320fe 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/crypt/Crypt.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/crypt/Crypt.java @@ -304,10 +304,8 @@ private void collInfo(final MongoCryptContext cryptContext, } else { collectionInfoRetriever.filter(databaseName, cryptContext.getMongoOperation(), operationTimeout) .contextWrite(sink.contextView()) - .doOnSuccess(result -> { - if (result != null) { - cryptContext.addMongoOperationResult(result); - } + .doOnNext(result -> cryptContext.addMongoOperationResult(result)) + .doOnComplete(() -> { cryptContext.completeMongoOperation(); executeStateMachineWithSink(cryptContext, databaseName, sink, operationTimeout); }) diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideEncryption25Lookup.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideEncryption25Lookup.java new file mode 100644 index 00000000000..cc3c28841de --- /dev/null +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideEncryption25Lookup.java @@ -0,0 +1,39 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.reactivestreams.client; + +import com.mongodb.ClientEncryptionSettings; +import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClient; +import com.mongodb.client.vault.ClientEncryption; +import com.mongodb.reactivestreams.client.syncadapter.SyncClientEncryption; +import com.mongodb.reactivestreams.client.syncadapter.SyncMongoClient; +import com.mongodb.reactivestreams.client.vault.ClientEncryptions; + +public class ClientSideEncryption25Lookup extends com.mongodb.client.ClientSideEncryption25Lookup { + + @Override + protected MongoClient createMongoClient(final MongoClientSettings settings) { + return new SyncMongoClient(MongoClients.create(settings)); + } + + @Override + protected ClientEncryption createClientEncryption(final ClientEncryptionSettings settings) { + return new SyncClientEncryption(ClientEncryptions.create(settings)); + } + +} diff --git a/driver-sync/src/main/com/mongodb/client/internal/CollectionInfoRetriever.java b/driver-sync/src/main/com/mongodb/client/internal/CollectionInfoRetriever.java index 934a3dce486..cb9ee9f7c0c 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/CollectionInfoRetriever.java +++ b/driver-sync/src/main/com/mongodb/client/internal/CollectionInfoRetriever.java @@ -16,6 +16,7 @@ package com.mongodb.client.internal; +import com.mongodb.client.ListCollectionsIterable; import com.mongodb.client.MongoClient; import com.mongodb.internal.time.Timeout; import com.mongodb.lang.Nullable; @@ -33,9 +34,8 @@ class CollectionInfoRetriever { this.client = notNull("client", client); } - @Nullable - public BsonDocument filter(final String databaseName, final BsonDocument filter, @Nullable final Timeout operationTimeout) { - return databaseWithTimeout(client.getDatabase(databaseName), TIMEOUT_ERROR_MESSAGE, - operationTimeout).listCollections(BsonDocument.class).filter(filter).first(); + public ListCollectionsIterable filter(final String databaseName, final BsonDocument filter, @Nullable final Timeout operationTimeout) { + return databaseWithTimeout(client.getDatabase(databaseName), TIMEOUT_ERROR_MESSAGE, operationTimeout) + .listCollections(BsonDocument.class).filter(filter); } } diff --git a/driver-sync/src/main/com/mongodb/client/internal/Crypt.java b/driver-sync/src/main/com/mongodb/client/internal/Crypt.java index b910f0ab01c..2bb965ff885 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/Crypt.java +++ b/driver-sync/src/main/com/mongodb/client/internal/Crypt.java @@ -19,6 +19,7 @@ import com.mongodb.MongoClientException; import com.mongodb.MongoException; import com.mongodb.MongoInternalException; +import com.mongodb.client.ListCollectionsIterable; import com.mongodb.client.MongoClient; import com.mongodb.client.model.vault.DataKeyOptions; import com.mongodb.client.model.vault.EncryptOptions; @@ -41,6 +42,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.Map; import java.util.function.Supplier; @@ -308,9 +310,10 @@ private void fetchCredentials(final MongoCryptContext cryptContext) { private void collInfo(final MongoCryptContext cryptContext, final String databaseName, @Nullable final Timeout operationTimeout) { try { - BsonDocument collectionInfo = assertNotNull(collectionInfoRetriever).filter(databaseName, cryptContext.getMongoOperation(), operationTimeout); - if (collectionInfo != null) { - cryptContext.addMongoOperationResult(collectionInfo); + ListCollectionsIterable results = assertNotNull(collectionInfoRetriever) + .filter(databaseName, cryptContext.getMongoOperation(), operationTimeout); + for (BsonDocument result : results.into(new ArrayList<>())) { + cryptContext.addMongoOperationResult(result); } cryptContext.completeMongoOperation(); } catch (Throwable t) { diff --git a/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryption25Lookup.java b/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryption25Lookup.java new file mode 100644 index 00000000000..9cb23dfc245 --- /dev/null +++ b/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryption25Lookup.java @@ -0,0 +1,433 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.client; + +import com.mongodb.AutoEncryptionSettings; +import com.mongodb.ClientEncryptionSettings; +import com.mongodb.MongoClientSettings; +import com.mongodb.MongoNamespace; +import com.mongodb.client.model.CreateCollectionOptions; +import com.mongodb.client.model.ValidationOptions; +import com.mongodb.client.vault.ClientEncryption; +import com.mongodb.client.vault.ClientEncryptions; +import com.mongodb.crypt.capi.MongoCryptException; +import com.mongodb.event.CommandEvent; +import com.mongodb.event.CommandStartedEvent; +import com.mongodb.event.CommandSucceededEvent; +import com.mongodb.fixture.EncryptionFixture; +import com.mongodb.internal.connection.TestCommandListener; +import com.mongodb.internal.crypt.capi.CAPI; +import org.bson.BsonArray; +import org.bson.BsonDocument; +import org.bson.Document; +import org.bson.types.Binary; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import java.io.File; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.mongodb.ClusterFixture.isStandalone; +import static com.mongodb.ClusterFixture.serverVersionAtLeast; +import static com.mongodb.client.Fixture.getMongoClientSettingsBuilder; +import static com.mongodb.fixture.EncryptionFixture.getKmsProviders; +import static com.mongodb.internal.connection.OidcAuthenticationProseTests.assertCause; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeFalse; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static util.JsonPoweredTestHelper.getTestDocument; + +public class ClientSideEncryption25Lookup { + private MongoClient client; + private TestCommandListener listener; + + protected MongoClient createMongoClient(final MongoClientSettings settings) { + return MongoClients.create(settings); + } + + protected ClientEncryption createClientEncryption(final ClientEncryptionSettings settings) { + return ClientEncryptions.create(settings); + } + + @BeforeEach + public void setUp() throws InterruptedException { + // TODO remove debug: + TestCommandListener commandListener = new TestCommandListener(); + TestCommandListener commandListener2 = new TestCommandListener(); + + + assumeTrue(serverVersionAtLeast(7, 0)); + assumeFalse(isStandalone()); + + // Create an unencrypted MongoClient. + MongoClient unencryptedClient = createMongoClient(getMongoClientSettingsBuilder() + .addCommandListener(commandListener) + .build()); + + // drop database db + MongoDatabase db = unencryptedClient.getDatabase("db"); + db.drop(); + + // Insert into db.keyvault. + MongoNamespace dataKeysNamespace = new MongoNamespace("db.keyvault"); + db.getCollection(dataKeysNamespace.getCollectionName(), BsonDocument.class) + .insertOne(bsonDocumentFromPath("key-doc.json")); + + // Create the following collections: + db.createCollection("csfle", new CreateCollectionOptions() + .validationOptions(new ValidationOptions() + .validator(new BsonDocument("$jsonSchema", bsonDocumentFromPath("schema-csfle.json"))))); + db.createCollection("csfle2", new CreateCollectionOptions() + .validationOptions(new ValidationOptions() + .validator(new BsonDocument("$jsonSchema", bsonDocumentFromPath("schema-csfle2.json"))))); + + db.createCollection("qe", + new CreateCollectionOptions().encryptedFields(bsonDocumentFromPath("schema-qe.json"))); + db.createCollection("qe2", + new CreateCollectionOptions().encryptedFields(bsonDocumentFromPath("schema-qe2.json"))); + + db.createCollection("no_schema"); + db.createCollection("no_schema2"); + + // Create an encrypted MongoClient configured with: + Map> kmsProviders = getKmsProviders(EncryptionFixture.KmsProviderType.LOCAL); + MongoClient encryptedClient = createMongoClient(getMongoClientSettingsBuilder() + .addCommandListener(commandListener2) + .autoEncryptionSettings( + AutoEncryptionSettings.builder() + .keyVaultNamespace(dataKeysNamespace.getFullName()) + .kmsProviders(kmsProviders) + .build()) + .build()); + + // Insert documents with the encrypted MongoClient: + Consumer insert = (name) -> { + encryptedClient.getDatabase("db").getCollection(name) + .insertOne(new Document(name, name)); + }; + insert.accept("csfle"); + insert.accept("csfle2"); + insert.accept("qe"); + insert.accept("qe2"); + insert.accept("no_schema"); + insert.accept("no_schema2"); + + Consumer assertDocument = (name) -> { + List pipeline = Arrays.asList( + BsonDocument.parse("{\"$project\" : {\"_id\" : 0, \"__safeContent__\" : 0}}") + ); + Document decryptedDoc = encryptedClient.getDatabase("db").getCollection(name) + .aggregate(pipeline).first(); + assertEquals(decryptedDoc, new Document(name, name)); + Document encryptedDoc = unencryptedClient.getDatabase("db").getCollection(name) + .aggregate(pipeline).first(); + assertNotNull(encryptedDoc); + assertEquals(Binary.class, encryptedDoc.get(name).getClass()); + + // TODO + System.out.println("ENC-DOC: " + encryptedDoc.toJson()); + System.out.println("DEC-DOC: " + decryptedDoc.toJson()); + System.out.println("VERSION: " + CAPI.mongocrypt_version(null).toString()); + }; + + assertDocument.accept("csfle"); + assertDocument.accept("csfle2"); + assertDocument.accept("qe"); + assertDocument.accept("qe2"); + + // TODO need registration mechanism for shutdown hook? + unencryptedClient.close(); + encryptedClient.close(); + + List events = commandListener2.getEvents(); + System.out.println(">> " + events); + + List events2 = commandListener2.getCommandStartedEvents(); + String collect = events2.stream() + .map(v -> v.getCommand().toJson()) + .collect(Collectors.joining("\n")); + System.out.println(">> " + collect); + + listener = new TestCommandListener(); + client = createMongoClient(getMongoClientSettingsBuilder() + .addCommandListener(listener) + .autoEncryptionSettings( + AutoEncryptionSettings.builder() + .keyVaultNamespace(dataKeysNamespace.getFullName()) + .kmsProviders(kmsProviders) + .build()) + .build()); + } + + @SafeVarargs + @SuppressWarnings("varargs") + private static Map merge( final Map.Entry... entries) { + HashMap result = new HashMap<>(); + result.putAll(Stream.of(entries).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); + return result; + } + + @AfterEach + @SuppressWarnings("try") + public void cleanUp() { + //noinspection EmptyTryBlock + try (MongoClient ignored = this.client) { + // just using try-with-resources to ensure they all get closed, even in the case of exceptions + } + } + + @ParameterizedTest + @CsvSource({ + "csfle, no_schema", + "qe, no_schema", + "no_schema, csfle", + "no_schema, qe", + "csfle, csfle2", + "qe, qe2", + "no_schema, no_schema2"}) + public void case0(final String from, final String to) { + + String mql = ("[\n" + + " {\"$match\" : {\"\" : \"\"}},\n" + + " {\n" + + " \"$lookup\" : {\n" + + " \"from\" : \"\",\n" + + " \"as\" : \"matched\",\n" + + " \"pipeline\" : [ {\"$match\" : {\"\" : \"\"}}, {\"$project\" : {\"_id\" : 0}} ]\n" + + " }\n" + + " },\n" + + " {\"$project\" : {\"_id\" : 0, \"__safeContent__\" : 0}}\n" + + "]").replace("", from).replace("", to); + + List pipeline = BsonArray.parse(mql).stream() + .map(stage -> stage.asDocument()) + .collect(Collectors.toList()); + assertEquals( + Document.parse("{\"\" : \"\", \"matched\" : [ {\"\" : \"\"} ]}" + .replace("", from).replace("", to)), + client.getDatabase("db").getCollection(from).aggregate(pipeline).first()); + } + + @Test + public void case1() { + List pipeline = BsonArray.parse("[\n" + + " {\"$match\" : {\"csfle\" : \"csfle\"}},\n" + + " {\n" + + " \"$lookup\" : {\n" + + " \"from\" : \"no_schema\",\n" + + " \"as\" : \"matched\",\n" + + " \"pipeline\" : [ {\"$match\" : {\"no_schema\" : \"no_schema\"}}, {\"$project\" : {\"_id\" : 0}} ]\n" + + " }\n" + + " },\n" + + " {\"$project\" : {\"_id\" : 0}}\n" + + "]").stream().map(stage -> stage.asDocument()).collect(Collectors.toList()); + assertEquals( + Document.parse("{\"csfle\" : \"csfle\", \"matched\" : [ {\"no_schema\" : \"no_schema\"} ]}"), + client.getDatabase("db").getCollection("csfle").aggregate(pipeline).first()); + } + + @Test + public void case2() { + List pipeline = BsonArray.parse("[\n" + + " {\"$match\" : {\"qe\" : \"qe\"}},\n" + + " {\n" + + " \"$lookup\" : {\n" + + " \"from\" : \"no_schema\",\n" + + " \"as\" : \"matched\",\n" + + " \"pipeline\" :\n" + + " [ {\"$match\" : {\"no_schema\" : \"no_schema\"}}, {\"$project\" : {\"_id\" : 0, \"__safeContent__\" : 0}} ]\n" + + " }\n" + + " },\n" + + " {\"$project\" : {\"_id\" : 0, \"__safeContent__\" : 0}}\n" + + "]").stream().map(stage -> stage.asDocument()).collect(Collectors.toList()); + assertEquals( + Document.parse("{\"qe\" : \"qe\", \"matched\" : [ {\"no_schema\" : \"no_schema\"} ]}"), + client.getDatabase("db").getCollection("qe").aggregate(pipeline).first()); + } + + @Test + public void case3() { + List pipeline = BsonArray.parse("[\n" + + " {\"$match\" : {\"no_schema\" : \"no_schema\"}},\n" + + " {\n" + + " \"$lookup\" : {\n" + + " \"from\" : \"csfle\",\n" + + " \"as\" : \"matched\",\n" + + " \"pipeline\" : [ {\"$match\" : {\"csfle\" : \"csfle\"}}, {\"$project\" : {\"_id\" : 0}} ]\n" + + " }\n" + + " },\n" + + " {\"$project\" : {\"_id\" : 0}}\n" + + "]").stream().map(stage -> stage.asDocument()).collect(Collectors.toList()); + assertEquals( + Document.parse("{\"no_schema\" : \"no_schema\", \"matched\" : [ {\"csfle\" : \"csfle\"} ]}"), + client.getDatabase("db").getCollection("no_schema").aggregate(pipeline).first()); + } + + @Test + public void case4() { + List pipeline = BsonArray.parse("[\n" + + " {\"$match\" : {\"no_schema\" : \"no_schema\"}},\n" + + " {\n" + + " \"$lookup\" : {\n" + + " \"from\" : \"qe\",\n" + + " \"as\" : \"matched\",\n" + + " \"pipeline\" : [ {\"$match\" : {\"qe\" : \"qe\"}}, {\"$project\" : {\"_id\" : 0, \"__safeContent__\" : 0}} ]\n" + + " }\n" + + " },\n" + + " {\"$project\" : {\"_id\" : 0}}\n" + + "]").stream().map(stage -> stage.asDocument()).collect(Collectors.toList()); + + Document first = client.getDatabase("db").getCollection("no_schema").aggregate(pipeline).first(); + + List events2 = listener.getEvents(); + String collect = events2.stream() + .map(v -> { + if (v instanceof CommandStartedEvent) { + return "STARTED: " + ((CommandStartedEvent) v).getCommand().toJson(); + } else if (v instanceof CommandSucceededEvent) { + return "SUCCEEDED: " + ((CommandSucceededEvent) v).getResponse().toJson(); + } else { + return ""; + } + }) + .collect(Collectors.joining("\n")); + System.out.println("RESULTS: " + collect); + // TODO: remove debugging + + assertEquals( + Document.parse("{\"no_schema\" : \"no_schema\", \"matched\" : [ {\"qe\" : \"qe\"} ]}"), + first); + } + + @Test + public void case5() { + List pipeline = BsonArray.parse("[\n" + + " {\"$match\" : {\"csfle\" : \"csfle\"}},\n" + + " {\n" + + " \"$lookup\" : {\n" + + " \"from\" : \"csfle2\",\n" + + " \"as\" : \"matched\",\n" + + " \"pipeline\" : [ {\"$match\" : {\"csfle2\" : \"csfle2\"}}, {\"$project\" : {\"_id\" : 0}} ]\n" + + " }\n" + + " },\n" + + " {\"$project\" : {\"_id\" : 0}}\n" + + "]").stream().map(stage -> stage.asDocument()).collect(Collectors.toList()); + + assertEquals( + Document.parse("{\"csfle\" : \"csfle\", \"matched\" : [ {\"csfle2\" : \"csfle2\"} ]}"), + client.getDatabase("db").getCollection("csfle").aggregate(pipeline).first()); + } + + @Test + public void case6() { + List pipeline = BsonArray.parse("[\n" + + " {\"$match\" : {\"qe\" : \"qe\"}},\n" + + " {\n" + + " \"$lookup\" : {\n" + + " \"from\" : \"qe2\",\n" + + " \"as\" : \"matched\",\n" + + " \"pipeline\" : [ {\"$match\" : {\"qe2\" : \"qe2\"}}, {\"$project\" : {\"_id\" : 0, \"__safeContent__\" : 0}} ]\n" + + " }\n" + + " },\n" + + " {\"$project\" : {\"_id\" : 0, \"__safeContent__\" : 0}}\n" + + "]").stream().map(stage -> stage.asDocument()).collect(Collectors.toList()); + + assertEquals( + Document.parse("{\"qe\" : \"qe\", \"matched\" : [ {\"qe2\" : \"qe2\"} ]}"), + client.getDatabase("db").getCollection("qe").aggregate(pipeline).first()); + } + + @Test + public void case7() { + List pipeline = BsonArray.parse("[\n" + + " {\"$match\" : {\"no_schema\" : \"no_schema\"}},\n" + + " {\n" + + " \"$lookup\" : {\n" + + " \"from\" : \"no_schema2\",\n" + + " \"as\" : \"matched\",\n" + + " \"pipeline\" : [ {\"$match\" : {\"no_schema2\" : \"no_schema2\"}}, {\"$project\" : {\"_id\" : 0}} ]\n" + + " }\n" + + " },\n" + + " {\"$project\" : {\"_id\" : 0}}\n" + + "]").stream().map(stage -> stage.asDocument()).collect(Collectors.toList()); + + assertEquals( + Document.parse("{\"no_schema\" : \"no_schema\", \"matched\" : [ {\"no_schema2\" : \"no_schema2\"} ]}"), + client.getDatabase("db").getCollection("no_schema").aggregate(pipeline).first()); + } + + @Test + public void case8() { + List pipeline = BsonArray.parse("[\n" + + " {\"$match\" : {\"csfle\" : \"qe\"}},\n" + + " {\n" + + " \"$lookup\" : {\n" + + " \"from\" : \"qe\",\n" + + " \"as\" : \"matched\",\n" + + " \"pipeline\" : [ {\"$match\" : {\"qe\" : \"qe\"}}, {\"$project\" : {\"_id\" : 0}} ]\n" + + " }\n" + + " },\n" + + " {\"$project\" : {\"_id\" : 0}}\n" + + "]").stream().map(stage -> stage.asDocument()).collect(Collectors.toList()); + + assertCause( + MongoCryptException.class, + "not supported", + () -> client.getDatabase("db").getCollection("csfle").aggregate(pipeline).first()); + } + + @Test + public void case9() { + List pipeline = BsonArray.parse("[\n" + + " {\"$match\" : {\"csfle\" : \"csfle\"}},\n" + + " {\n" + + " \"$lookup\" : {\n" + + " \"from\" : \"no_schema\",\n" + + " \"as\" : \"matched\",\n" + + " \"pipeline\" : [ {\"$match\" : {\"no_schema\" : \"no_schema\"}}, {\"$project\" : {\"_id\" : 0}} ]\n" + + " }\n" + + " },\n" + + " {\"$project\" : {\"_id\" : 0}}\n" + + "]").stream().map(stage -> stage.asDocument()).collect(Collectors.toList()); + assertCause( + RuntimeException.class, + "Upgrade", + () -> client.getDatabase("db").getCollection("csfle").aggregate(pipeline).first()); + // TODO + } + + public static BsonDocument bsonDocumentFromPath(final String path) { + try { + return getTestDocument(new File(ClientSideEncryption25Lookup.class + .getResource("/client-side-encryption-data/lookup/" + path).toURI())); + } catch (Exception e) { + fail("Unable to load resource", e); + return null; + } + } +} diff --git a/driver-sync/src/test/functional/com/mongodb/internal/connection/OidcAuthenticationProseTests.java b/driver-sync/src/test/functional/com/mongodb/internal/connection/OidcAuthenticationProseTests.java index 2d82ecf3d92..3ed50446a8d 100644 --- a/driver-sync/src/test/functional/com/mongodb/internal/connection/OidcAuthenticationProseTests.java +++ b/driver-sync/src/test/functional/com/mongodb/internal/connection/OidcAuthenticationProseTests.java @@ -922,7 +922,7 @@ private void performFind(final MongoClient mongoClient) { .first(); } - private static void assertCause( + public static void assertCause( final Class expectedCause, final String expectedMessageFragment, final Executable e) { Throwable cause = assertThrows(Throwable.class, e); while (cause.getCause() != null) { diff --git a/mongodb-crypt/build.gradle.kts b/mongodb-crypt/build.gradle.kts index 6c07a315185..64a0981134b 100644 --- a/mongodb-crypt/build.gradle.kts +++ b/mongodb-crypt/build.gradle.kts @@ -60,7 +60,7 @@ val jnaLibsPath: String = System.getProperty("jnaLibsPath", "${jnaResourcesDir}$ val jnaResources: String = System.getProperty("jna.library.path", jnaLibsPath) // Download jnaLibs that match the git tag or revision to jnaResourcesBuildDir -val downloadRevision = "9a88ac5698e8e3ffcd6580b98c247f0126f26c40" // r1.11.0 +val downloadRevision = "67f10bfb8de69549987cc62a1d4548d7b511a7ef" // TODO val binariesArchiveName = "libmongocrypt-java.tar.gz" /** diff --git a/mongodb-crypt/src/main/com/mongodb/internal/crypt/capi/CAPI.java b/mongodb-crypt/src/main/com/mongodb/internal/crypt/capi/CAPI.java index b8e2cacc677..075bbe15c9c 100644 --- a/mongodb-crypt/src/main/com/mongodb/internal/crypt/capi/CAPI.java +++ b/mongodb-crypt/src/main/com/mongodb/internal/crypt/capi/CAPI.java @@ -486,6 +486,14 @@ public interface mongocrypt_random_fn extends Callback { public static native void mongocrypt_setopt_bypass_query_analysis (mongocrypt_t crypt); + /** + * Opt-into enabling sending multiple collection info documents. + * + * @param crypt The @ref mongocrypt_t object to update + */ + public static native void + mongocrypt_setopt_enable_multiple_collinfo (mongocrypt_t crypt); + /** * Set the contention factor used for explicit encryption. * The contention factor is only used for indexed Queryable Encryption. diff --git a/mongodb-crypt/src/main/com/mongodb/internal/crypt/capi/MongoCryptImpl.java b/mongodb-crypt/src/main/com/mongodb/internal/crypt/capi/MongoCryptImpl.java index 37f2263da69..d365f7f7671 100644 --- a/mongodb-crypt/src/main/com/mongodb/internal/crypt/capi/MongoCryptImpl.java +++ b/mongodb-crypt/src/main/com/mongodb/internal/crypt/capi/MongoCryptImpl.java @@ -65,6 +65,7 @@ import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_setopt_bypass_query_analysis; import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_setopt_crypto_hook_sign_rsaes_pkcs1_v1_5; import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_setopt_crypto_hooks; +import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_setopt_enable_multiple_collinfo; import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_setopt_encrypted_field_config_map; import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_setopt_kms_provider_aws; import static com.mongodb.internal.crypt.capi.CAPI.mongocrypt_setopt_kms_provider_local; @@ -120,6 +121,8 @@ class MongoCryptImpl implements MongoCrypt { logCallback = new LogCallback(); + mongocrypt_setopt_enable_multiple_collinfo(wrapped); + configure(() -> mongocrypt_setopt_log_handler(wrapped, logCallback, null)); if (mongocrypt_is_crypto_available()) { From 1a4d7bd243f4e08d059cf6cb028ac87fd3508aa8 Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Tue, 25 Feb 2025 07:25:15 -0700 Subject: [PATCH 2/6] Update driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryption25Lookup.java Co-authored-by: Viacheslav Babanin --- .../com/mongodb/client/ClientSideEncryption25Lookup.java | 1 + 1 file changed, 1 insertion(+) diff --git a/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryption25Lookup.java b/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryption25Lookup.java index 9cb23dfc245..9cd7988ee53 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryption25Lookup.java +++ b/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryption25Lookup.java @@ -403,6 +403,7 @@ public void case8() { @Test public void case9() { + assumeTrue(serverVersionLessThan(8, 1)); List pipeline = BsonArray.parse("[\n" + " {\"$match\" : {\"csfle\" : \"csfle\"}},\n" + " {\n" From 533bcdfacee83c25f984f01e7af1f3fc4b37d852 Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Tue, 25 Feb 2025 07:43:06 -0700 Subject: [PATCH 3/6] PR fixes --- .../bulk/BaseClientDeleteOptionsTest.java | 2 +- .../bulk/BaseClientUpdateOptionsTest.java | 2 +- ...ClientUpsertableWriteModelOptionsTest.java | 2 +- .../bulk/BaseClientWriteModelOptionsTest.java | 2 +- .../com/mongodb/testing/MongoAssertions.java | 43 ++++ .../MongoBaseInterfaceAssertions.java | 2 +- ...ientSideEncryption25LookupProseTests.java} | 2 +- ...ientSideEncryption25LookupProseTests.java} | 210 +----------------- .../OidcAuthenticationProseTests.java | 17 +- 9 files changed, 60 insertions(+), 222 deletions(-) create mode 100644 driver-core/src/test/unit/com/mongodb/testing/MongoAssertions.java rename driver-core/src/test/unit/com/mongodb/{ => testing}/MongoBaseInterfaceAssertions.java (98%) rename driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/{ClientSideEncryption25Lookup.java => ClientSideEncryption25LookupProseTests.java} (92%) rename driver-sync/src/test/functional/com/mongodb/client/{ClientSideEncryption25Lookup.java => ClientSideEncryption25LookupProseTests.java} (51%) diff --git a/driver-core/src/test/unit/com/mongodb/client/model/bulk/BaseClientDeleteOptionsTest.java b/driver-core/src/test/unit/com/mongodb/client/model/bulk/BaseClientDeleteOptionsTest.java index e9832c24b21..fdcba01c2d3 100644 --- a/driver-core/src/test/unit/com/mongodb/client/model/bulk/BaseClientDeleteOptionsTest.java +++ b/driver-core/src/test/unit/com/mongodb/client/model/bulk/BaseClientDeleteOptionsTest.java @@ -16,7 +16,7 @@ package com.mongodb.client.model.bulk; -import com.mongodb.MongoBaseInterfaceAssertions; +import com.mongodb.testing.MongoBaseInterfaceAssertions; import org.junit.jupiter.api.Test; class BaseClientDeleteOptionsTest { diff --git a/driver-core/src/test/unit/com/mongodb/client/model/bulk/BaseClientUpdateOptionsTest.java b/driver-core/src/test/unit/com/mongodb/client/model/bulk/BaseClientUpdateOptionsTest.java index 43ba8e0967e..c9131452063 100644 --- a/driver-core/src/test/unit/com/mongodb/client/model/bulk/BaseClientUpdateOptionsTest.java +++ b/driver-core/src/test/unit/com/mongodb/client/model/bulk/BaseClientUpdateOptionsTest.java @@ -16,7 +16,7 @@ package com.mongodb.client.model.bulk; -import com.mongodb.MongoBaseInterfaceAssertions; +import com.mongodb.testing.MongoBaseInterfaceAssertions; import org.junit.jupiter.api.Test; class BaseClientUpdateOptionsTest { diff --git a/driver-core/src/test/unit/com/mongodb/client/model/bulk/BaseClientUpsertableWriteModelOptionsTest.java b/driver-core/src/test/unit/com/mongodb/client/model/bulk/BaseClientUpsertableWriteModelOptionsTest.java index 5992a508574..8fecf8d14fd 100644 --- a/driver-core/src/test/unit/com/mongodb/client/model/bulk/BaseClientUpsertableWriteModelOptionsTest.java +++ b/driver-core/src/test/unit/com/mongodb/client/model/bulk/BaseClientUpsertableWriteModelOptionsTest.java @@ -16,7 +16,7 @@ package com.mongodb.client.model.bulk; -import com.mongodb.MongoBaseInterfaceAssertions; +import com.mongodb.testing.MongoBaseInterfaceAssertions; import org.junit.jupiter.api.Test; final class BaseClientUpsertableWriteModelOptionsTest { diff --git a/driver-core/src/test/unit/com/mongodb/client/model/bulk/BaseClientWriteModelOptionsTest.java b/driver-core/src/test/unit/com/mongodb/client/model/bulk/BaseClientWriteModelOptionsTest.java index 66fec81632e..17b3803727a 100644 --- a/driver-core/src/test/unit/com/mongodb/client/model/bulk/BaseClientWriteModelOptionsTest.java +++ b/driver-core/src/test/unit/com/mongodb/client/model/bulk/BaseClientWriteModelOptionsTest.java @@ -16,7 +16,7 @@ package com.mongodb.client.model.bulk; -import com.mongodb.MongoBaseInterfaceAssertions; +import com.mongodb.testing.MongoBaseInterfaceAssertions; import org.junit.jupiter.api.Test; final class BaseClientWriteModelOptionsTest { diff --git a/driver-core/src/test/unit/com/mongodb/testing/MongoAssertions.java b/driver-core/src/test/unit/com/mongodb/testing/MongoAssertions.java new file mode 100644 index 00000000000..8f1bbf8df67 --- /dev/null +++ b/driver-core/src/test/unit/com/mongodb/testing/MongoAssertions.java @@ -0,0 +1,43 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.testing; + +import org.junit.jupiter.api.function.Executable; +import org.opentest4j.AssertionFailedError; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +public final class MongoAssertions { + + private MongoAssertions() { + //NOP + } + + public static void assertCause( + final Class expectedCause, final String expectedMessageFragment, final Executable e) { + Throwable cause = assertThrows(Throwable.class, e); + while (cause.getCause() != null) { + cause = cause.getCause(); + } + if (!cause.getMessage().contains(expectedMessageFragment)) { + throw new AssertionFailedError("Unexpected message: " + cause.getMessage(), cause); + } + if (!expectedCause.isInstance(cause)) { + throw new AssertionFailedError("Unexpected cause: " + cause.getClass(), assertThrows(Throwable.class, e)); + } + } +} diff --git a/driver-core/src/test/unit/com/mongodb/MongoBaseInterfaceAssertions.java b/driver-core/src/test/unit/com/mongodb/testing/MongoBaseInterfaceAssertions.java similarity index 98% rename from driver-core/src/test/unit/com/mongodb/MongoBaseInterfaceAssertions.java rename to driver-core/src/test/unit/com/mongodb/testing/MongoBaseInterfaceAssertions.java index 93f784b0506..0c0fe913123 100644 --- a/driver-core/src/test/unit/com/mongodb/MongoBaseInterfaceAssertions.java +++ b/driver-core/src/test/unit/com/mongodb/testing/MongoBaseInterfaceAssertions.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb; +package com.mongodb.testing; import org.reflections.Reflections; diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideEncryption25Lookup.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideEncryption25LookupProseTests.java similarity index 92% rename from driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideEncryption25Lookup.java rename to driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideEncryption25LookupProseTests.java index cc3c28841de..6cbd9b60e0d 100644 --- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideEncryption25Lookup.java +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideEncryption25LookupProseTests.java @@ -24,7 +24,7 @@ import com.mongodb.reactivestreams.client.syncadapter.SyncMongoClient; import com.mongodb.reactivestreams.client.vault.ClientEncryptions; -public class ClientSideEncryption25Lookup extends com.mongodb.client.ClientSideEncryption25Lookup { +public class ClientSideEncryption25LookupProseTests extends com.mongodb.client.ClientSideEncryption25LookupProseTests { @Override protected MongoClient createMongoClient(final MongoClientSettings settings) { diff --git a/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryption25Lookup.java b/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryption25LookupProseTests.java similarity index 51% rename from driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryption25Lookup.java rename to driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryption25LookupProseTests.java index 9cd7988ee53..3f8d5192596 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryption25Lookup.java +++ b/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryption25LookupProseTests.java @@ -25,12 +25,7 @@ import com.mongodb.client.vault.ClientEncryption; import com.mongodb.client.vault.ClientEncryptions; import com.mongodb.crypt.capi.MongoCryptException; -import com.mongodb.event.CommandEvent; -import com.mongodb.event.CommandStartedEvent; -import com.mongodb.event.CommandSucceededEvent; import com.mongodb.fixture.EncryptionFixture; -import com.mongodb.internal.connection.TestCommandListener; -import com.mongodb.internal.crypt.capi.CAPI; import org.bson.BsonArray; import org.bson.BsonDocument; import org.bson.Document; @@ -43,18 +38,17 @@ import java.io.File; import java.util.Arrays; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Consumer; import java.util.stream.Collectors; -import java.util.stream.Stream; import static com.mongodb.ClusterFixture.isStandalone; import static com.mongodb.ClusterFixture.serverVersionAtLeast; +import static com.mongodb.ClusterFixture.serverVersionLessThan; import static com.mongodb.client.Fixture.getMongoClientSettingsBuilder; import static com.mongodb.fixture.EncryptionFixture.getKmsProviders; -import static com.mongodb.internal.connection.OidcAuthenticationProseTests.assertCause; +import static com.mongodb.testing.MongoAssertions.assertCause; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.fail; @@ -62,9 +56,8 @@ import static org.junit.jupiter.api.Assumptions.assumeTrue; import static util.JsonPoweredTestHelper.getTestDocument; -public class ClientSideEncryption25Lookup { +public class ClientSideEncryption25LookupProseTests { private MongoClient client; - private TestCommandListener listener; protected MongoClient createMongoClient(final MongoClientSettings settings) { return MongoClients.create(settings); @@ -76,18 +69,11 @@ protected ClientEncryption createClientEncryption(final ClientEncryptionSettings @BeforeEach public void setUp() throws InterruptedException { - // TODO remove debug: - TestCommandListener commandListener = new TestCommandListener(); - TestCommandListener commandListener2 = new TestCommandListener(); - - assumeTrue(serverVersionAtLeast(7, 0)); assumeFalse(isStandalone()); // Create an unencrypted MongoClient. - MongoClient unencryptedClient = createMongoClient(getMongoClientSettingsBuilder() - .addCommandListener(commandListener) - .build()); + MongoClient unencryptedClient = createMongoClient(getMongoClientSettingsBuilder().build()); // drop database db MongoDatabase db = unencryptedClient.getDatabase("db"); @@ -117,7 +103,6 @@ public void setUp() throws InterruptedException { // Create an encrypted MongoClient configured with: Map> kmsProviders = getKmsProviders(EncryptionFixture.KmsProviderType.LOCAL); MongoClient encryptedClient = createMongoClient(getMongoClientSettingsBuilder() - .addCommandListener(commandListener2) .autoEncryptionSettings( AutoEncryptionSettings.builder() .keyVaultNamespace(dataKeysNamespace.getFullName()) @@ -148,11 +133,6 @@ public void setUp() throws InterruptedException { .aggregate(pipeline).first(); assertNotNull(encryptedDoc); assertEquals(Binary.class, encryptedDoc.get(name).getClass()); - - // TODO - System.out.println("ENC-DOC: " + encryptedDoc.toJson()); - System.out.println("DEC-DOC: " + decryptedDoc.toJson()); - System.out.println("VERSION: " + CAPI.mongocrypt_version(null).toString()); }; assertDocument.accept("csfle"); @@ -160,22 +140,10 @@ public void setUp() throws InterruptedException { assertDocument.accept("qe"); assertDocument.accept("qe2"); - // TODO need registration mechanism for shutdown hook? unencryptedClient.close(); encryptedClient.close(); - List events = commandListener2.getEvents(); - System.out.println(">> " + events); - - List events2 = commandListener2.getCommandStartedEvents(); - String collect = events2.stream() - .map(v -> v.getCommand().toJson()) - .collect(Collectors.joining("\n")); - System.out.println(">> " + collect); - - listener = new TestCommandListener(); client = createMongoClient(getMongoClientSettingsBuilder() - .addCommandListener(listener) .autoEncryptionSettings( AutoEncryptionSettings.builder() .keyVaultNamespace(dataKeysNamespace.getFullName()) @@ -184,14 +152,6 @@ public void setUp() throws InterruptedException { .build()); } - @SafeVarargs - @SuppressWarnings("varargs") - private static Map merge( final Map.Entry... entries) { - HashMap result = new HashMap<>(); - result.putAll(Stream.of(entries).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); - return result; - } - @AfterEach @SuppressWarnings("try") public void cleanUp() { @@ -210,15 +170,14 @@ public void cleanUp() { "csfle, csfle2", "qe, qe2", "no_schema, no_schema2"}) - public void case0(final String from, final String to) { - + void cases1Through7(final String from, final String to) { String mql = ("[\n" + " {\"$match\" : {\"\" : \"\"}},\n" + " {\n" + " \"$lookup\" : {\n" + " \"from\" : \"\",\n" + " \"as\" : \"matched\",\n" - + " \"pipeline\" : [ {\"$match\" : {\"\" : \"\"}}, {\"$project\" : {\"_id\" : 0}} ]\n" + + " \"pipeline\" : [ {\"$match\" : {\"\" : \"\"}}, {\"$project\" : {\"_id\" : 0, \"__safeContent__\" : 0}} ]\n" + " }\n" + " },\n" + " {\"$project\" : {\"_id\" : 0, \"__safeContent__\" : 0}}\n" @@ -234,155 +193,7 @@ public void case0(final String from, final String to) { } @Test - public void case1() { - List pipeline = BsonArray.parse("[\n" - + " {\"$match\" : {\"csfle\" : \"csfle\"}},\n" - + " {\n" - + " \"$lookup\" : {\n" - + " \"from\" : \"no_schema\",\n" - + " \"as\" : \"matched\",\n" - + " \"pipeline\" : [ {\"$match\" : {\"no_schema\" : \"no_schema\"}}, {\"$project\" : {\"_id\" : 0}} ]\n" - + " }\n" - + " },\n" - + " {\"$project\" : {\"_id\" : 0}}\n" - + "]").stream().map(stage -> stage.asDocument()).collect(Collectors.toList()); - assertEquals( - Document.parse("{\"csfle\" : \"csfle\", \"matched\" : [ {\"no_schema\" : \"no_schema\"} ]}"), - client.getDatabase("db").getCollection("csfle").aggregate(pipeline).first()); - } - - @Test - public void case2() { - List pipeline = BsonArray.parse("[\n" - + " {\"$match\" : {\"qe\" : \"qe\"}},\n" - + " {\n" - + " \"$lookup\" : {\n" - + " \"from\" : \"no_schema\",\n" - + " \"as\" : \"matched\",\n" - + " \"pipeline\" :\n" - + " [ {\"$match\" : {\"no_schema\" : \"no_schema\"}}, {\"$project\" : {\"_id\" : 0, \"__safeContent__\" : 0}} ]\n" - + " }\n" - + " },\n" - + " {\"$project\" : {\"_id\" : 0, \"__safeContent__\" : 0}}\n" - + "]").stream().map(stage -> stage.asDocument()).collect(Collectors.toList()); - assertEquals( - Document.parse("{\"qe\" : \"qe\", \"matched\" : [ {\"no_schema\" : \"no_schema\"} ]}"), - client.getDatabase("db").getCollection("qe").aggregate(pipeline).first()); - } - - @Test - public void case3() { - List pipeline = BsonArray.parse("[\n" - + " {\"$match\" : {\"no_schema\" : \"no_schema\"}},\n" - + " {\n" - + " \"$lookup\" : {\n" - + " \"from\" : \"csfle\",\n" - + " \"as\" : \"matched\",\n" - + " \"pipeline\" : [ {\"$match\" : {\"csfle\" : \"csfle\"}}, {\"$project\" : {\"_id\" : 0}} ]\n" - + " }\n" - + " },\n" - + " {\"$project\" : {\"_id\" : 0}}\n" - + "]").stream().map(stage -> stage.asDocument()).collect(Collectors.toList()); - assertEquals( - Document.parse("{\"no_schema\" : \"no_schema\", \"matched\" : [ {\"csfle\" : \"csfle\"} ]}"), - client.getDatabase("db").getCollection("no_schema").aggregate(pipeline).first()); - } - - @Test - public void case4() { - List pipeline = BsonArray.parse("[\n" - + " {\"$match\" : {\"no_schema\" : \"no_schema\"}},\n" - + " {\n" - + " \"$lookup\" : {\n" - + " \"from\" : \"qe\",\n" - + " \"as\" : \"matched\",\n" - + " \"pipeline\" : [ {\"$match\" : {\"qe\" : \"qe\"}}, {\"$project\" : {\"_id\" : 0, \"__safeContent__\" : 0}} ]\n" - + " }\n" - + " },\n" - + " {\"$project\" : {\"_id\" : 0}}\n" - + "]").stream().map(stage -> stage.asDocument()).collect(Collectors.toList()); - - Document first = client.getDatabase("db").getCollection("no_schema").aggregate(pipeline).first(); - - List events2 = listener.getEvents(); - String collect = events2.stream() - .map(v -> { - if (v instanceof CommandStartedEvent) { - return "STARTED: " + ((CommandStartedEvent) v).getCommand().toJson(); - } else if (v instanceof CommandSucceededEvent) { - return "SUCCEEDED: " + ((CommandSucceededEvent) v).getResponse().toJson(); - } else { - return ""; - } - }) - .collect(Collectors.joining("\n")); - System.out.println("RESULTS: " + collect); - // TODO: remove debugging - - assertEquals( - Document.parse("{\"no_schema\" : \"no_schema\", \"matched\" : [ {\"qe\" : \"qe\"} ]}"), - first); - } - - @Test - public void case5() { - List pipeline = BsonArray.parse("[\n" - + " {\"$match\" : {\"csfle\" : \"csfle\"}},\n" - + " {\n" - + " \"$lookup\" : {\n" - + " \"from\" : \"csfle2\",\n" - + " \"as\" : \"matched\",\n" - + " \"pipeline\" : [ {\"$match\" : {\"csfle2\" : \"csfle2\"}}, {\"$project\" : {\"_id\" : 0}} ]\n" - + " }\n" - + " },\n" - + " {\"$project\" : {\"_id\" : 0}}\n" - + "]").stream().map(stage -> stage.asDocument()).collect(Collectors.toList()); - - assertEquals( - Document.parse("{\"csfle\" : \"csfle\", \"matched\" : [ {\"csfle2\" : \"csfle2\"} ]}"), - client.getDatabase("db").getCollection("csfle").aggregate(pipeline).first()); - } - - @Test - public void case6() { - List pipeline = BsonArray.parse("[\n" - + " {\"$match\" : {\"qe\" : \"qe\"}},\n" - + " {\n" - + " \"$lookup\" : {\n" - + " \"from\" : \"qe2\",\n" - + " \"as\" : \"matched\",\n" - + " \"pipeline\" : [ {\"$match\" : {\"qe2\" : \"qe2\"}}, {\"$project\" : {\"_id\" : 0, \"__safeContent__\" : 0}} ]\n" - + " }\n" - + " },\n" - + " {\"$project\" : {\"_id\" : 0, \"__safeContent__\" : 0}}\n" - + "]").stream().map(stage -> stage.asDocument()).collect(Collectors.toList()); - - assertEquals( - Document.parse("{\"qe\" : \"qe\", \"matched\" : [ {\"qe2\" : \"qe2\"} ]}"), - client.getDatabase("db").getCollection("qe").aggregate(pipeline).first()); - } - - @Test - public void case7() { - List pipeline = BsonArray.parse("[\n" - + " {\"$match\" : {\"no_schema\" : \"no_schema\"}},\n" - + " {\n" - + " \"$lookup\" : {\n" - + " \"from\" : \"no_schema2\",\n" - + " \"as\" : \"matched\",\n" - + " \"pipeline\" : [ {\"$match\" : {\"no_schema2\" : \"no_schema2\"}}, {\"$project\" : {\"_id\" : 0}} ]\n" - + " }\n" - + " },\n" - + " {\"$project\" : {\"_id\" : 0}}\n" - + "]").stream().map(stage -> stage.asDocument()).collect(Collectors.toList()); - - assertEquals( - Document.parse("{\"no_schema\" : \"no_schema\", \"matched\" : [ {\"no_schema2\" : \"no_schema2\"} ]}"), - client.getDatabase("db").getCollection("no_schema").aggregate(pipeline).first()); - } - - @Test - public void case8() { + void case8() { List pipeline = BsonArray.parse("[\n" + " {\"$match\" : {\"csfle\" : \"qe\"}},\n" + " {\n" @@ -402,8 +213,8 @@ public void case8() { } @Test - public void case9() { - assumeTrue(serverVersionLessThan(8, 1)); + void case9() { + assumeTrue(serverVersionLessThan(8, 1)); List pipeline = BsonArray.parse("[\n" + " {\"$match\" : {\"csfle\" : \"csfle\"}},\n" + " {\n" @@ -419,12 +230,11 @@ public void case9() { RuntimeException.class, "Upgrade", () -> client.getDatabase("db").getCollection("csfle").aggregate(pipeline).first()); - // TODO } public static BsonDocument bsonDocumentFromPath(final String path) { try { - return getTestDocument(new File(ClientSideEncryption25Lookup.class + return getTestDocument(new File(ClientSideEncryption25LookupProseTests.class .getResource("/client-side-encryption-data/lookup/" + path).toURI())); } catch (Exception e) { fail("Unable to load resource", e); diff --git a/driver-sync/src/test/functional/com/mongodb/internal/connection/OidcAuthenticationProseTests.java b/driver-sync/src/test/functional/com/mongodb/internal/connection/OidcAuthenticationProseTests.java index 3ed50446a8d..2d09fd7bdc5 100644 --- a/driver-sync/src/test/functional/com/mongodb/internal/connection/OidcAuthenticationProseTests.java +++ b/driver-sync/src/test/functional/com/mongodb/internal/connection/OidcAuthenticationProseTests.java @@ -39,8 +39,6 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.function.Executable; -import org.opentest4j.AssertionFailedError; import java.io.IOException; import java.lang.reflect.Field; @@ -70,6 +68,7 @@ import static com.mongodb.MongoCredential.OidcCallbackResult; import static com.mongodb.MongoCredential.TOKEN_RESOURCE_KEY; import static com.mongodb.assertions.Assertions.assertNotNull; +import static com.mongodb.testing.MongoAssertions.assertCause; import static java.lang.System.getenv; import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -922,20 +921,6 @@ private void performFind(final MongoClient mongoClient) { .first(); } - public static void assertCause( - final Class expectedCause, final String expectedMessageFragment, final Executable e) { - Throwable cause = assertThrows(Throwable.class, e); - while (cause.getCause() != null) { - cause = cause.getCause(); - } - if (!cause.getMessage().contains(expectedMessageFragment)) { - throw new AssertionFailedError("Unexpected message: " + cause.getMessage(), cause); - } - if (!expectedCause.isInstance(cause)) { - throw new AssertionFailedError("Unexpected cause: " + cause.getClass(), assertThrows(Throwable.class, e)); - } - } - protected void delayNextFind() { try (MongoClient client = createMongoClient(Fixture.getMongoClientSettings())) { From 8e3be8a3f07645485766f89d90eb52aa6d3ea53c Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Tue, 25 Feb 2025 09:40:36 -0700 Subject: [PATCH 4/6] PR fixes --- .../client/internal/CollectionInfoRetriever.java | 10 +++++++--- .../src/main/com/mongodb/client/internal/Crypt.java | 7 +++---- .../client/ClientSideEncryption25LookupProseTests.java | 5 +++-- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/driver-sync/src/main/com/mongodb/client/internal/CollectionInfoRetriever.java b/driver-sync/src/main/com/mongodb/client/internal/CollectionInfoRetriever.java index cb9ee9f7c0c..9d02a1e8756 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/CollectionInfoRetriever.java +++ b/driver-sync/src/main/com/mongodb/client/internal/CollectionInfoRetriever.java @@ -16,12 +16,14 @@ package com.mongodb.client.internal; -import com.mongodb.client.ListCollectionsIterable; import com.mongodb.client.MongoClient; import com.mongodb.internal.time.Timeout; import com.mongodb.lang.Nullable; import org.bson.BsonDocument; +import java.util.ArrayList; +import java.util.List; + import static com.mongodb.assertions.Assertions.notNull; import static com.mongodb.client.internal.TimeoutHelper.databaseWithTimeout; @@ -34,8 +36,10 @@ class CollectionInfoRetriever { this.client = notNull("client", client); } - public ListCollectionsIterable filter(final String databaseName, final BsonDocument filter, @Nullable final Timeout operationTimeout) { + public List filter(final String databaseName, final BsonDocument filter, @Nullable final Timeout operationTimeout) { return databaseWithTimeout(client.getDatabase(databaseName), TIMEOUT_ERROR_MESSAGE, operationTimeout) - .listCollections(BsonDocument.class).filter(filter); + .listCollections(BsonDocument.class) + .filter(filter) + .into(new ArrayList<>()); } } diff --git a/driver-sync/src/main/com/mongodb/client/internal/Crypt.java b/driver-sync/src/main/com/mongodb/client/internal/Crypt.java index 2bb965ff885..15ba16e66da 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/Crypt.java +++ b/driver-sync/src/main/com/mongodb/client/internal/Crypt.java @@ -19,7 +19,6 @@ import com.mongodb.MongoClientException; import com.mongodb.MongoException; import com.mongodb.MongoInternalException; -import com.mongodb.client.ListCollectionsIterable; import com.mongodb.client.MongoClient; import com.mongodb.client.model.vault.DataKeyOptions; import com.mongodb.client.model.vault.EncryptOptions; @@ -42,7 +41,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; -import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.function.Supplier; @@ -310,9 +309,9 @@ private void fetchCredentials(final MongoCryptContext cryptContext) { private void collInfo(final MongoCryptContext cryptContext, final String databaseName, @Nullable final Timeout operationTimeout) { try { - ListCollectionsIterable results = assertNotNull(collectionInfoRetriever) + List results = assertNotNull(collectionInfoRetriever) .filter(databaseName, cryptContext.getMongoOperation(), operationTimeout); - for (BsonDocument result : results.into(new ArrayList<>())) { + for (BsonDocument result : results) { cryptContext.addMongoOperationResult(result); } cryptContext.completeMongoOperation(); diff --git a/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryption25LookupProseTests.java b/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryption25LookupProseTests.java index 3f8d5192596..53c1f89b3ac 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryption25LookupProseTests.java +++ b/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryption25LookupProseTests.java @@ -68,8 +68,7 @@ protected ClientEncryption createClientEncryption(final ClientEncryptionSettings } @BeforeEach - public void setUp() throws InterruptedException { - assumeTrue(serverVersionAtLeast(7, 0)); + public void setUp() { assumeFalse(isStandalone()); // Create an unencrypted MongoClient. @@ -171,6 +170,7 @@ public void cleanUp() { "qe, qe2", "no_schema, no_schema2"}) void cases1Through7(final String from, final String to) { + assumeTrue(serverVersionAtLeast(8, 1)); String mql = ("[\n" + " {\"$match\" : {\"\" : \"\"}},\n" + " {\n" @@ -194,6 +194,7 @@ void cases1Through7(final String from, final String to) { @Test void case8() { + assumeTrue(serverVersionAtLeast(8, 1)); List pipeline = BsonArray.parse("[\n" + " {\"$match\" : {\"csfle\" : \"qe\"}},\n" + " {\n" From c59330cbf04a7704ebd0b5a8b19de1cadec44678 Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Tue, 25 Feb 2025 14:21:14 -0700 Subject: [PATCH 5/6] Spec updates --- ...lientSideEncryption25LookupProseTests.java | 71 ++++++++++--------- 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryption25LookupProseTests.java b/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryption25LookupProseTests.java index 53c1f89b3ac..99b9a00cc3d 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryption25LookupProseTests.java +++ b/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryption25LookupProseTests.java @@ -20,6 +20,7 @@ import com.mongodb.ClientEncryptionSettings; import com.mongodb.MongoClientSettings; import com.mongodb.MongoNamespace; +import com.mongodb.WriteConcern; import com.mongodb.client.model.CreateCollectionOptions; import com.mongodb.client.model.ValidationOptions; import com.mongodb.client.vault.ClientEncryption; @@ -71,35 +72,8 @@ protected ClientEncryption createClientEncryption(final ClientEncryptionSettings public void setUp() { assumeFalse(isStandalone()); - // Create an unencrypted MongoClient. - MongoClient unencryptedClient = createMongoClient(getMongoClientSettingsBuilder().build()); - - // drop database db - MongoDatabase db = unencryptedClient.getDatabase("db"); - db.drop(); - - // Insert into db.keyvault. + // Create an encrypted MongoClient named `encryptedClient` configured with: MongoNamespace dataKeysNamespace = new MongoNamespace("db.keyvault"); - db.getCollection(dataKeysNamespace.getCollectionName(), BsonDocument.class) - .insertOne(bsonDocumentFromPath("key-doc.json")); - - // Create the following collections: - db.createCollection("csfle", new CreateCollectionOptions() - .validationOptions(new ValidationOptions() - .validator(new BsonDocument("$jsonSchema", bsonDocumentFromPath("schema-csfle.json"))))); - db.createCollection("csfle2", new CreateCollectionOptions() - .validationOptions(new ValidationOptions() - .validator(new BsonDocument("$jsonSchema", bsonDocumentFromPath("schema-csfle2.json"))))); - - db.createCollection("qe", - new CreateCollectionOptions().encryptedFields(bsonDocumentFromPath("schema-qe.json"))); - db.createCollection("qe2", - new CreateCollectionOptions().encryptedFields(bsonDocumentFromPath("schema-qe2.json"))); - - db.createCollection("no_schema"); - db.createCollection("no_schema2"); - - // Create an encrypted MongoClient configured with: Map> kmsProviders = getKmsProviders(EncryptionFixture.KmsProviderType.LOCAL); MongoClient encryptedClient = createMongoClient(getMongoClientSettingsBuilder() .autoEncryptionSettings( @@ -108,11 +82,38 @@ public void setUp() { .kmsProviders(kmsProviders) .build()) .build()); + // Use `encryptedClient` to drop `db.keyvault`. + MongoDatabase encryptedDb = encryptedClient.getDatabase("db"); + MongoCollection encryptedCollection = encryptedDb + .getCollection(dataKeysNamespace.getCollectionName(), BsonDocument.class) + .withWriteConcern(WriteConcern.MAJORITY); + encryptedCollection.drop(); + // Insert `` into `db.keyvault` with majority write concern. + encryptedCollection.insertOne(bsonDocumentFromPath("key-doc.json")); + + // Use `encryptedClient` to drop and create the following collections: + Arrays.asList("csfle", "csfle2", "qe", "qe2", "no_schema", "no_schema2").forEach(c -> { + encryptedDb.getCollection(c).drop(); + }); + // create + encryptedDb.createCollection("csfle", new CreateCollectionOptions() + .validationOptions(new ValidationOptions() + .validator(new BsonDocument("$jsonSchema", bsonDocumentFromPath("schema-csfle.json"))))); + encryptedDb.createCollection("csfle2", new CreateCollectionOptions() + .validationOptions(new ValidationOptions() + .validator(new BsonDocument("$jsonSchema", bsonDocumentFromPath("schema-csfle2.json"))))); - // Insert documents with the encrypted MongoClient: + encryptedDb.createCollection("qe", + new CreateCollectionOptions().encryptedFields(bsonDocumentFromPath("schema-qe.json"))); + encryptedDb.createCollection("qe2", + new CreateCollectionOptions().encryptedFields(bsonDocumentFromPath("schema-qe2.json"))); + + encryptedDb.createCollection("no_schema"); + encryptedDb.createCollection("no_schema2"); + + // Insert documents with `encryptedClient`: Consumer insert = (name) -> { - encryptedClient.getDatabase("db").getCollection(name) - .insertOne(new Document(name, name)); + encryptedDb.getCollection(name).insertOne(new Document(name, name)); }; insert.accept("csfle"); insert.accept("csfle2"); @@ -121,14 +122,18 @@ public void setUp() { insert.accept("no_schema"); insert.accept("no_schema2"); + // Create an unencrypted MongoClient named `unencryptedClient`. + MongoClient unencryptedClient = createMongoClient(getMongoClientSettingsBuilder().build()); + MongoDatabase unencryptedDb = unencryptedClient.getDatabase("db"); + Consumer assertDocument = (name) -> { List pipeline = Arrays.asList( BsonDocument.parse("{\"$project\" : {\"_id\" : 0, \"__safeContent__\" : 0}}") ); - Document decryptedDoc = encryptedClient.getDatabase("db").getCollection(name) + Document decryptedDoc = encryptedDb.getCollection(name) .aggregate(pipeline).first(); assertEquals(decryptedDoc, new Document(name, name)); - Document encryptedDoc = unencryptedClient.getDatabase("db").getCollection(name) + Document encryptedDoc = unencryptedDb.getCollection(name) .aggregate(pipeline).first(); assertNotNull(encryptedDoc); assertEquals(Binary.class, encryptedDoc.get(name).getClass()); From ebf4d32dd27e38698dc5d58c0f245a6a938504cb Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Tue, 25 Feb 2025 16:22:33 -0700 Subject: [PATCH 6/6] PR fixes --- .../mongodb/client/ClientSideEncryption25LookupProseTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryption25LookupProseTests.java b/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryption25LookupProseTests.java index 99b9a00cc3d..594c4fd2e13 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryption25LookupProseTests.java +++ b/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryption25LookupProseTests.java @@ -71,6 +71,7 @@ protected ClientEncryption createClientEncryption(final ClientEncryptionSettings @BeforeEach public void setUp() { assumeFalse(isStandalone()); + assumeTrue(serverVersionAtLeast(7, 0)); // Create an encrypted MongoClient named `encryptedClient` configured with: MongoNamespace dataKeysNamespace = new MongoNamespace("db.keyvault");