Skip to content

Commit 23a0786

Browse files
committed
CSHARP-2833: Support shorter SCRAM conversation
1 parent 36499d2 commit 23a0786

File tree

3 files changed

+126
-51
lines changed

3 files changed

+126
-51
lines changed

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ public async Task AuthenticateAsync(IConnection connection, ConnectionDescriptio
132132
/// <inheritdoc/>
133133
public BsonDocument CustomizeInitialIsMasterCommand(BsonDocument isMasterCommand)
134134
{
135-
return isMasterCommand;
135+
return isMasterCommand;
136136
}
137137

138138
private CommandWireProtocol<BsonDocument> CreateCommandProtocol(BsonDocument command)
@@ -163,12 +163,19 @@ private MongoAuthenticationException CreateException(IConnection connection, Exc
163163

164164
private BsonDocument CreateStartCommand(ISaslStep currentStep)
165165
{
166-
return new BsonDocument
166+
var startCommand = new BsonDocument
167167
{
168168
{ "saslStart", 1 },
169169
{ "mechanism", _mechanism.Name },
170170
{ "payload", currentStep.BytesToSendToServer }
171171
};
172+
173+
if (_mechanism.Name.StartsWith("SCRAM", StringComparison.OrdinalIgnoreCase))
174+
{
175+
startCommand.Add("options", new BsonDocument("skipEmptyExchange", true));
176+
}
177+
178+
return startCommand;
172179
}
173180

174181
private ISaslStep Transition(SaslConversation conversation, ISaslStep currentStep, BsonDocument result)
@@ -365,4 +372,4 @@ public ISaslStep Transition(SaslConversation conversation, byte[] bytesReceivedF
365372
}
366373
}
367374
}
368-
}
375+
}

tests/MongoDB.Driver.Core.Tests/Core/Authentication/ScramSha1AuthenticatorTests.cs

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
using Xunit;
2727
using MongoDB.Driver.Core.Connections;
2828
using MongoDB.Bson.TestHelpers.XunitExtensions;
29+
using System.Linq;
2930

