Skip to content

Commit 0bb42fa

Browse files
CSHARP-4255: Fix bug and some tests. (#993)
1 parent c0c521e commit 0bb42fa

File tree

9 files changed

+425
-46
lines changed

9 files changed

+425
-46
lines changed

src/MongoDB.Driver/CreateCollectionOptions.cs

+48
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,27 @@ public DocumentValidationLevel? ValidationLevel
196196
get { return _validationLevel; }
197197
set { _validationLevel = value; }
198198
}
199+
200+
internal virtual CreateCollectionOptions Clone() =>
201+
new CreateCollectionOptions
202+
{
203+
_autoIndexId = _autoIndexId,
204+
_capped = _capped,
205+
_changeStreamPreAndPostImagesOptions = _changeStreamPreAndPostImagesOptions,
206+
_collation = _collation,
207+
_encryptedFields = _encryptedFields,
208+
_expireAfter = _expireAfter,
209+
_indexOptionDefaults = _indexOptionDefaults,
210+
_maxDocuments = _maxDocuments,
211+
_maxSize = _maxSize,
212+
_noPadding = _noPadding,
213+
_serializerRegistry = _serializerRegistry,
214+
_storageEngine = _storageEngine,
215+
_timeSeriesOptions = _timeSeriesOptions,
216+
_usePowerOf2Sizes = _usePowerOf2Sizes,
217+
_validationAction = _validationAction,
218+
_validationLevel = _validationLevel
219+
};
199220
}
200221

201222
/// <summary>
@@ -282,5 +303,32 @@ public FilterDefinition<TDocument> Validator
282303
get { return _validator; }
283304
set { _validator = value; }
284305
}
306+
307+
internal override CreateCollectionOptions Clone() =>
308+
new CreateCollectionOptions<TDocument>
309+
{
310+
#pragma warning disable CS0618 // Type or member is obsolete
311+
AutoIndexId = base.AutoIndexId,
312+
#pragma warning restore CS0618 // Type or member is obsolete
313+
Capped = base.Capped,
314+
ChangeStreamPreAndPostImagesOptions = base.ChangeStreamPreAndPostImagesOptions,
315+
Collation = base.Collation,
316+
EncryptedFields = base.EncryptedFields,
317+
ExpireAfter = base.ExpireAfter,
318+
IndexOptionDefaults = base.IndexOptionDefaults,
319+
MaxDocuments = base.MaxDocuments,
320+
MaxSize = base.MaxSize,
321+
NoPadding = base.NoPadding,
322+
SerializerRegistry = base.SerializerRegistry,
323+
StorageEngine = base.StorageEngine,
324+
TimeSeriesOptions = base.TimeSeriesOptions,
325+
UsePowerOf2Sizes = base.UsePowerOf2Sizes,
326+
ValidationAction = base.ValidationAction,
327+
ValidationLevel = base.ValidationLevel,
328+
329+
_clusteredIndex = _clusteredIndex,
330+
_documentSerializer = _documentSerializer,
331+
_validator = _validator
332+
};
285333
}
286334
}

src/MongoDB.Driver/Encryption/ClientEncryption.cs

