Skip to content

CSHARP-4149: Add FLE 2 behavior for CreateCollection() and Collection.Drop(). #790

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
May 12, 2022

Conversation

DmitryLukyanov
Copy link
Contributor

No description provided.

@@ -19,7 +19,9 @@
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Serializers;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no real changes here, I just removed original CreateIndexesOperation and renamed CreateIndexesUsingCommandOperation to CreateIndexesOperation. This change was missed in one of the previous tickets and it a bit affected tests I wrote in this ticket

});
}

private async Task<IWriteOperation<BsonDocument>> CreateDropCollectionOperationAsync(string name, DropCollectionOptions options, IClientSessionHandle session, CancellationToken cancellationToken)
Copy link
Contributor Author

@DmitryLukyanov DmitryLukyanov May 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's unfortunate that creating collection step requires calling a server, so now we need to have 2 versions of this method: sync and async

@@ -824,7 +908,7 @@ private MessageEncoderSettings GetMessageEncoderSettings()
#pragma warning disable 618
if (BsonDefaults.GuidRepresentationMode == GuidRepresentationMode.V2)
{
messageEncoderSettings.Add(MessageEncoderSettingsName.GuidRepresentation, _settings.GuidRepresentation);
messageEncoderSettings.Add(MessageEncoderSettingsName.GuidRepresentation, withEncryption ? GuidRepresentation.Unspecified : _settings.GuidRepresentation);
Copy link
Contributor Author

@DmitryLukyanov DmitryLukyanov May 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rstam can you please take a look at this change? In 2 words, since create/drop collection with FLE happens without libmongocrypt (ie LibMongoCryptController) call, but server still expects subType=4 in the provided FLE configuration (encryptedFields), we have to configure correct guidRepresentation manually. In this case we rely on what was provided in the schema

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need a withEncryption parameter at all, you can just do:

messageEncoderSettings.Add(MessageEncoderSettingsName.GuidRepresentation, GuidRepresentation.Unspecified);

whether encryption is configured or not.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And it is correct to only do this when GuidRepresentationMode is V2.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

discussed offline

@DmitryLukyanov DmitryLukyanov requested a review from JamesKovacs May 5, 2022 15:24
@JamesKovacs JamesKovacs requested review from BorisDog and removed request for JamesKovacs May 5, 2022 19:04
Copy link
Contributor

@BorisDog BorisDog left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly LGTM, some minor comments.

@DmitryLukyanov DmitryLukyanov requested a review from BorisDog May 9, 2022 19:46
Copy link
Contributor

@BorisDog BorisDog left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, two minor comments for you considerations.

{
if (autoEncryptionOptions != null)
{
var listCollectionOptions = new ListCollectionsOptions() { Filter = $"{{ name : '{collectionNamespace.CollectionName}' }}" };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: I'd combine the two conditions into single statement.

var subject = new CompositeWriteOperation<BsonDocument>((healthyOperation1.Object, IsMainOperation: false), (faultyOperation2.Object, IsMainOperation: false), (healthyOperation3.Object, IsMainOperation: true));

var resultedException = async
? Record.Exception(() => subject.ExecuteAsync(Mock.Of<IWriteBinding>(), CancellationToken.None).GetAwaiter().GetResult())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure that it's crucial to change all the old tests. But lots of new new tests are definitely using the usual TPL oriented approach.

#pragma warning restore
var collectionNamespace = new CollectionNamespace(_databaseNamespace, name);

_ = EncryptedCollectionHelper.TryGetEffectiveEncryptedFields(collectionNamespace, options.EncryptedFields, _client.Settings?.AutoEncryptionOptions?.EncryptedFieldsMap, out var effectiveEncryptedFields);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can you ignore the return value? What if TryGetEffectiveEncryptedFields returns false?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This "looks" like a bug. Consider a new helper method instead:

var effectiveEncryptedFields = EncryptedCollectionHelper.GetEffectiveEncryptedFields(collectionNamespace, options.EncryptedFields, _client.Settings?.AutoEncryptionOptions?.EncryptedFieldsMap);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new helper method could be:

public static BsonDocument GetEffectiveEncryptedFields(CollectionNamespace collectionNamespace, BsonDocument encryptedFields, IReadOnlyDictionary<string, BsonDocument> encryptedFieldsMap)
{
    if (TryGetEffectiveEncryptedFields(collectionNamespace, encryptedFields, encryptedFieldsMap, out var effectiveEncryptedFields))
    {
        return effectiveEncryptedFields;
    }
    else
    {
        return null;
    }
}

I realize there's a bit of redundancy in this proposed helper method but it's very clear and doesn't rely on any side effects of the TryGetEffectiveEncryptedFields method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

var subject = new CompositeWriteOperation<BsonDocument>((healthyOperation1.Object, IsMainOperation: false), (faultyOperation2.Object, IsMainOperation: false), (healthyOperation3.Object, IsMainOperation: true));

var resultedException = async
? Record.Exception(() => subject.ExecuteAsync(Mock.Of<IWriteBinding>(), CancellationToken.None).GetAwaiter().GetResult())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the only reason OLD tests don't return Task is that when they were written xUnit did not yet support that.

And there was probably some period of time between when xUnit started supporting async tests and when we became aware of it and started using that.

I think it's fine to use async tests going forward. No need to change old tests. Perhaps if we work on on old tests for some reason we can change them little by little.

@@ -291,28 +292,48 @@ public override Task CreateCollectionAsync(IClientSessionHandle session, string

public override void DropCollection(string name, CancellationToken cancellationToken)
{
UsingImplicitSession(session => DropCollection(session, name, cancellationToken), cancellationToken);
UsingImplicitSession(session => DropCollection(session, name, options: null, cancellationToken), cancellationToken);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would avoid duplicating even small amounts of logic. Simply provide the default value for the missing parameter and delegate to the next method:

DropCollection(name, options: null, cancellationToken);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

ExecuteWriteOperation(session, operation, cancellationToken);
}

public override Task DropCollectionAsync(string name, CancellationToken cancellationToken)
{
return UsingImplicitSessionAsync(session => DropCollectionAsync(session, name, cancellationToken), cancellationToken);
return UsingImplicitSessionAsync(session => DropCollectionAsync(session, name, options: null, cancellationToken), cancellationToken);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, avoid duplicating even the smallest amount of logic:

return DropCollectionAsync(name, options: null, cancellationToken);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -824,7 +908,7 @@ private MessageEncoderSettings GetMessageEncoderSettings()
#pragma warning disable 618
if (BsonDefaults.GuidRepresentationMode == GuidRepresentationMode.V2)
{
messageEncoderSettings.Add(MessageEncoderSettingsName.GuidRepresentation, _settings.GuidRepresentation);
messageEncoderSettings.Add(MessageEncoderSettingsName.GuidRepresentation, withEncryption ? GuidRepresentation.Unspecified : _settings.GuidRepresentation);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need a withEncryption parameter at all, you can just do:

messageEncoderSettings.Add(MessageEncoderSettingsName.GuidRepresentation, GuidRepresentation.Unspecified);

whether encryption is configured or not.

}

return true;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found the logic of the above method somewhat confusing and it took me a long time to convince myself that it could be correct. I think the following refactoring expresses the intent more clearly:

public static bool TryGetEffectiveEncryptedFields(CollectionNamespace collectionNamespace, BsonDocument encryptedFields, IReadOnlyDictionary<string, BsonDocument> encryptedFieldsMap, out BsonDocument effectiveEncryptedFields)
{
    if (encryptedFields != null)
    {
        effectiveEncryptedFields = encryptedFields;
        return true;
    }

    if (encryptedFieldsMap != null)
    {
        return encryptedFieldsMap.TryGetValue(collectionNamespace.ToString(), out effectiveEncryptedFields);
    }

    effectiveEncryptedFields = null;
    return false;
}

What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

(CreateInnerCollectionOperation(EncryptedCollectionHelper.GetAdditionalCollectionName(encryptedFields, collectionNamespace, HelperCollectionForEncryption.Esc)), IsMainOperation: false),
(CreateInnerCollectionOperation(EncryptedCollectionHelper.GetAdditionalCollectionName(encryptedFields, collectionNamespace, HelperCollectionForEncryption.Ecc)), IsMainOperation: false),
(CreateInnerCollectionOperation(EncryptedCollectionHelper.GetAdditionalCollectionName(encryptedFields, collectionNamespace, HelperCollectionForEncryption.Ecos)), IsMainOperation: false),
(mainOperation, true),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion:

(mainOperation, IsMainOperation: true),

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -824,7 +908,7 @@ private MessageEncoderSettings GetMessageEncoderSettings()
#pragma warning disable 618
if (BsonDefaults.GuidRepresentationMode == GuidRepresentationMode.V2)
{
messageEncoderSettings.Add(MessageEncoderSettingsName.GuidRepresentation, _settings.GuidRepresentation);
messageEncoderSettings.Add(MessageEncoderSettingsName.GuidRepresentation, withEncryption ? GuidRepresentation.Unspecified : _settings.GuidRepresentation);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And it is correct to only do this when GuidRepresentationMode is V2.

@DmitryLukyanov DmitryLukyanov requested a review from rstam May 11, 2022 21:31
Copy link
Contributor

@rstam rstam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@DmitryLukyanov DmitryLukyanov merged commit c7c1510 into mongodb:master May 12, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants