Skip to content

Commit a30b051

Browse files
committed
Support auto-encryption in the legacy MongoClient
1 parent d01d61a commit a30b051

File tree

16 files changed

+572
-346
lines changed

16 files changed

+572
-346
lines changed

driver-legacy/src/main/com/mongodb/Mongo.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.mongodb.client.internal.MongoClientDelegate;
2525
import com.mongodb.client.internal.MongoIterableImpl;
2626
import com.mongodb.client.internal.OperationExecutor;
27+
import com.mongodb.client.internal.SimpleMongoClient;
2728
import com.mongodb.connection.BufferProvider;
2829
import com.mongodb.connection.Cluster;
2930
import com.mongodb.connection.ClusterConnectionMode;
@@ -57,6 +58,7 @@
5758
import java.util.concurrent.ScheduledExecutorService;
5859

5960
import static com.mongodb.ReadPreference.primary;
61+
import static com.mongodb.client.internal.Crypts.createCrypt;
6062
import static com.mongodb.connection.ClusterConnectionMode.MULTIPLE;
6163
import static com.mongodb.connection.ClusterType.REPLICA_SET;
6264
import static com.mongodb.internal.event.EventListenerHelper.getCommandListener;
@@ -320,10 +322,19 @@ public Mongo(
320322
this.readConcern = options.getReadConcern();
321323
this.optionHolder = new Bytes.OptionHolder(null);
322324
this.credentialsList = unmodifiableList(credentialsList);
323-
this.delegate = new MongoClientDelegate(cluster, credentialsList, this);
325+
326+
AutoEncryptionSettings autoEncryptionSettings = options.getAutoEncryptionSettings();
327+
this.delegate = new MongoClientDelegate(cluster, credentialsList, this,
328+
autoEncryptionSettings == null ? null : createCrypt(asSimpleMongoClient(), autoEncryptionSettings));
329+
324330
cursorCleaningService = options.isCursorFinalizerEnabled() ? createCursorCleaningService() : null;
325331
}
326332

333+
// bit of a hack, but it works because only MongoClient subclass will ever make use of this
334+
SimpleMongoClient asSimpleMongoClient() {
335+
throw new UnsupportedOperationException();
336+
}
337+
327338
/**
328339
* Sets the default write concern to use for write operations executed on any {@link DBCollection} created indirectly from this
329340
* instance, via a {@link DB} instance created from {@link #getDB(String)}.

driver-legacy/src/main/com/mongodb/MongoClient.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.mongodb.client.MongoIterable;
2424
import com.mongodb.client.internal.MongoDatabaseImpl;
2525
import com.mongodb.client.internal.MongoIterables;
26+
import com.mongodb.client.internal.SimpleMongoClient;
2627
import com.mongodb.client.model.changestream.ChangeStreamLevel;
2728
import com.mongodb.lang.Nullable;
2829
import org.bson.BsonDocument;
@@ -705,6 +706,21 @@ public void close() {
705706
super.close();
706707
}
707708

709+
@Override
710+
SimpleMongoClient asSimpleMongoClient() {
711+
return new SimpleMongoClient() {
712+
@Override
713+
public MongoDatabase getDatabase(final String databaseName) {
714+
return MongoClient.this.getDatabase(databaseName);
715+
}
716+
717+
@Override
718+
public void close() {
719+
MongoClient.this.close();
720+
}
721+
};
722+
}
723+
708724
private <TResult> ChangeStreamIterable<TResult> createChangeStreamIterable(@Nullable final ClientSession clientSession,
709725
final List<? extends Bson> pipeline,
710726
final Class<TResult> resultClass) {

driver-legacy/src/main/com/mongodb/MongoClientOptions.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ public class MongoClientOptions {
105105
private final List<ClusterListener> clusterListeners;
106106
private final List<CommandListener> commandListeners;
107107

108+
private final AutoEncryptionSettings autoEncryptionSettings;
109+
108110
@SuppressWarnings("deprecation")
109111
private MongoClientOptions(final Builder builder) {
110112
description = builder.description;
@@ -144,6 +146,7 @@ private MongoClientOptions(final Builder builder) {
144146

145147
clusterListeners = unmodifiableList(builder.clusterListeners);
146148
commandListeners = unmodifiableList(builder.commandListeners);
149+
autoEncryptionSettings = builder.autoEncryptionSettings;
147150

148151
ConnectionPoolSettings.Builder connectionPoolSettingsBuilder = ConnectionPoolSettings.builder()
149152
.minSize(getMinConnectionsPerHost())
@@ -735,6 +738,17 @@ public boolean isCursorFinalizerEnabled() {
735738
return cursorFinalizerEnabled;
736739
}
737740

741+
/**
742+
* Gets the auto-encryption settings
743+
*
744+
* @return the auto-encryption settings, which may be null
745+
* @since 3.11
746+
*/
747+
@Nullable
748+
public AutoEncryptionSettings getAutoEncryptionSettings() {
749+
return autoEncryptionSettings;
750+
}
751+
738752
ConnectionPoolSettings getConnectionPoolSettings() {
739753
return connectionPoolSettings;
740754
}
@@ -875,6 +889,10 @@ public boolean equals(final Object o) {
875889
if (!compressorList.equals(that.compressorList)) {
876890
return false;
877891
}
892+
if (autoEncryptionSettings != null ? !autoEncryptionSettings.equals(that.autoEncryptionSettings)
893+
: that.autoEncryptionSettings != null) {
894+
return false;
895+
}
878896

879897
return true;
880898
}
@@ -917,6 +935,7 @@ public int hashCode() {
917935
result = 31 * result + (cursorFinalizerEnabled ? 1 : 0);
918936
result = 31 * result + (socketFactory != null ? socketFactory.hashCode() : 0);
919937
result = 31 * result + compressorList.hashCode();
938+
result = 31 * result + (autoEncryptionSettings != null ? autoEncryptionSettings.hashCode() : 0);
920939
return result;
921940
}
922941

@@ -963,6 +982,7 @@ public String toString() {
963982
+ ", socketSettings=" + socketSettings
964983
+ ", serverSettings=" + serverSettings
965984
+ ", heartbeatSocketSettings=" + heartbeatSocketSettings
985+
+ ", autoEncryptionSettings=" + autoEncryptionSettings
966986
+ '}';
967987
}
968988