+44-18
Original file line numberDiff line numberDiff line change
@@ -82,59 +82,85 @@ public Task<BsonDocument> AddAlternateKeyNameAsync(Guid id, string alternateKeyN
8282
/// <summary>
8383
/// Create encrypted collection.
8484
/// </summary>
85-
/// <param name="collectionNamespace">The collection namespace.</param>
85+
/// <param name="database">The database.</param>
86+
/// <param name="collectionName">The collection name.</param>
8687
/// <param name="createCollectionOptions">The create collection options.</param>
8788
/// <param name="kmsProvider">The kms provider.</param>
8889
/// <param name="dataKeyOptions">The datakey options.</param>
8990
/// <param name="cancellationToken">The cancellation token.</param>
91+
/// <returns>The operation result.</returns>
9092
/// <remarks>
9193
/// if EncryptionFields contains a keyId with a null value, a data key will be automatically generated and assigned to keyId value.
9294
/// </remarks>
93-
public void CreateEncryptedCollection<TCollection>(CollectionNamespace collectionNamespace, CreateCollectionOptions createCollectionOptions, string kmsProvider, DataKeyOptions dataKeyOptions, CancellationToken cancellationToken = default)
95+
public CreateEncryptedCollectionResult CreateEncryptedCollection(IMongoDatabase database, string collectionName, CreateCollectionOptions createCollectionOptions, string kmsProvider, DataKeyOptions dataKeyOptions, CancellationToken cancellationToken = default)
9496
{
95-
Ensure.IsNotNull(collectionNamespace, nameof(collectionNamespace));
97+
Ensure.IsNotNull(database, nameof(database));
98+
Ensure.IsNotNull(collectionName, nameof(collectionName));
9699
Ensure.IsNotNull(createCollectionOptions, nameof(createCollectionOptions));
97100
Ensure.IsNotNull(dataKeyOptions, nameof(dataKeyOptions));
98101
Ensure.IsNotNull(kmsProvider, nameof(kmsProvider));
99102

100-
foreach (var fieldDocument in EncryptedCollectionHelper.IterateEmptyKeyIds(collectionNamespace, createCollectionOptions.EncryptedFields))
103+
var encryptedFields = createCollectionOptions.EncryptedFields?.DeepClone()?.AsBsonDocument;
104+
try
101105
{
102-
var dataKey = CreateDataKey(kmsProvider, dataKeyOptions, cancellationToken);
103-
EncryptedCollectionHelper.ModifyEncryptedFields(fieldDocument, dataKey);
106+
foreach (var fieldDocument in EncryptedCollectionHelper.IterateEmptyKeyIds(new CollectionNamespace(database.DatabaseNamespace.DatabaseName, collectionName), encryptedFields))
107+
{
108+
var dataKey = CreateDataKey(kmsProvider, dataKeyOptions, cancellationToken);
109+
EncryptedCollectionHelper.ModifyEncryptedFields(fieldDocument, dataKey);
110+
}
111+
112+
var effectiveCreateEncryptionOptions = createCollectionOptions.Clone();
113+
effectiveCreateEncryptionOptions.EncryptedFields = encryptedFields;
114+
database.CreateCollection(collectionName, effectiveCreateEncryptionOptions, cancellationToken);
115+
}
116+
catch (Exception ex)
117+
{
118+
throw new MongoEncryptionCreateCollectionException(ex, encryptedFields);
104119
}
105120

106-
var database = _libMongoCryptController.KeyVaultClient.GetDatabase(collectionNamespace.DatabaseNamespace.DatabaseName);
107-
108-
database.CreateCollection(collectionNamespace.CollectionName, createCollectionOptions, cancellationToken);
121+
return new CreateEncryptedCollectionResult(encryptedFields);
109122
}
110123

111124
/// <summary>
112125
/// Create encrypted collection.
113126
/// </summary>
114-
/// <param name="collectionNamespace">The collection namespace.</param>
127+
/// <param name="database">The database.</param>
128+
/// <param name="collectionName">The collection name.</param>
115129
/// <param name="createCollectionOptions">The create collection options.</param>
116130
/// <param name="kmsProvider">The kms provider.</param>
117131
/// <param name="dataKeyOptions">The datakey options.</param>
118132
/// <param name="cancellationToken">The cancellation token.</param>
133+
/// <returns>The operation result.</returns>
119134
/// <remarks>
120135
/// if EncryptionFields contains a keyId with a null value, a data key will be automatically generated and assigned to keyId value.
121136
/// </remarks>
122-
public async Task CreateEncryptedCollectionAsync<TCollection>(CollectionNamespace collectionNamespace, CreateCollectionOptions createCollectionOptions, string kmsProvider, DataKeyOptions dataKeyOptions, CancellationToken cancellationToken = default)
137+
public async Task<CreateEncryptedCollectionResult> CreateEncryptedCollectionAsync(IMongoDatabase database, string collectionName, CreateCollectionOptions createCollectionOptions, string kmsProvider, DataKeyOptions dataKeyOptions, CancellationToken cancellationToken = default)
123138
{
124-
Ensure.IsNotNull(collectionNamespace, nameof(collectionNamespace));
139+
Ensure.IsNotNull(database, nameof(database));
140+
Ensure.IsNotNull(collectionName, nameof(collectionName));
125141
Ensure.IsNotNull(createCollectionOptions, nameof(createCollectionOptions));
126142
Ensure.IsNotNull(dataKeyOptions, nameof(dataKeyOptions));
127143
Ensure.IsNotNull(kmsProvider, nameof(kmsProvider));
128144

129-
foreach (var fieldDocument in EncryptedCollectionHelper.IterateEmptyKeyIds(collectionNamespace, createCollectionOptions.EncryptedFields))
145+
var encryptedFields = createCollectionOptions.EncryptedFields?.DeepClone()?.AsBsonDocument;
146+
try
130147
{
131-
var dataKey = await CreateDataKeyAsync(kmsProvider, dataKeyOptions, cancellationToken).ConfigureAwait(false);
132-
EncryptedCollectionHelper.ModifyEncryptedFields(fieldDocument, dataKey);
148+
foreach (var fieldDocument in EncryptedCollectionHelper.IterateEmptyKeyIds(new CollectionNamespace(database.DatabaseNamespace.DatabaseName, collectionName), encryptedFields))
149+
{
150+
var dataKey = await CreateDataKeyAsync(kmsProvider, dataKeyOptions, cancellationToken).ConfigureAwait(false);
151+
EncryptedCollectionHelper.ModifyEncryptedFields(fieldDocument, dataKey);
152+
}
153+
154+
var effectiveCreateEncryptionOptions = createCollectionOptions.Clone();
155+
effectiveCreateEncryptionOptions.EncryptedFields = encryptedFields;
156+
await database.CreateCollectionAsync(collectionName, effectiveCreateEncryptionOptions, cancellationToken).ConfigureAwait(false);
157+
}
158+
catch (Exception ex)
159+
{
160+
throw new MongoEncryptionCreateCollectionException(ex, encryptedFields);
133161
}
134162

135-
var database = _libMongoCryptController.KeyVaultClient.GetDatabase(collectionNamespace.DatabaseNamespace.DatabaseName);
136-
137-
await database.CreateCollectionAsync(collectionNamespace.CollectionName, createCollectionOptions, cancellationToken).ConfigureAwait(false);
163+
return new CreateEncryptedCollectionResult(encryptedFields);
138164
}
139165

140166
/// <summary>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using MongoDB.Bson;
17+
18+
namespace MongoDB.Driver.Encryption
19+
{
20+
/// <summary>
21+
/// Represents the result of a create encrypted collection.
22+
/// </summary>
23+
public sealed class CreateEncryptedCollectionResult
24+
{
25+
private readonly BsonDocument _encryptedFields;
26+
27+
/// <summary>
28+
/// Initializes a new instance of the <see cref="CreateEncryptedCollectionResult"/> class.
29+
/// </summary>
30+
/// <param name="encryptedFields">The encrypted fields document.</param>
31+
public CreateEncryptedCollectionResult(BsonDocument encryptedFields) => _encryptedFields = encryptedFields;
32+
33+
/// <summary>
34+
/// The encrypted fields document.
35+
/// </summary>
36+
public BsonDocument EncryptedFields => _encryptedFields;
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Runtime.Serialization;
18+
using MongoDB.Bson;
19+
20+
namespace MongoDB.Driver.Encryption
21+
{
22+
/// <summary>
23+
/// Represents an encryption exception.
24+
/// </summary>
25+
[Serializable]
26+
public class MongoEncryptionCreateCollectionException : MongoEncryptionException
27+
{
28+
private readonly BsonDocument _encryptedFields;
29+
30+
/// <summary>
31+
/// Initializes a new instance of the <see cref="MongoEncryptionException"/> class.
32+
/// </summary>
33+
/// <param name="innerException">The inner exception.</param>
34+
/// <param name="encryptedFields">The encrypted fields.</param>
35+
public MongoEncryptionCreateCollectionException(Exception innerException, BsonDocument encryptedFields)
36+
: base(innerException)
37+
{
38+
_encryptedFields = encryptedFields;
39+
}
40+
41+
/// <summary>
42+
/// Initializes a new instance of the <see cref="MongoEncryptionCreateCollectionException"/> class (this overload used by deserialization).
43+
/// </summary>
44+
/// <param name="info">The SerializationInfo.</param>
45+
/// <param name="context">The StreamingContext.</param>
46+
protected MongoEncryptionCreateCollectionException(SerializationInfo info, StreamingContext context)
47+
: base(info, context)
48+
{
49+
_encryptedFields = (BsonDocument)info.GetValue(nameof(_encryptedFields), typeof(BsonDocument));
50+
}
51+
52+
/// <summary>
53+
/// The encrypted fields.
54+
/// </summary>
55+
public BsonDocument EncryptedFields => _encryptedFields;
56+
57+
// public methods
58+
/// <summary>
59+
/// Gets the object data.
60+
/// </summary>
61+
/// <param name="info">The information.</param>
62+
/// <param name="context">The context.</param>
63+
public override void GetObjectData(SerializationInfo info, StreamingContext context)
64+
{
65+
base.GetObjectData(info, context);
66+
info.AddValue(nameof(_encryptedFields), _encryptedFields);
67+
}
68+
}
69+
}

src/MongoDB.Driver/Encryption/MongoEncryptionException.cs

-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
using System;
1717
using System.Runtime.Serialization;
18-
using MongoDB.Driver.Core.Misc;
1918

2019
namespace MongoDB.Driver.Encryption
2120
{

tests/MongoDB.Bson.TestHelpers/BsonValueEquivalencyComparer.cs

+10-7
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* limitations under the License.
1414
*/
1515

16+
using System;
1617
using System.Collections.Generic;
1718

1819
namespace MongoDB.Bson.TestHelpers
@@ -22,15 +23,17 @@ public class BsonValueEquivalencyComparer : IEqualityComparer<BsonValue>
2223
#region static
2324
public static BsonValueEquivalencyComparer Instance { get; } = new BsonValueEquivalencyComparer();
2425

25-
public static bool Compare(BsonValue a, BsonValue b)
26+
public static bool Compare(BsonValue a, BsonValue b, Action<BsonValue, BsonValue> massageAction = null)
2627
{
28+
massageAction?.Invoke(a, b);
29+
2730
if (a.BsonType == BsonType.Document && b.BsonType == BsonType.Document)
2831
{
29-
return CompareDocuments((BsonDocument)a, (BsonDocument)b);
32+
return CompareDocuments((BsonDocument)a, (BsonDocument)b, massageAction);
3033
}
3134
else if (a.BsonType == BsonType.Array && b.BsonType == BsonType.Array)
3235
{
33-
return CompareArrays((BsonArray)a, (BsonArray)b);
36+
return CompareArrays((BsonArray)a, (BsonArray)b, massageAction);
3437
}
3538
else if (a.BsonType == b.BsonType)
3639
{
@@ -50,7 +53,7 @@ public static bool Compare(BsonValue a, BsonValue b)
5053
}
5154
}
5255

53-
private static bool CompareArrays(BsonArray a, BsonArray b)
56+
private static bool CompareArrays(BsonArray a, BsonArray b, Action<BsonValue, BsonValue> massageAction = null)
5457
{
5558
if (a.Count != b.Count)
5659
{
@@ -59,7 +62,7 @@ private static bool CompareArrays(BsonArray a, BsonArray b)
5962

6063
for (var i = 0; i < a.Count; i++)
6164
{
62-
if (!Compare(a[i], b[i]))
65+
if (!Compare(a[i], b[i], massageAction))
6366
{
6467
return false;
6568
}
@@ -68,7 +71,7 @@ private static bool CompareArrays(BsonArray a, BsonArray b)
6871
return true;
6972
}
7073

71-
private static bool CompareDocuments(BsonDocument a, BsonDocument b)
74+
private static bool CompareDocuments(BsonDocument a, BsonDocument b, Action<BsonValue, BsonValue> massageAction = null)
7275
{
7376
if (a.ElementCount != b.ElementCount)
7477
{
@@ -83,7 +86,7 @@ private static bool CompareDocuments(BsonDocument a, BsonDocument b)
8386
return false;
8487
}
8588

86-
if (!Compare(aElement.Value, bElement.Value))
89+
if (!Compare(aElement.Value, bElement.Value, massageAction))
8790
{
8891
return false;
8992
}

0 commit comments

Comments
 (0)