Skip to content

Commit 0493aec

Browse files
committed
CSHARP-3033: Support speculative MONGODB-X509 authentication attempts in isMaster
1 parent 2143350 commit 0493aec

File tree

3 files changed

+47
-13
lines changed

3 files changed

+47
-13
lines changed

src/MongoDB.Driver.Core/Core/Authentication/MongoDBX509Authenticator.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ public void Authenticate(IConnection connection, ConnectionDescription descripti
6969
Ensure.IsNotNull(description, nameof(description));
7070
EnsureUsernameIsNotNullOrNullIsSupported(connection, description);
7171

72+
if (description.IsMasterResult.SpeculativeAuthenticate != null)
73+
{
74+
return;
75+
}
76+
7277
try
7378
{
7479
var protocol = CreateAuthenticateProtocol();
@@ -87,6 +92,11 @@ public async Task AuthenticateAsync(IConnection connection, ConnectionDescriptio
8792
Ensure.IsNotNull(description, nameof(description));
8893
EnsureUsernameIsNotNullOrNullIsSupported(connection, description);
8994

95+
if (description.IsMasterResult.SpeculativeAuthenticate != null)
96+
{
97+
return;
98+
}
99+
90100
try
91101
{
92102
var protocol = CreateAuthenticateProtocol();
@@ -101,18 +111,25 @@ public async Task AuthenticateAsync(IConnection connection, ConnectionDescriptio
101111
/// <inheritdoc/>
102112
public BsonDocument CustomizeInitialIsMasterCommand(BsonDocument isMasterCommand)
103113
{
114+
isMasterCommand.Add("speculativeAuthenticate", CreateAuthenticateCommand());
104115
return isMasterCommand;
105116
}
106117

107118
// private methods
108-
private CommandWireProtocol<BsonDocument> CreateAuthenticateProtocol()
119+
120+
private BsonDocument CreateAuthenticateCommand()
109121
{
110-
var command = new BsonDocument
122+
return new BsonDocument
111123
{
112124
{ "authenticate", 1 },
113125
{ "mechanism", Name },
114126
{ "user", _username, _username != null }
115127
};
128+
}
129+
130+
private CommandWireProtocol<BsonDocument> CreateAuthenticateProtocol()
131+
{
132+
var command = CreateAuthenticateCommand();
116133

117134
var protocol = new CommandWireProtocol<BsonDocument>(
118135
new DatabaseNamespace("$external"),

src/MongoDB.Driver.Core/Core/Misc/Feature.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ public class Feature
8989
private static readonly Feature __serverReturnsResumableChangeStreamErrorLabel = new Feature("ServerReturnsResumableChangeStreamErrorLabel", new SemanticVersion(4, 3, 0));
9090
private static readonly Feature __serverReturnsRetryableWriteErrorLabel = new Feature("ServerReturnsRetryableWriteErrorLabel", new SemanticVersion(4, 3, 0));
9191
private static readonly Feature __shardedTransactions = new Feature("ShardedTransactions", new SemanticVersion(4, 1, 6));
92+
private static readonly Feature __speculativeAuthentication = new Feature("SpeculativeAuthentication", new SemanticVersion(4, 4, 0, "rc0"));
9293
private static readonly Feature __tailableCursor = new Feature("TailableCursor", new SemanticVersion(3, 2, 0));
9394
private static readonly Feature __transactions = new Feature("Transactions", new SemanticVersion(4, 0, 0));
9495
private static readonly Feature __userManagementCommands = new Feature("UserManagementCommands", new SemanticVersion(2, 6, 0));
@@ -426,6 +427,11 @@ public class Feature
426427
/// </summary>
427428
public static Feature ShardedTransactions => __shardedTransactions;
428429

430+
/// <summary>
431+
/// Gets the speculative authentication feature.
432+
/// </summary>
433+
public static Feature SpeculativeAuthentication => __speculativeAuthentication;
434+
429435
/// <summary>
430436
/// Gets the tailable cursor feature.
431437
/// </summary>

tests/MongoDB.Driver.Tests/AuthenticationTests.cs

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,15 @@
1616
using System;
1717
using System.Collections.Generic;
1818
using System.Linq;
19-
using System.Runtime.InteropServices;
2019
using System.Security.Cryptography.X509Certificates;
2120
using System.Threading;
22-
using System.Threading.Tasks;
2321
using FluentAssertions;
2422
using MongoDB.Bson;
25-
using MongoDB.Bson.Serialization.Serializers;
2623
using MongoDB.Bson.TestHelpers.XunitExtensions;
24+
using MongoDB.Driver.Core.Clusters.ServerSelectors;
2725
using MongoDB.Driver.Core.Misc;
28-
using MongoDB.Driver.Core.Operations;
2926
using MongoDB.Driver.Core.TestHelpers.XunitExtensions;
3027
using MongoDB.Driver.TestHelpers;
31-
using Moq;
3228
using Xunit;
3329

3430
namespace MongoDB.Driver.Tests
@@ -144,7 +140,7 @@ public void Authentication_succeeds_when_user_has_Scram_Sha_1_Mechanism_and_mech
144140
settings.Credential = MongoCredential
145141
.FromComponents(mechanism: null, source: null, username: userName, password: password);
146142

147-
AssertAuthenticationSucceeds(settings, async);
143+
AssertAuthenticationSucceeds(settings, async, speculativeAuthenticatationShouldSucceedIfPossible: false);
148144
}
149145

150146
[SkippableTheory]
@@ -352,10 +348,13 @@ public void Authentication_succeeds_with_MONGODB_X509_mechanism(
352348
settings.SslSettings = settings.SslSettings.Clone();
353349
settings.SslSettings.ClientCertificates = new[] { clientCertificate };
354350

355-
AssertAuthenticationSucceeds(settings, async);
351+
AssertAuthenticationSucceeds(settings, async, speculativeAuthenticatationShouldSucceedIfPossible: true);
356352
}
357353

358-
private void AssertAuthenticationSucceeds(MongoClientSettings settings, bool async)
354+
private void AssertAuthenticationSucceeds(
355+
MongoClientSettings settings,
356+
bool async,
357+
bool speculativeAuthenticatationShouldSucceedIfPossible = true)
359358
{
360359
// If we don't use a DisposableClient, the second run of AuthenticationSucceedsWithMongoDB_X509_mechanism
361360
// will fail because the backing Cluster's connections will be associated with a dropped user
@@ -371,6 +370,17 @@ private void AssertAuthenticationSucceeds(MongoClientSettings settings, bool asy
371370
{
372371
_ = client.ListDatabaseNames().ToList();
373372
}
373+
if (Feature.SpeculativeAuthentication.IsSupported(CoreTestConfiguration.ServerVersion) &&
374+
speculativeAuthenticatationShouldSucceedIfPossible &&
375+
Driver.CoreTestConfiguration.Cluster.Description.Type != Core.Clusters.ClusterType.Sharded) // Until https://jira.mongodb.org/browse/SERVER-47908 is resolved
376+
{
377+
var cancellationToken = CancellationToken.None;
378+
var serverSelector = new ReadPreferenceServerSelector(settings.ReadPreference);
379+
var server = client.Cluster.SelectServer(serverSelector, cancellationToken);
380+
var channel = server.GetChannel(cancellationToken);
381+
var isMasterResult = channel.ConnectionDescription.IsMasterResult;
382+
isMasterResult.SpeculativeAuthenticate.Should().NotBeNull();
383+
}
374384
}
375385
}
376386

@@ -453,9 +463,10 @@ private void DropDatabaseUser(MongoClient client, string database, string userNa
453463
private string GetRfc2253FormattedUsernameFromX509ClientCertificate(X509Certificate2 certificate)
454464
{
455465
var distinguishedName = certificate.SubjectName.Name;
456-
// Authentication will fail if we don't remove the spaces, even if we add the username WITH the spaces.
457-
var nameWithNoSpaces = string.Join(",", distinguishedName.Split(',').Select(s => s.Trim()));
458-
return nameWithNoSpaces.Replace("S=", "ST=");
466+
// Authentication will fail if we don't remove the delimiting spaces, even if we add the username WITH the
467+
// delimiting spaces.
468+
var nameWithoutDelimitingSpaces = string.Join(",", distinguishedName.Split(',').Select(s => s.Trim()));
469+
return nameWithoutDelimitingSpaces.Replace("S=", "ST=");
459470
}
460471
}
461472
}

0 commit comments

Comments
 (0)