Skip to content

Commit 5966e95

Browse files
authored
Add CSOT to field Encryption/Decryption. (#1308)
JAVA-4055
1 parent 2686904 commit 5966e95

File tree

43 files changed

+1629
-324
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1629
-324
lines changed

driver-core/src/main/com/mongodb/ClientEncryptionSettings.java

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,18 @@
1717
package com.mongodb;
1818

1919
import com.mongodb.annotations.NotThreadSafe;
20+
import com.mongodb.lang.Nullable;
2021

2122
import javax.net.ssl.SSLContext;
2223
import java.util.HashMap;
2324
import java.util.Map;
25+
import java.util.concurrent.TimeUnit;
2426
import java.util.function.Supplier;
2527

28+
import static com.mongodb.assertions.Assertions.isTrueArgument;
2629
import static com.mongodb.assertions.Assertions.notNull;
2730
import static java.util.Collections.unmodifiableMap;
31+
import static java.util.concurrent.TimeUnit.MILLISECONDS;
2832

2933
/**
3034
* The client-side settings for data key creation and explicit encryption.
@@ -42,6 +46,8 @@ public final class ClientEncryptionSettings {
4246
private final Map<String, Map<String, Object>> kmsProviders;
4347
private final Map<String, Supplier<Map<String, Object>>> kmsProviderPropertySuppliers;
4448
private final Map<String, SSLContext> kmsProviderSslContextMap;
49+
@Nullable
50+
private final Long timeoutMS;
4551
/**
4652
* A builder for {@code ClientEncryptionSettings} so that {@code ClientEncryptionSettings} can be immutable, and to support easier
4753
* construction through chaining.
@@ -53,6 +59,8 @@ public static final class Builder {
5359
private Map<String, Map<String, Object>> kmsProviders;
5460
private Map<String, Supplier<Map<String, Object>>> kmsProviderPropertySuppliers = new HashMap<>();
5561
private Map<String, SSLContext> kmsProviderSslContextMap = new HashMap<>();
62+
@Nullable
63+
private Long timeoutMS;
5664

5765
/**
5866
* Sets the {@link MongoClientSettings} that will be used to access the key vault.
@@ -120,6 +128,43 @@ public Builder kmsProviderSslContextMap(final Map<String, SSLContext> kmsProvide
120128
return this;
121129
}
122130

131+
/**
132+
* Sets the time limit for the full execution of an operation.
133+
*
134+
* <ul>
135+
* <li>{@code null} means that the timeout mechanism for operations will defer to using:
136+
* <ul>
137+
* <li>{@code waitQueueTimeoutMS}: The maximum wait time in milliseconds that a thread may wait for a connection to become
138+
* available</li>
139+
* <li>{@code socketTimeoutMS}: How long a send or receive on a socket can take before timing out.</li>
140+
* <li>{@code wTimeoutMS}: How long the server will wait for the write concern to be fulfilled before timing out.</li>
141+
* <li>{@code maxTimeMS}: The cumulative time limit for processing operations on a cursor.
142+
* See: <a href="https://docs.mongodb.com/manual/reference/method/cursor.maxTimeMS">cursor.maxTimeMS</a>.</li>
143+
* <li>{@code maxCommitTimeMS}: The maximum amount of time to allow a single {@code commitTransaction} command to execute.
144+
* See: {@link TransactionOptions#getMaxCommitTime}.</li>
145+
* </ul>
146+
* </li>
147+
* <li>{@code 0} means infinite timeout.</li>
148+
* <li>{@code > 0} The time limit to use for the full execution of an operation.</li>
149+
* </ul>
150+
*
151+
* <p><strong>Note:</strong> The timeout set through this method overrides the timeout defined in the key vault client settings
152+
* specified in {@link #keyVaultMongoClientSettings(MongoClientSettings)}.
153+
* Essentially, for operations that require accessing the key vault, the remaining timeout from the initial operation
154+
* determines the duration allowed for key vault access.</p>
155+
*
156+
* @param timeout the timeout
157+
* @param timeUnit the time unit
158+
* @return this
159+
* @since CSOT
160+
* @see #getTimeout
161+
*/
162+
public ClientEncryptionSettings.Builder timeout(final long timeout, final TimeUnit timeUnit) {
163+
isTrueArgument("timeoutMS must be >= 0", timeout >= 0);
164+
this.timeoutMS = MILLISECONDS.convert(timeout, timeUnit);
165+
return this;
166+
}
167+
123168
/**
124169
* Build an instance of {@code ClientEncryptionSettings}.
125170
*
@@ -253,12 +298,45 @@ public Map<String, SSLContext> getKmsProviderSslContextMap() {
253298
return unmodifiableMap(kmsProviderSslContextMap);
254299
}
255300

301+
/**
302+
* The time limit for the full execution of an operation.
303+
*
304+
* <p>If set the following deprecated options will be ignored:
305+
* {@code waitQueueTimeoutMS}, {@code socketTimeoutMS}, {@code wTimeoutMS}, {@code maxTimeMS} and {@code maxCommitTimeMS}</p>
306+
*
307+
* <ul>
308+
* <li>{@code null} means that the timeout mechanism for operations will defer to using:
309+
* <ul>
310+
* <li>{@code waitQueueTimeoutMS}: The maximum wait time in milliseconds that a thread may wait for a connection to become
311+
* available</li>
312+
* <li>{@code socketTimeoutMS}: How long a send or receive on a socket can take before timing out.</li>
313+
* <li>{@code wTimeoutMS}: How long the server will wait for the write concern to be fulfilled before timing out.</li>
314+
* <li>{@code maxTimeMS}: The cumulative time limit for processing operations on a cursor.
315+
* See: <a href="https://docs.mongodb.com/manual/reference/method/cursor.maxTimeMS">cursor.maxTimeMS</a>.</li>
316+
* <li>{@code maxCommitTimeMS}: The maximum amount of time to allow a single {@code commitTransaction} command to execute.
317+
* See: {@link TransactionOptions#getMaxCommitTime}.</li>
318+
* </ul>
319+
* </li>
320+
* <li>{@code 0} means infinite timeout.</li>
321+
* <li>{@code > 0} The time limit to use for the full execution of an operation.</li>
322+
* </ul>
323+
*
324+
* @param timeUnit the time unit
325+
* @return the timeout in the given time unit
326+
* @since CSOT
327+
*/
328+
@Nullable
329+
public Long getTimeout(final TimeUnit timeUnit) {
330+
return timeoutMS == null ? null : timeUnit.convert(timeoutMS, MILLISECONDS);
331+
}
332+
256333
private ClientEncryptionSettings(final Builder builder) {
257334
this.keyVaultMongoClientSettings = notNull("keyVaultMongoClientSettings", builder.keyVaultMongoClientSettings);
258335
this.keyVaultNamespace = notNull("keyVaultNamespace", builder.keyVaultNamespace);
259336
this.kmsProviders = notNull("kmsProviders", builder.kmsProviders);
260337
this.kmsProviderPropertySuppliers = notNull("kmsProviderPropertySuppliers", builder.kmsProviderPropertySuppliers);
261338
this.kmsProviderSslContextMap = notNull("kmsProviderSslContextMap", builder.kmsProviderSslContextMap);
339+
timeoutMS = builder.timeoutMS;
262340
}
263341

264342
}