@@ -1015,6 +1035,7 @@ public static class Builder {
10151035
private DBEncoderFactory dbEncoderFactory = DefaultDBEncoder.FACTORY;
10161036
private SocketFactory socketFactory;
10171037
private boolean cursorFinalizerEnabled = true;
1038+
private AutoEncryptionSettings autoEncryptionSettings;
10181039

10191040
/**
10201041
* Creates a Builder for MongoClientOptions, getting the appropriate system properties for initialization.
@@ -1073,6 +1094,7 @@ public Builder(final MongoClientOptions options) {
10731094
connectionPoolListeners.addAll(options.getConnectionPoolListeners());
10741095
serverListeners.addAll(options.getServerListeners());
10751096
serverMonitorListeners.addAll(options.getServerMonitorListeners());
1097+
autoEncryptionSettings = options.getAutoEncryptionSettings();
10761098
}
10771099

10781100
/**
@@ -1628,6 +1650,18 @@ public Builder requiredReplicaSetName(final String requiredReplicaSetName) {
16281650
return this;
16291651
}
16301652

1653+
/**
1654+
* Set options for auto-encryption.
1655+
*
1656+
* @param autoEncryptionSettings auto encryption settings
1657+
* @return this
1658+
* @since 3.11
1659+
*/
1660+
public Builder autoEncryptionSettings(final AutoEncryptionSettings autoEncryptionSettings) {
1661+
this.autoEncryptionSettings = autoEncryptionSettings;
1662+
return this;
1663+
}
1664+
16311665
/**
16321666
* Sets defaults to be what they are in {@code MongoOptions}.
16331667
*
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.mongodb;
18+
19+
import com.mongodb.client.AbstractClientSideEncryptionTest;
20+
import com.mongodb.client.MongoDatabase;
21+
import com.mongodb.internal.connection.TestCommandListener;
22+
import org.bson.BsonArray;
23+
import org.bson.BsonDocument;
24+
import org.junit.After;
25+
import org.junit.runner.RunWith;
26+
import org.junit.runners.Parameterized;
27+
28+
import static com.mongodb.ClusterFixture.getConnectionString;
29+
30+
// See https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/tests
31+
@RunWith(Parameterized.class)
32+
public class ClientSideEncryptionLegacyTest extends AbstractClientSideEncryptionTest {
33+
34+
private MongoClient mongoClient;
35+
36+
public ClientSideEncryptionLegacyTest(final String filename, final String description, final BsonDocument specDocument,
37+
final BsonArray data, final BsonDocument definition, final boolean skipTest) {
38+
super(filename, description, specDocument, data, definition, skipTest);
39+
}
40+
41+
@Override
42+
protected void createMongoClient(final AutoEncryptionSettings autoEncryptionSettings, final TestCommandListener commandListener) {
43+
mongoClient = new MongoClient(new MongoClientURI(getConnectionString().getConnectionString(),
44+
MongoClientOptions.builder()
45+
.autoEncryptionSettings(autoEncryptionSettings)
46+
.addCommandListener(commandListener)));
47+
}
48+
49+
@Override
50+
protected MongoDatabase getDatabase(final String databaseName) {
51+
return mongoClient.getDatabase(databaseName);
52+
}
53+
54+
@After
55+
public void cleanUp() {
56+
if (mongoClient != null) {
57+
mongoClient.close();
58+
}
59+
}
60+
}

driver-legacy/src/test/unit/com/mongodb/MongoClientOptionsSpecification.groovy

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ class MongoClientOptionsSpecification extends Specification {
8888
.build()
8989
options.sslSettings == SslSettings.builder().build();
9090
options.compressorList == []
91+
options.getAutoEncryptionSettings() == null
9192
}
9293

9394
@SuppressWarnings('UnnecessaryObjectReferences')
@@ -161,6 +162,10 @@ class MongoClientOptionsSpecification extends Specification {
161162
def encoderFactory = new MyDBEncoderFactory()
162163
def socketFactory = SSLSocketFactory.getDefault()
163164
def serverSelector = Mock(ServerSelector)
165+
def autoEncryptionSettings = AutoEncryptionSettings.builder()
166+
.keyVaultNamespace('admin.keys')
167+
.kmsProviders(['local': ['key': new byte[64]]])
168+
.build()
164169
def options = MongoClientOptions.builder()
165170
.description('test')
166171
.applicationName('appName')
@@ -193,6 +198,7 @@ class MongoClientOptionsSpecification extends Specification {
193198
.cursorFinalizerEnabled(false)
194199
.dbEncoderFactory(encoderFactory)
195200
.compressorList([MongoCompressor.createZlibCompressor()])
201+
.autoEncryptionSettings(autoEncryptionSettings)
196202
.build()
197203

198204
expect:
@@ -241,6 +247,7 @@ class MongoClientOptionsSpecification extends Specification {
241247
options.sslSettings == SslSettings.builder().enabled(true).invalidHostNameAllowed(true)
242248
.context(SSLContext.getDefault()).build()
243249
options.compressorList == [MongoCompressor.createZlibCompressor()]
250+
options.getAutoEncryptionSettings() == autoEncryptionSettings
244251
}
245252

246253
@IgnoreIf({ isNotAtLeastJava7() })
@@ -336,6 +343,10 @@ class MongoClientOptionsSpecification extends Specification {
336343
.addServerListener(Mock(ServerListener))
337344
.addServerMonitorListener(Mock(ServerMonitorListener))
338345
.compressorList([MongoCompressor.createZlibCompressor()])
346+
.autoEncryptionSettings(AutoEncryptionSettings.builder()
347+
.keyVaultNamespace('admin.keys')
348+
.kmsProviders(['local': ['key': new byte[64]]])
349+
.build())
339350
.build()
340351

341352
then:
@@ -648,6 +659,7 @@ class MongoClientOptionsSpecification extends Specification {
648659
.addServerListener(Mock(ServerListener))
649660
.addServerMonitorListener(Mock(ServerMonitorListener))
650661
.compressorList([MongoCompressor.createZlibCompressor()])
662+
.autoEncryptionSettings()
651663
.build()
652664

653665
when:
@@ -661,12 +673,13 @@ class MongoClientOptionsSpecification extends Specification {
661673
when:
662674
// A regression test so that if any more methods are added then the builder(final MongoClientOptions options) should be updated
663675
def actual = MongoClientOptions.Builder.declaredFields.grep { !it.synthetic } *.name.sort()
664-
def expected = ['alwaysUseMBeans', 'applicationName', 'clusterListeners', 'codecRegistry', 'commandListeners', 'compressorList',
665-
'connectTimeout', 'connectionPoolListeners', 'cursorFinalizerEnabled', 'dbDecoderFactory', 'dbEncoderFactory',
666-
'description', 'heartbeatConnectTimeout', 'heartbeatFrequency', 'heartbeatSocketTimeout', 'localThreshold',
667-
'maxConnectionIdleTime', 'maxConnectionLifeTime', 'maxConnectionsPerHost', 'maxWaitTime', 'minConnectionsPerHost',
668-
'minHeartbeatFrequency', 'readConcern', 'readPreference', 'requiredReplicaSetName', 'retryReads', 'retryWrites',
669-
'serverListeners', 'serverMonitorListeners', 'serverSelectionTimeout', 'serverSelector', 'socketFactory',
676+
def expected = ['alwaysUseMBeans', 'applicationName', 'autoEncryptionSettings', 'clusterListeners', 'codecRegistry',
677+
'commandListeners', 'compressorList', 'connectTimeout', 'connectionPoolListeners', 'cursorFinalizerEnabled',
678+
'dbDecoderFactory', 'dbEncoderFactory', 'description', 'heartbeatConnectTimeout', 'heartbeatFrequency',
679+
'heartbeatSocketTimeout', 'localThreshold', 'maxConnectionIdleTime', 'maxConnectionLifeTime',
680+
'maxConnectionsPerHost', 'maxWaitTime', 'minConnectionsPerHost', 'minHeartbeatFrequency', 'readConcern',
681+
'readPreference', 'requiredReplicaSetName', 'retryReads', 'retryWrites', 'serverListeners',
682+
'serverMonitorListeners', 'serverSelectionTimeout', 'serverSelector', 'socketFactory',
670683
'socketKeepAlive', 'socketTimeout', 'sslContext', 'sslEnabled', 'sslInvalidHostNameAllowed',
671684
'threadsAllowedToBlockForConnectionMultiplier', 'writeConcern']
672685

driver-sync/src/main/com/mongodb/client/MongoClients.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,7 @@ public static MongoClient create(final ConnectionString connectionString,
111111
public static MongoClient create(final MongoClientSettings settings, @Nullable final MongoDriverInformation mongoDriverInformation) {
112112
MongoDriverInformation.Builder builder = mongoDriverInformation == null ? MongoDriverInformation.builder()
113113
: MongoDriverInformation.builder(mongoDriverInformation);
114-
MongoClientImpl client = new MongoClientImpl(settings, builder.driverName("sync").build());
115-
client.init();
116-
return client;
114+
return new MongoClientImpl(settings, builder.driverName("sync").build());
117115
}
118116

119117
private MongoClients() {

driver-sync/src/main/com/mongodb/client/internal/CollectionInfoRetriever.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,16 @@
1616

1717
package com.mongodb.client.internal;
1818

19-
import com.mongodb.client.MongoClient;
2019
import com.mongodb.lang.Nullable;
2120
import org.bson.BsonDocument;
2221

2322
import static com.mongodb.assertions.Assertions.notNull;
2423

2524
class CollectionInfoRetriever {
2625

27-
private final MongoClient client;
26+
private final SimpleMongoClient client;
2827

29-
CollectionInfoRetriever(final MongoClient client) {
28+
CollectionInfoRetriever(final SimpleMongoClient client) {
3029
this.client = notNull("client", client);
3130
}
3231

driver-sync/src/main/com/mongodb/client/internal/Crypts.java

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import com.mongodb.MongoClientException;
2222
import com.mongodb.MongoClientSettings;
2323
import com.mongodb.MongoNamespace;
24-
import com.mongodb.client.MongoClient;
2524
import com.mongodb.client.MongoClients;
2625
import com.mongodb.crypt.capi.MongoAwsKmsProviderOptions;
2726
import com.mongodb.crypt.capi.MongoCryptOptions;
@@ -34,9 +33,9 @@
3433
import java.security.NoSuchAlgorithmException;
3534
import java.util.Map;
3635

37-
final class Crypts {
36+
public final class Crypts {
3837

39-
static Crypt createCrypt(final MongoClient client, final AutoEncryptionSettings options) {
38+
public static Crypt createCrypt(final SimpleMongoClient client, final AutoEncryptionSettings options) {
4039
return new Crypt(MongoCrypts.create(createMongoCryptOptions(options.getKmsProviders(),
4140
options.getNamespaceToLocalSchemaDocumentMap())),
4241
new CollectionInfoRetriever(client),
@@ -46,7 +45,7 @@ static Crypt createCrypt(final MongoClient client, final AutoEncryptionSettings
4645
options.isBypassAutoEncryption());
4746
}
4847

49-
static Crypt create(final MongoClient keyVaultClient, final KeyVaultEncryptionSettings options) {
48+
static Crypt create(final SimpleMongoClient keyVaultClient, final KeyVaultEncryptionSettings options) {
5049
return new Crypt(MongoCrypts.create(
5150
createMongoCryptOptions(options.getKmsProviders(), null)),
5251
createKeyRetriever(keyVaultClient, false, options.getKeyVaultNamespace()),
@@ -79,13 +78,13 @@ private static MongoCryptOptions createMongoCryptOptions(final Map<String, Map<S
7978
return mongoCryptOptionsBuilder.build();
8079
}
8180

82-
private static KeyRetriever createKeyRetriever(final MongoClient defaultKeyVaultClient,
81+
private static KeyRetriever createKeyRetriever(final SimpleMongoClient defaultKeyVaultClient,
8382
final MongoClientSettings keyVaultMongoClientSettings,
8483
final String keyVaultNamespaceString) {
85-
MongoClient keyVaultClient;
84+
SimpleMongoClient keyVaultClient;
8685
boolean keyRetrieverOwnsClient;
8786
if (keyVaultMongoClientSettings != null) {
88-
keyVaultClient = MongoClients.create(keyVaultMongoClientSettings);
87+
keyVaultClient = SimpleMongoClients.create(MongoClients.create(keyVaultMongoClientSettings));
8988
keyRetrieverOwnsClient = true;
9089
} else {
9190
keyVaultClient = defaultKeyVaultClient;
@@ -95,7 +94,7 @@ private static KeyRetriever createKeyRetriever(final MongoClient defaultKeyVault
9594
return createKeyRetriever(keyVaultClient, keyRetrieverOwnsClient, keyVaultNamespaceString);
9695
}
9796

98-
private static KeyRetriever createKeyRetriever(final MongoClient keyVaultClient, final boolean keyRetrieverOwnsClient,
97+
private static KeyRetriever createKeyRetriever(final SimpleMongoClient keyVaultClient, final boolean keyRetrieverOwnsClient,
9998
final String keyVaultNamespaceString) {
10099
return new KeyRetriever(keyVaultClient, keyRetrieverOwnsClient, new MongoNamespace(keyVaultNamespaceString));
101100
}

0 commit comments

Comments
 (0)