Skip to content

Commit 8a453e0

Browse files
committed
CSHARP-3228 / CSHARP-3239: Avoid double-escaping connection string options when merging in options from a TXT record.
1 parent 924829a commit 8a453e0

File tree

3 files changed

+58
-18
lines changed

3 files changed

+58
-18
lines changed

src/MongoDB.Driver.Core/Core/Configuration/ConnectionString.cs

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -556,9 +556,7 @@ public string GetOption(string name)
556556
}
557557

558558
var txtRecords = _dnsResolver.ResolveTxtRecords(host, cancellationToken);
559-
var options = GetOptionsFromTxtRecords(txtRecords);
560-
561-
var resolvedOptions = GetResolvedOptions(options);
559+
var resolvedOptions = GetResolvedOptionsFromTxtRecords(txtRecords);
562560

563561
return BuildResolvedConnectionString(resolvedScheme, hosts, resolvedOptions);
564562
}
@@ -606,9 +604,7 @@ public string GetOption(string name)
606604
}
607605

608606
var txtRecords = await _dnsResolver.ResolveTxtRecordsAsync(host, cancellationToken).ConfigureAwait(false);
609-
var options = GetOptionsFromTxtRecords(txtRecords);
610-
611-
var resolvedOptions = GetResolvedOptions(options);
607+
var resolvedOptions = GetResolvedOptionsFromTxtRecords(txtRecords);
612608

613609
return BuildResolvedConnectionString(resolvedScheme, hosts, resolvedOptions);
614610
}
@@ -655,9 +651,9 @@ private ConnectionString BuildResolvedConnectionString(ConnectionStringScheme re
655651
mergedOptions.AddRange(
656652
resolvedOptions
657653
.AllKeys
658-
.SelectMany(x => resolvedOptions
659-
.GetValues(x)
660-
.Select(y => $"{x}={Uri.EscapeDataString(y)}")));
654+
.SelectMany(key => resolvedOptions
655+
.GetValues(key)
656+
.Select(value => $"{key}={Uri.EscapeDataString(value)}")));
661657

662658
if (mergedOptions.Count > 0)
663659
{
@@ -732,8 +728,10 @@ private void ExtractOptions(Match match)
732728
foreach (Capture option in match.Groups["option"].Captures)
733729
{
734730
var parts = option.Value.Split('=');
735-
_allOptions.Add(parts[0], parts[1]);
736-
ParseOption(parts[0].Trim(), Uri.UnescapeDataString(parts[1].Trim()));
731+
var name = parts[0].Trim();
732+
var value = Uri.UnescapeDataString(parts[1].Trim());
733+
_allOptions.Add(name, value);
734+
ParseOption(name, value);
737735
}
738736
}
739737

@@ -1254,23 +1252,20 @@ private List<string> GetHostsFromSrvRecords(IEnumerable<SrvRecord> srvRecords)
12541252
return hosts;
12551253
}
12561254

1257-
private List<string> GetOptionsFromTxtRecords(List<TxtRecord> txtRecords)
1255+
private NameValueCollection GetResolvedOptionsFromTxtRecords(IReadOnlyCollection<TxtRecord> txtRecords)
12581256
{
12591257
if (txtRecords.Count > 1)
12601258
{
12611259
throw new MongoConfigurationException("Only 1 TXT record is allowed when using the SRV protocol.");
12621260
}
12631261

1264-
return txtRecords.Select(tr => tr.Strings.Aggregate("", (acc, s) => acc + Uri.UnescapeDataString(s))).ToList();
1265-
}
1262+
var txtRecord = txtRecords.FirstOrDefault();
12661263