driver-core/src/main/com/mongodb/internal/TimeoutContext.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,14 @@ public static MongoOperationTimeoutException createMongoTimeoutException(final S
5151
}
5252

5353
public static MongoOperationTimeoutException createMongoTimeoutException(final Throwable cause) {
54+
return createMongoTimeoutException("Operation timed out: " + cause.getMessage(), cause);
55+
}
56+
57+
public static MongoOperationTimeoutException createMongoTimeoutException(final String message, final Throwable cause) {
5458
if (cause instanceof MongoOperationTimeoutException) {
5559
return (MongoOperationTimeoutException) cause;
5660
}
57-
return new MongoOperationTimeoutException("Operation timed out: " + cause.getMessage(), cause);
61+
return new MongoOperationTimeoutException(message, cause);
5862
}
5963

6064
public static TimeoutContext createMaintenanceTimeoutContext(final TimeoutSettings timeoutSettings) {
@@ -65,6 +69,10 @@ public TimeoutContext(final TimeoutSettings timeoutSettings) {
6569
this(false, timeoutSettings, calculateTimeout(timeoutSettings.getTimeoutMS()));
6670
}
6771

72+
public TimeoutContext(final TimeoutSettings timeoutSettings, @Nullable final Timeout timeout) {
73+
this(false, timeoutSettings, timeout);
74+
}
75+
6876
TimeoutContext(final boolean isMaintenanceContext, final TimeoutSettings timeoutSettings, @Nullable final Timeout timeout) {
6977
this.isMaintenanceContext = isMaintenanceContext;
7078
this.timeoutSettings = timeoutSettings;
@@ -271,4 +279,9 @@ public Timeout startWaitQueueTimeout(final StartTime checkoutStart) {
271279
public int getConnectTimeoutMs() {
272280
return (int) getTimeoutSettings().getConnectTimeoutMS();
273281
}
282+
283+
@Nullable
284+
public Timeout getTimeout() {
285+
return timeout;
286+
}
274287
}

driver-core/src/main/com/mongodb/internal/connection/OperationContext.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,6 @@ public OperationContext(final RequestContext requestContext, final SessionContex
4343
this(NEXT_ID.incrementAndGet(), requestContext, sessionContext, timeoutContext, serverApi);
4444
}
4545

46-
public static OperationContext todoOperationContext() {
47-
// TODO (CSOT) - JAVA-4055 : should be removed during crypt work
48-
return simpleOperationContext(TimeoutSettings.DEFAULT, null);
49-
}
50-
5146
public static OperationContext simpleOperationContext(
5247
final TimeoutSettings timeoutSettings, @Nullable final ServerApi serverApi) {
5348
return new OperationContext(
@@ -57,6 +52,14 @@ public static OperationContext simpleOperationContext(
5752
serverApi);
5853
}
5954

55+
public static OperationContext simpleOperationContext(final TimeoutContext timeoutContext) {
56+
return new OperationContext(
57+
IgnorableRequestContext.INSTANCE,
58+
NoOpSessionContext.INSTANCE,
59+
timeoutContext,
60+
null);
61+
}
62+
6063
public OperationContext withSessionContext(final SessionContext sessionContext) {
6164
return new OperationContext(id, requestContext, sessionContext, timeoutContext, serverApi);
6265
}

driver-core/src/test/functional/com/mongodb/client/CommandMonitoringTestHelper.java

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.bson.BsonInt32;
3030
import org.bson.BsonInt64;
3131
import org.bson.BsonString;
32+
import org.bson.BsonType;
3233
import org.bson.BsonValue;
3334
import org.bson.codecs.BsonDocumentCodec;
3435
import org.bson.codecs.BsonValueCodecProvider;
@@ -224,25 +225,33 @@ private static CommandSucceededEvent massageActualCommandSucceededEvent(final Co
224225
private static CommandStartedEvent massageActualCommandStartedEvent(final CommandStartedEvent event,
225226
@Nullable final Map<String, BsonDocument> lsidMap,
226227
final CommandStartedEvent expectedCommandStartedEvent) {
227-
BsonDocument command = getWritableCloneOfCommand(event.getCommand());
228+
BsonDocument actualCommand = getWritableCloneOfCommand(event.getCommand());
229+
BsonDocument expectedCommand = expectedCommandStartedEvent.getCommand();
228230

229-
massageCommand(event, command);
231+
massageCommand(event, actualCommand);
230232

231-
if (command.containsKey("readConcern") && (command.getDocument("readConcern").containsKey("afterClusterTime"))) {
232-
command.getDocument("readConcern").put("afterClusterTime", new BsonInt32(42));
233+
if (actualCommand.containsKey("readConcern") && (actualCommand.getDocument("readConcern").containsKey("afterClusterTime"))) {
234+
actualCommand.getDocument("readConcern").put("afterClusterTime", new BsonInt32(42));
233235
}
234-
// Tests expect maxTimeMS to be int32, but Java API requires maxTime to be a long. This massage seems preferable to casting
235-
if (command.containsKey("maxTimeMS")) {
236-
command.put("maxTimeMS", new BsonInt32(command.getNumber("maxTimeMS").intValue()));
236+
if (actualCommand.containsKey("maxTimeMS") && !isExpectedMaxTimeMsLong(expectedCommand)) {
237+
// Some tests expect maxTimeMS to be int32, but Java API requires maxTime to be a long. This massage seems preferable to casting
238+
actualCommand.put("maxTimeMS", new BsonInt32(actualCommand.getNumber("maxTimeMS").intValue()));
237239
}
238240
// Tests do not expect the "ns" field in a result after running createIndex.
239-
if (command.containsKey("createIndexes") && command.containsKey("indexes")) {
240-
massageCommandIndexes(command.getArray("indexes"));
241+
if (actualCommand.containsKey("createIndexes") && actualCommand.containsKey("indexes")) {
242+
massageCommandIndexes(actualCommand.getArray("indexes"));
241243
}
242-
massageActualCommand(command, expectedCommandStartedEvent.getCommand());
244+
massageActualCommand(actualCommand, expectedCommand);
243245

244246
return new CommandStartedEvent(event.getRequestContext(), event.getOperationId(), event.getRequestId(),
245-
event.getConnectionDescription(), event.getDatabaseName(), event.getCommandName(), command);
247+
event.getConnectionDescription(), event.getDatabaseName(), event.getCommandName(), actualCommand);
248+
}
249+
250+
private static boolean isExpectedMaxTimeMsLong(final BsonDocument expectedCommand) {
251+
if (expectedCommand.containsKey("maxTimeMS")) {
252+
return expectedCommand.get("maxTimeMS").getBsonType() == BsonType.INT64;
253+
}
254+
return false;
246255
}
247256

248257
private static void massageCommandIndexes(final BsonArray indexes) {

driver-core/src/test/functional/com/mongodb/client/CrudTestHelper.java

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@
2121
import org.bson.BsonType;
2222
import org.bson.BsonValue;
2323

24-
import static org.junit.Assert.assertEquals;
24+
import java.util.List;
25+
import java.util.stream.Collectors;
26+
27+
import static java.util.Collections.singletonList;
2528

2629
public final class CrudTestHelper {
2730

@@ -32,15 +35,12 @@ public static void replaceTypeAssertionWithActual(final BsonDocument expected, f
3235
BsonDocument valueDocument = value.asDocument();
3336
BsonValue actualValue = actual.get(key);
3437
if (valueDocument.size() == 1 && valueDocument.getFirstKey().equals("$$type")) {
35-
String type = valueDocument.getString("$$type").getValue();
36-
if (type.equals("binData")) {
37-
assertEquals(BsonType.BINARY, actualValue.getBsonType());
38-
expected.put(key, actualValue);
39-
} else if (type.equals("long")) {
40-
assertEquals(BsonType.INT64, actualValue.getBsonType());
38+
List<String> types = getExpectedTypes(valueDocument.get("$$type"));
39+
String actualType = asTypeString(actualValue.getBsonType());
40+
if (types.contains(actualType)) {
4141
expected.put(key, actualValue);
4242
} else {
43-
throw new UnsupportedOperationException("Unsupported type: " + type);
43+
throw new UnsupportedOperationException("Unsupported type: " + actualValue);
4444
}
4545
} else if (actualValue != null && actualValue.isDocument()) {
4646
replaceTypeAssertionWithActual(valueDocument, actualValue.asDocument());
@@ -53,6 +53,31 @@ public static void replaceTypeAssertionWithActual(final BsonDocument expected, f
5353
}
5454
}
5555

56+
private static String asTypeString(final BsonType bsonType) {
57+
switch (bsonType) {
58+
case BINARY:
59+
return "binData";
60+
case INT32:
61+
return "int";
62+
case INT64:
63+
return "long";
64+
default:
65+
throw new UnsupportedOperationException("Unsupported bson type conversion to string: " + bsonType);
66+
}
67+
}
68+
69+
private static List<String> getExpectedTypes(final BsonValue expectedTypes) {
70+
List<String> types;
71+
if (expectedTypes.isString()) {
72+
types = singletonList(expectedTypes.asString().getValue());
73+
} else if (expectedTypes.isArray()) {
74+
types = expectedTypes.asArray().stream().map(type -> type.asString().getValue()).collect(Collectors.toList());
75+
} else {
76+
throw new UnsupportedOperationException("Unsupported type for $$type value");
77+
}
78+
return types;
79+
}
80+
5681
private static void replaceTypeAssertionWithActual(final BsonArray expected, final BsonArray actual) {
5782
for (int i = 0; i < expected.size(); i++) {
5883
BsonValue value = expected.get(i);
@@ -63,6 +88,7 @@ private static void replaceTypeAssertionWithActual(final BsonArray expected, fin
6388
}
6489
}
6590
}
91+
6692
private CrudTestHelper() {
6793
}
6894

driver-core/src/test/resources/client-side-encryption/legacy/timeoutMS.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
"tests": [
101101
{
102102
"description": "timeoutMS applied to listCollections to get collection schema",
103+
"comment": "Updated timeoutMS and blockTimeMS manually to address race conditions in tests with SSL handshake.",
103104
"failPoint": {
104105
"configureFailPoint": "failCommand",
105106
"mode": {
@@ -110,7 +111,7 @@
110111
"listCollections"
111112
],
112113
"blockConnection": true,
113-
"blockTimeMS": 60
114+
"blockTimeMS": 100
114115
}
115116
},
116117
"clientOptions": {
@@ -119,7 +120,7 @@
119120
"aws": {}
120121
}
121122
},
122-
"timeoutMS": 50
123+
"timeoutMS": 90
123124
},
124125
"operations": [
125126
{

driver-legacy/src/test/functional/com/mongodb/ClientSideEncryptionLegacyTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ protected MongoDatabase getDatabase(final String databaseName) {
4747

4848
@After
4949
public void cleanUp() {
50+
super.cleanUp();
5051
if (mongoClient != null) {
5152
mongoClient.close();
5253
}

0 commit comments

Comments
 (0)