3031
namespace MongoDB.Driver.Core.Authentication
3132
{
@@ -134,20 +135,32 @@ public void Authenticate_should_throw_when_server_provides_invalid_serverSignatu
134135
[Theory]
135136
[ParameterAttributeData]
136137
public void Authenticate_should_not_throw_when_authentication_succeeds(
138+
[Values(false, true)] bool useLongAuthentication,
137139
[Values(false, true)] bool async)
138140
{
139141
var randomStringGenerator = new ConstantRandomStringGenerator("fyko+d2lbbFgONRv9qkxdawL");
140142
var subject = new ScramSha1Authenticator(__credential, randomStringGenerator);
141143

142144
var saslStartReply = MessageHelper.BuildReply<RawBsonDocument>(
143145
RawBsonDocumentHelper.FromJson("{conversationId: 1, payload: BinData(0,\"cj1meWtvK2QybGJiRmdPTlJ2OXFreGRhd0xIbytWZ2s3cXZVT0tVd3VXTElXZzRsLzlTcmFHTUhFRSxzPXJROVpZM01udEJldVAzRTFURFZDNHc9PSxpPTEwMDAw\"), done: false, ok: 1}"));
144-
var saslContinueReply = MessageHelper.BuildReply<RawBsonDocument>(
145-
RawBsonDocumentHelper.FromJson("{conversationId: 1, payload: BinData(0,\"dj1VTVdlSTI1SkQxeU5ZWlJNcFo0Vkh2aFo5ZTA9\"), done: true, ok: 1}"));
146+
var saslContinueReply = MessageHelper.BuildReply<RawBsonDocument>(RawBsonDocumentHelper.FromJson(
147+
@"{conversationId: 1,
148+
payload: BinData(0,""dj1VTVdlSTI1SkQxeU5ZWlJNcFo0Vkh2aFo5ZTA9"")," +
149+
$" done: {new BsonBoolean(!useLongAuthentication)}, " +
150+
@" ok: 1}"));
151+
var saslLastStepReply = MessageHelper.BuildReply<RawBsonDocument>(RawBsonDocumentHelper.FromJson(
152+
@"{conversationId: 1," +
153+
$" payload: BinData(0,\"\")," +
154+
@" done: true,
155+
ok: 1}"));
146156

147157
var connection = new MockConnection(__serverId);
148158
connection.EnqueueReplyMessage(saslStartReply);
149159
connection.EnqueueReplyMessage(saslContinueReply);
150-
160+
if (useLongAuthentication)
161+
{
162+
connection.EnqueueReplyMessage(saslLastStepReply);
163+
}
151164
var expectedRequestId = RequestMessage.CurrentGlobalRequestId + 1;
152165

153166
Action act;
@@ -161,18 +174,42 @@ public void Authenticate_should_not_throw_when_authentication_succeeds(
161174
}
162175

163176
act.ShouldNotThrow();
164-
SpinWait.SpinUntil(() => connection.GetSentMessages().Count >= 2, TimeSpan.FromSeconds(5)).Should().BeTrue();
177+
var expectedSentMessageCount = useLongAuthentication ? 3 : 2;
178+
SpinWait.SpinUntil(
179+
() => connection.GetSentMessages().Count >= expectedSentMessageCount,
180+
TimeSpan.FromSeconds(5)
181+
).Should().BeTrue();
165182

166183
var sentMessages = MessageHelper.TranslateMessagesToBsonDocuments(connection.GetSentMessages());
167-
sentMessages.Count.Should().Be(2);
184+
sentMessages.Count.Should().Be(expectedSentMessageCount);
168185

169-
var actualRequestId0 = sentMessages[0]["requestId"].AsInt32;
170-
var actualRequestId1 = sentMessages[1]["requestId"].AsInt32;
171-
actualRequestId0.Should().BeInRange(expectedRequestId, expectedRequestId + 10);
172-
actualRequestId1.Should().BeInRange(actualRequestId0 + 1, actualRequestId0 + 11);
186+
var actualRequestIds = sentMessages.Select(m => m["requestId"].AsInt32).ToList();
187+
for (var i = 0; i != actualRequestIds.Count; ++i)
188+
{
189+
actualRequestIds[i].Should().BeInRange(expectedRequestId + i, expectedRequestId + 10 + i);
190+
}
173191

174-
sentMessages[0].Should().Be("{opcode: \"query\", requestId: " + actualRequestId0 + ", database: \"source\", collection: \"$cmd\", batchSize: -1, slaveOk: true, query: {saslStart: 1, mechanism: \"SCRAM-SHA-1\", payload: new BinData(0, \"biwsbj11c2VyLHI9ZnlrbytkMmxiYkZnT05Sdjlxa3hkYXdM\")}}");
175-
sentMessages[1].Should().Be("{opcode: \"query\", requestId: " + actualRequestId1 + ", database: \"source\", collection: \"$cmd\", batchSize: -1, slaveOk: true, query: {saslContinue: 1, conversationId: 1, payload: new BinData(0, \"Yz1iaXdzLHI9ZnlrbytkMmxiYkZnT05Sdjlxa3hkYXdMSG8rVmdrN3F2VU9LVXd1V0xJV2c0bC85U3JhR01IRUUscD1NQzJUOEJ2Ym1XUmNrRHc4b1dsNUlWZ2h3Q1k9\")}}");
192+
sentMessages[0].Should().Be(
193+
$"{{opcode: \"query\", " +
194+
$" requestId: {actualRequestIds[0]}, " +
195+
$" database: \"source\", collection: \"$cmd\", batchSize: -1, slaveOk: true, " +
196+
$" query: {{saslStart: 1, mechanism: \"SCRAM-SHA-1\", " +
197+
$" payload: new BinData(0, \"biwsbj11c2VyLHI9ZnlrbytkMmxiYkZnT05Sdjlxa3hkYXdM\")" +
198+
@" options: { skipEmptyExchange: true }}}");
199+
sentMessages[1].Should().Be("{opcode: \"query\", requestId: " + actualRequestIds[1] + ", database: \"source\", collection: \"$cmd\", batchSize: -1, slaveOk: true, query: {saslContinue: 1, conversationId: 1, payload: new BinData(0, \"Yz1iaXdzLHI9ZnlrbytkMmxiYkZnT05Sdjlxa3hkYXdMSG8rVmdrN3F2VU9LVXd1V0xJV2c0bC85U3JhR01IRUUscD1NQzJUOEJ2Ym1XUmNrRHc4b1dsNUlWZ2h3Q1k9\")}}");
200+
if (useLongAuthentication)
201+
{
202+
sentMessages[2].Should().Be(
203+
@"{opcode: ""query""," +
204+
$" requestId: {actualRequestIds[2]}," +
205+
@" database: ""source"",
206+
collection: ""$cmd"",
207+
batchSize: -1,
208+
slaveOk: true,
209+
query: {saslContinue: 1,
210+
conversationId: 1, " +
211+
$" payload: new BinData(0, \"\")}}}}");
212+
}
176213
}
177214

178215
[Theory]
@@ -189,7 +226,6 @@ public void Authenticate_should_use_cache(
189226
var saslContinueReply = MessageHelper.BuildReply<RawBsonDocument>(
190227
RawBsonDocumentHelper.FromJson(
191228
"{conversationId: 1, payload: BinData(0,\"dj1VTVdlSTI1SkQxeU5ZWlJNcFo0Vkh2aFo5ZTA9\"), done: true, ok: 1}"));
192-
193229
var connection = new MockConnection(__serverId);
194230
connection.EnqueueReplyMessage(saslStartReply);
195231
connection.EnqueueReplyMessage(saslContinueReply);

tests/MongoDB.Driver.Core.Tests/Core/Authentication/ScramSha256AuthenticatorTests.cs

Lines changed: 68 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
using Xunit;
2828
using MongoDB.Driver.Core.Connections;
2929
using MongoDB.Bson.TestHelpers.XunitExtensions;
30+
using System.Linq;
3031

3132
namespace MongoDB.Driver.Core.Authentication
3233
{
@@ -49,19 +50,21 @@ public class ScramSha256AuthenticatorTests
4950

5051
/*
5152
* This is a simple example of a SCRAM-SHA-256 authentication exchange. The username
52-
* 'user' and password 'pencil' are being used, with a client nonce of "rOprNGfwEbeRWgbNEkqO"
53+
* 'user' and password 'pencil' are being used, with a client nonce of "rOprNGfwEbeRWgbNEkqO"
5354
* C: n,,n=user,r=rOprNGfwEbeRWgbNEkqO
5455
* S: r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096
5556
* C: c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=
5657
* S: v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4=
5758
*/
5859

59-
private static readonly string _clientRequest1 = $"n,,n=user,r={_clientNonce}";
60-
private static readonly string _serverResponse1 =
60+
private static readonly string __clientRequest1 = $"n,,n=user,r={_clientNonce}";
61+
private static readonly string __serverResponse1 =
6162
$"r={_clientNonce}{_serverNonce},s={_serverSalt},i={_iterationCount}";
62-
private static readonly string _clientRequest2 =
63+
private static readonly string __clientRequest2 =
6364
$"c=biws,r={_clientNonce}{_serverNonce},p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=";
64-
private static readonly string _serverReponse2 = "v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4=";
65+
private static readonly string __serverResponse2 = "v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4=";
66+
private static readonly string __clientOptionalFinalRequest = "";
67+
private static readonly string __serverOptionalFinalResponse = "";
6568

6669
private static void Authenticate(
6770
ScramSha256Authenticator authenticator,
@@ -140,7 +143,7 @@ public void Authenticate_should_throw_when_server_provides_invalid_r_value(
140143
{
141144
var randomStringGenerator = new ConstantRandomStringGenerator(_clientNonce);
142145
var subject = new ScramSha256Authenticator(__credential, randomStringGenerator);
143-
var poisonedSaslStart = PoisonSaslMessage(message: _clientRequest1, poison: "bluePill");
146+
var poisonedSaslStart = PoisonSaslMessage(message: __clientRequest1, poison: "bluePill");
144147
var poisonedSaslStartReply = CreateSaslStartReply(poisonedSaslStart, _serverNonce, _serverSalt, _iterationCount);
145148
var poisonedSaslStartReplyMessage = MessageHelper.BuildReply(RawBsonDocumentHelper.FromJson(
146149
@"{conversationId: 1, " +
@@ -168,17 +171,17 @@ public void Authenticate_should_throw_when_server_provides_invalid_serverSignatu
168171
var randomStringGenerator = new ConstantRandomStringGenerator(_clientNonce);
169172
var subject = new ScramSha256Authenticator(__credential, randomStringGenerator);
170173

171-
var saslStartReply = CreateSaslStartReply(_clientRequest1, _serverNonce, _serverSalt, _iterationCount);
172-
var poisonedSaslContinueReply = PoisonSaslMessage(message: _serverReponse2, poison: "redApple");
174+
var saslStartReply = CreateSaslStartReply(__clientRequest1, _serverNonce, _serverSalt, _iterationCount);
175+
var poisonedSaslContinueReply = PoisonSaslMessage(message: __serverResponse2, poison: "redApple");
173176
var saslStartReplyMessage = MessageHelper.BuildReply(RawBsonDocumentHelper.FromJson(
174177
@"{conversationId: 1, " +
175178
$" payload: BinData(0,\"{ToUtf8Base64(saslStartReply)}\")," +
176-
@" done: false,
179+
@" done: false,
177180
ok: 1}"));
178181
var poisonedSaslContinueReplyMessage = MessageHelper.BuildReply(RawBsonDocumentHelper.FromJson(
179182
@"{conversationId: 1, " +
180183
$" payload: BinData(0,\"{ToUtf8Base64(poisonedSaslContinueReply)}\")," +
181-
@" done: true,
184+
@" done: true,
182185
ok: 1}"));
183186

184187
var connection = new MockConnection(__serverId);
@@ -203,25 +206,35 @@ public void Authenticate_should_throw_when_server_provides_invalid_serverSignatu
203206
[Theory]
204207
[ParameterAttributeData]
205208
public void Authenticate_should_not_throw_when_authentication_succeeds(
209+
[Values(false, true)] bool useLongAuthentication,
206210
[Values(false, true)] bool async)
207211
{
208212
var randomStringGenerator = new ConstantRandomStringGenerator(_clientNonce);
209213
var subject = new ScramSha256Authenticator(__credential, randomStringGenerator);
210214

211215
var saslStartReply = MessageHelper.BuildReply<RawBsonDocument>(RawBsonDocumentHelper.FromJson(
212216
@"{conversationId: 1," +
213-
$" payload: BinData(0,\"{ToUtf8Base64(_serverResponse1)}\")," +
214-
@" done: false,
217+
$" payload: BinData(0,\"{ToUtf8Base64(__serverResponse1)}\")," +
218+
@" done: false,
215219
ok: 1}"));
216220
var saslContinueReply = MessageHelper.BuildReply<RawBsonDocument>(RawBsonDocumentHelper.FromJson(
217221
@"{conversationId: 1," +
218-
$" payload: BinData(0,\"{ToUtf8Base64(_serverReponse2)}\")," +
219-
@" done: true,
222+
$" payload: BinData(0,\"{ToUtf8Base64(__serverResponse2)}\")," +
223+
$" done: {new BsonBoolean(!useLongAuthentication)}," +
224+
@" ok: 1}"));
225+
var saslLastStepReply = MessageHelper.BuildReply<RawBsonDocument>(RawBsonDocumentHelper.FromJson(
226+
@"{conversationId: 1," +
227+
$" payload: BinData(0,\"{ToUtf8Base64(__serverOptionalFinalResponse)}\")," +
228+
@" done: true,
220229
ok: 1}"));
221230

222231
var connection = new MockConnection(__serverId);
223232
connection.EnqueueReplyMessage(saslStartReply);
224233
connection.EnqueueReplyMessage(saslContinueReply);
234+
if (useLongAuthentication)
235+
{
236+
connection.EnqueueReplyMessage(saslLastStepReply);
237+
}
225238

226239
var expectedRequestId = RequestMessage.CurrentGlobalRequestId + 1;
227240

@@ -237,36 +250,55 @@ public void Authenticate_should_not_throw_when_authentication_succeeds(
237250

238251
var exception = Record.Exception(act);
239252
exception.Should().BeNull();
240-
SpinWait.SpinUntil(() => connection.GetSentMessages().Count >= 2, TimeSpan.FromSeconds(5)).Should().BeTrue();
253+
var expectedSentMessageCount = useLongAuthentication ? 3 : 2;
254+
SpinWait.SpinUntil(
255+
() => connection.GetSentMessages().Count >= expectedSentMessageCount,
256+
TimeSpan.FromSeconds(5)
257+
).Should().BeTrue();
241258

242259
var sentMessages = MessageHelper.TranslateMessagesToBsonDocuments(connection.GetSentMessages());
243-
sentMessages.Count.Should().Be(2);
260+
sentMessages.Count.Should().Be(expectedSentMessageCount);
244261

245-
var actualRequestId0 = sentMessages[0]["requestId"].AsInt32;
246-
var actualRequestId1 = sentMessages[1]["requestId"].AsInt32;
247-
actualRequestId0.Should().BeInRange(expectedRequestId, expectedRequestId + 10);
248-
actualRequestId1.Should().BeInRange(actualRequestId0 + 1, actualRequestId0 + 11);
262+
var actualRequestIds = sentMessages.Select(m => m["requestId"].AsInt32).ToList();
263+
for (var i = 0; i != actualRequestIds.Count; ++i)
264+
{
265+
actualRequestIds[i].Should().BeInRange(expectedRequestId + i, expectedRequestId + 10 + i);
266+
}
249267

250268
sentMessages[0].Should().Be(
251269
@"{opcode: ""query""," +
252-
$" requestId: {actualRequestId0}," +
253-
@" database: ""source"",
254-
collection: ""$cmd"",
255-
batchSize: -1,
256-
slaveOk: true,
257-
query: {saslStart: 1,
270+
$" requestId: {actualRequestIds[0]}," +
271+
@" database: ""source"",
272+
collection: ""$cmd"",
273+
batchSize: -1,
274+
slaveOk: true,
275+
query: {saslStart: 1,
258276
mechanism: ""SCRAM-SHA-256""," +
259-
$" payload: new BinData(0, \"{ToUtf8Base64(_clientRequest1)}\")}}}}");
277+
$" payload: new BinData(0, \"{ToUtf8Base64(__clientRequest1)}\")" +
278+
@" options: { skipEmptyExchange: true }}}");
260279
sentMessages[1].Should().Be(
261280
@"{opcode: ""query""," +
262-
$" requestId: {actualRequestId1}," +
263-
@" database: ""source"",
264-
collection: ""$cmd"",
265-
batchSize: -1,
266-
slaveOk: true,
267-
query: {saslContinue: 1,
281+
$" requestId: {actualRequestIds[1]}," +
282+
@" database: ""source"",
283+
collection: ""$cmd"",
284+
batchSize: -1,
285+
slaveOk: true,
286+
query: {saslContinue: 1,
268287
conversationId: 1, " +
269-
$" payload: new BinData(0, \"{ToUtf8Base64(_clientRequest2)}\")}}}}");
288+
$" payload: new BinData(0, \"{ToUtf8Base64(__clientRequest2)}\")}}}}");
289+
if (useLongAuthentication)
290+
{
291+
sentMessages[2].Should().Be(
292+
@"{opcode: ""query""," +
293+
$" requestId: {actualRequestIds[2]}," +
294+
@" database: ""source"",
295+
collection: ""$cmd"",
296+
batchSize: -1,
297+
slaveOk: true,
298+
query: {saslContinue: 1,
299+
conversationId: 1, " +
300+
$" payload: new BinData(0, \"{ToUtf8Base64(__clientOptionalFinalRequest)}\")}}}}");
301+
}
270302
}
271303

272304
[Theory]
@@ -279,12 +311,12 @@ public void Authenticate_should_use_cache(
279311

280312
var saslStartReply = MessageHelper.BuildReply<RawBsonDocument>(RawBsonDocumentHelper.FromJson(
281313
@"{conversationId: 1," +
282-
$" payload: BinData(0,\"{ToUtf8Base64(_serverResponse1)}\")," +
314+
$" payload: BinData(0,\"{ToUtf8Base64(__serverResponse1)}\")," +
283315
@" done: false,
284316
ok: 1}"));
285317
var saslContinueReply = MessageHelper.BuildReply<RawBsonDocument>(RawBsonDocumentHelper.FromJson(
286318
@"{conversationId: 1," +
287-
$" payload: BinData(0,\"{ToUtf8Base64(_serverReponse2)}\")," +
319+
$" payload: BinData(0,\"{ToUtf8Base64(__serverResponse2)}\")," +
288320
@" done: true,
289321
ok: 1}"));
290322

0 commit comments

Comments
 (0)