1267-
private NameValueCollection GetResolvedOptions(List<string> options)
1268-
{
12691264
// Build a dummy connection string in order to parse the options
12701265
var dummyConnectionString = "mongodb://localhost/";
1271-
if (options.Count > 0)
1266+
if (txtRecord != null)
12721267
{
1273-
dummyConnectionString += "?" + string.Join("&", options);
1268+
dummyConnectionString += "?" + string.Join("", txtRecord.Strings);
12741269
}
12751270
var dnsConnectionString = new ConnectionString(dummyConnectionString);
12761271
ValidateResolvedOptions(dnsConnectionString.AllOptionNames);

tests/MongoDB.Driver.Core.Tests/Core/Configuration/ConnectionStringTests.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
using FluentAssertions;
2323
using MongoDB.Bson;
2424
using MongoDB.Bson.TestHelpers;
25+
using MongoDB.Bson.TestHelpers.XunitExtensions;
2526
using MongoDB.Driver.Core.Clusters;
2627
using MongoDB.Driver.Core.Compression;
2728
using Xunit;
@@ -66,6 +67,9 @@ public void constructor_should_throw_when_isResolved_is_invalid()
6667
[InlineData("mongodb://test5.test.build.10gen.cc", "mongodb://test5.test.build.10gen.cc", true)]
6768
[InlineData("mongodb+srv://test5.test.build.10gen.cc", "mongodb://localhost.test.build.10gen.cc:27017/?replicaSet=repl0&authSource=thisDB&tls=true", false)]
6869
[InlineData("mongodb+srv://test5.test.build.10gen.cc", "mongodb://localhost.test.build.10gen.cc:27017/?replicaSet=repl0&authSource=thisDB&tls=true", true)]
70+
[InlineData("mongodb+srv://test5.test.build.10gen.cc/?authSource=$external", "mongodb://localhost.test.build.10gen.cc:27017/?replicaSet=repl0&authSource=%24external&tls=true", false)]
71+
[InlineData("mongodb+srv://test5.test.build.10gen.cc/?authSource=%24external", "mongodb://localhost.test.build.10gen.cc:27017/?replicaSet=repl0&authSource=%24external&tls=true", false)]
72+
[InlineData("mongodb+srv://test5.test.build.10gen.cc/?authSource=$external&authMechanism=MONGODB-AWS", "mongodb://localhost.test.build.10gen.cc:27017/?replicaSet=repl0&authSource=%24external&authMechanism=MONGODB-AWS&tls=true", false)]
6973
public void Resolve_should_return_expected_result(string connectionString, string expectedResult, bool async)
7074
{
7175
var subject = new ConnectionString(connectionString);
@@ -84,6 +88,26 @@ public void Resolve_should_return_expected_result(string connectionString, strin
8488
result.ToString().Should().Be(expectedResult);
8589
}
8690

91+
[Theory]
92+
[ParameterAttributeData]
93+
public void Resolve_against_mongodb_aws_session_token_should_return_the_expected_aws_session_token([Values(false, true)] bool escapeToken)
94+
{
95+
const string authMechanism = "MONGODB-AWS";
96+
const string username = "AKIAIOSFODNN7EXAMPLE";
97+
const string password = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY";
98+
const string awsSessionToken = "AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT+FvwqnKwRcOIfrRh3c/LTo6UDdyJwOOvEVPvLXCrrrUtdnniCEXAMPLE/IvU1dYUg2RVAJBanLiHb4IgRmpRV3zrkuWJOgQs8IZZaIv2BXIa2R4OlgkBN9bkUDNCJiBeb/AXlzBBko7b15fjrBs2+cTQtpZ3CYWFXG8C5zqx37wnOE49mRl/+OtkIKGO7fAE";
99+
100+
var preparedToken = escapeToken ? Uri.EscapeDataString(awsSessionToken) : awsSessionToken;
101+
var uri = $"mongodb+srv://{username}:{Uri.EscapeDataString(password)}@test5.test.build.10gen.cc/test?authSource=$external&authMechanism={authMechanism}&authMechanismProperties=AWS_SESSION_TOKEN:{preparedToken}";
102+
var connectionString = new ConnectionString(uri);
103+
104+
var result = connectionString.Resolve();
105+
106+
result.AuthMechanism.Should().Be(authMechanism);
107+
result.Username.Should().Be(username);
108+
result.AuthMechanismProperties["AWS_SESSION_TOKEN"].Should().Be(awsSessionToken);
109+
}
110+
87111
[Theory]
88112
[InlineData("mongodb://test5.test.build.10gen.cc", false, "mongodb://test5.test.build.10gen.cc", false)]
89113
[InlineData("mongodb://test5.test.build.10gen.cc", false, "mongodb://test5.test.build.10gen.cc", true)]

tests/MongoDB.Driver.Tests/MongoClientSettingsTests.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using System.Threading;
2121
using FluentAssertions;
2222
using MongoDB.Bson;
23+
using MongoDB.Bson.TestHelpers.XunitExtensions;
2324
using MongoDB.Driver.Core.Clusters;
2425
using MongoDB.Driver.Core.Compression;
2526
using MongoDB.Driver.Core.Configuration;
@@ -560,6 +561,26 @@ public void TestFromUrlResolving(string connectionString, ConnectionStringScheme
560561
result.Scheme.Should().Be(expectedScheme);
561562
}
562563

564+
[Theory]
565+
[ParameterAttributeData]
566+
public void TestFromUrlWithMongoDBAWS_should_parse_credentials_correctly([Values(false, true)] bool escapeToken)
567+
{
568+
const string authMechanism = "MONGODB-AWS";
569+
const string username = "AKIAIOSFODNN7EXAMPLE";
570+
const string password = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY";
571+
const string awsSessionToken = "AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT+FvwqnKwRcOIfrRh3c/LTo6UDdyJwOOvEVPvLXCrrrUtdnniCEXAMPLE/IvU1dYUg2RVAJBanLiHb4IgRmpRV3zrkuWJOgQs8IZZaIv2BXIa2R4OlgkBN9bkUDNCJiBeb/AXlzBBko7b15fjrBs2+cTQtpZ3CYWFXG8C5zqx37wnOE49mRl/+OtkIKGO7fAE";
572+
573+
var preparedToken = escapeToken ? Uri.EscapeDataString(awsSessionToken) : awsSessionToken;
574+
var uri = $"mongodb+srv://{username}:{Uri.EscapeDataString(password)}@awssessiontokentest.example.net/test?authSource=$external&authMechanism={authMechanism}&authMechanismProperties=AWS_SESSION_TOKEN:{preparedToken}";
575+
var url = new MongoUrl(uri);
576+
577+
var result = MongoClientSettings.FromUrl(url).Credential;
578+
579+
result.Mechanism.Should().Be(authMechanism);
580+
result.Username.Should().Be(username);
581+
result.GetMechanismProperty("AWS_SESSION_TOKEN", string.Empty).Should().Be(awsSessionToken);
582+
}
583+
563584
[Fact]
564585
public void TestFrozenCopy()
565586
{

0 commit comments

Comments
 (0)