Skip to content

Commit 099476d

Browse files
authored
[Messaging] Relax emulator endpoint restrictions (Azure#44052)
The focus of these changes is to remove the restriction that the endpoint used in connection strings must resolve to a variant of `localhost` when using the development emulator. A fix to parsing was also made to be resilient to an endpoint with no scheme that specifies a custom port. Previously, this would parse incorrectly and be rejected. The scenario is now detected and parsed. Also included is a package bump for the AMQP transport library and release prep for the Event Hubs core package.
1 parent 7fd0fbc commit 099476d

File tree

10 files changed

+191
-90
lines changed

10 files changed

+191
-90
lines changed

eng/Packages.Data.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@
151151
<PackageReference Update="Azure.ResourceManager.Storage" Version="1.2.1" />
152152

153153
<!-- Other approved packages -->
154-
<PackageReference Update="Microsoft.Azure.Amqp" Version="2.6.6" />
154+
<PackageReference Update="Microsoft.Azure.Amqp" Version="2.6.7" />
155155
<PackageReference Update="Microsoft.Azure.WebPubSub.Common" Version="1.2.0" />
156156
<PackageReference Update="Microsoft.Identity.Client" Version="4.60.3" />
157157
<PackageReference Update="Microsoft.Identity.Client.Extensions.Msal" Version="4.60.3" />

sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Resources.Designer.cs

Lines changed: 0 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Resources.resx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -342,9 +342,6 @@
342342
<data name="TroubleshootingGuideLink" xml:space="preserve">
343343
<value>For troubleshooting information, see https://aka.ms/azsdk/net/eventhubs/exceptions/troubleshoot</value>
344344
</data>
345-
<data name="InvalidEmulatorEndpoint" xml:space="preserve">
346-
<value>The Event Hubs emulator is only available locally. The endpoint must reference to the local host.</value>
347-
</data>
348345
<data name="BufferedProducerStartupTimeout" xml:space="preserve">
349346
<value>The buffered producer took too long to start.</value>
350347
</data>

sdk/eventhub/Azure.Messaging.EventHubs/CHANGELOG.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
# Release History
22

3-
## 5.12.0-beta.1 (Unreleased)
4-
5-
### Features Added
6-
7-
### Breaking Changes
3+
## 5.11.3 (2024-05-15)
84

95
### Bugs Fixed
106

7+
- Fixed an error that caused connection strings using host names without a scheme to fail parsing and be considered invalid.
8+
119
### Other Changes
1210

13-
- Updated the `Microsoft.Azure.Amqp` dependency to 2.6.6, which includes a bug fix for an internal `NullReferenceException` that would sometimes impact creating new links. _(see: [#258](https://github.com/azure/azure-amqp/issues/258))_
11+
- Removed the restriction that endpoints used with the development emulator had to resolve to a `localhost` variant.
12+
13+
- Updated the `Microsoft.Azure.Amqp` dependency to 2.6.7, which contains several bug fixes, including for an internal `NullReferenceException` that would sometimes impact creating new links. _(see: [#258](https://github.com/azure/azure-amqp/issues/258))_
1414

1515
## 5.11.2 (2024-04-10)
1616

sdk/eventhub/Azure.Messaging.EventHubs/src/Azure.Messaging.EventHubs.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<Description>Azure Event Hubs is a highly scalable publish-subscribe service that can ingest millions of events per second and stream them to multiple consumers. This client library allows for both publishing and consuming events using Azure Event Hubs. For more information about Event Hubs, see https://azure.microsoft.com/en-us/services/event-hubs/</Description>
4-
<Version>5.12.0-beta.1</Version>
4+
<Version>5.11.3</Version>
55
<!--The ApiCompatVersion is managed automatically and should not generally be modified manually.-->
66
<ApiCompatVersion>5.11.2</ApiCompatVersion>
77
<PackageTags>Azure;Event Hubs;EventHubs;.NET;AMQP;IoT;$(PackageCommonTags)</PackageTags>

sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubsConnectionStringProperties.cs

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.ComponentModel;
6+
using System.Runtime.CompilerServices;
67
using System.Text;
78
using Azure.Core;
89

@@ -244,13 +245,6 @@ internal void Validate(string explicitEventHubName,
244245
{
245246
throw new ArgumentException(Resources.MissingConnectionInformation, connectionStringArgumentName);
246247
}
247-
248-
// Ensure that the namespace reflects the local host when the development emulator is being used.
249-
250-
if ((UseDevelopmentEmulator) && (!Endpoint.IsLoopback))
251-
{
252-
throw new ArgumentException(Resources.InvalidEmulatorEndpoint, connectionStringArgumentName);
253-
}
254248
}
255249

256250
/// <summary>
@@ -334,6 +328,17 @@ public static EventHubsConnectionStringProperties Parse(string connectionString)
334328
{
335329
endpointUri = null;
336330
}
331+
else if (string.IsNullOrEmpty(endpointUri.Host) && (CountChar(':', value.AsSpan()) == 1))
332+
{
333+
// If the host was empty after parsing and the value has a single port/scheme separator,
334+
// then the parsing likely failed to recognize the host due to the lack of a scheme. Add
335+
// an artificial scheme and try to parse again.
336+
337+
if (!Uri.TryCreate(string.Concat(EventHubsEndpointScheme, value), UriKind.Absolute, out endpointUri))
338+
{
339+
endpointUri = null;
340+
}
341+
}
337342

338343
var endpointBuilder = endpointUri switch
339344
{
@@ -400,5 +405,30 @@ public static EventHubsConnectionStringProperties Parse(string connectionString)
400405

401406
return parsedValues;
402407
}
408+
409+
/// <summary>
410+
/// Counts the number of times a character occurs in a given span.
411+
/// </summary>
412+
///
413+
/// <param name="span">The span to evaluate.</param>
414+
/// <param name="value">The character to count.</param>
415+
///
416+
/// <returns>The number of times the <paramref name="value"/> occurs in <paramref name="span"/>.</returns>
417+
///
418+
private static int CountChar(char value,
419+
ReadOnlySpan<char> span)
420+
{
421+
var count = 0;
422+
423+
foreach (var character in span)
424+
{
425+
if (character == value)
426+
{
427+
++count;
428+
}
429+
}
430+
431+
return count;
432+
}
403433
}
404434
}

sdk/eventhub/Azure.Messaging.EventHubs/tests/Core/EventHubsConnectionStringPropertiesTests.cs

Lines changed: 55 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ public void ParseIgnoresUnknownTokens()
347347
[TestCase("amqp://test.endpoint.com")]
348348
[TestCase("http://test.endpoint.com")]
349349
[TestCase("https://test.endpoint.com:8443")]
350-
public void ParseDoesAcceptsHostNamesAndUrisForTheEndpoint(string endpointValue)
350+
public void ParseAcceptsHostNamesAndUrisForTheEndpoint(string endpointValue)
351351
{
352352
var connectionString = $"Endpoint={ endpointValue };EntityPath=dummy";
353353
var parsed = EventHubsConnectionStringProperties.Parse(connectionString);
@@ -371,7 +371,7 @@ public void ParseDoesAcceptsHostNamesAndUrisForTheEndpoint(string endpointValue)
371371
/// </summary>
372372
///
373373
[Test]
374-
[TestCase("test.endpoint.com:443")]
374+
[TestCase("test-endpoint|com:443")]
375375
[TestCase("notvalid=[broke]")]
376376
public void ParseDoesNotAllowAnInvalidEndpointFormat(string endpointValue)
377377
{
@@ -402,6 +402,7 @@ public void ParseConsidersMissingValuesAsMalformed(string connectionString)
402402
/// method.
403403
/// </summary>
404404
///
405+
[Test]
405406
public void ParseDetectsDevelopmentEmulatorUse()
406407
{
407408
var connectionString = "Endpoint=localhost:1234;SharedAccessKeyName=[name];SharedAccessKey=[value];UseDevelopmentEmulator=true";
@@ -416,6 +417,7 @@ public void ParseDetectsDevelopmentEmulatorUse()
416417
/// method.
417418
/// </summary>
418419
///
420+
[Test]
419421
public void ParseRespectsDevelopmentEmulatorValue()
420422
{
421423
var connectionString = "Endpoint=localhost:1234;SharedAccessKeyName=[name];SharedAccessKey=[value];UseDevelopmentEmulator=false";
@@ -425,6 +427,57 @@ public void ParseRespectsDevelopmentEmulatorValue()
425427
Assert.That(parsed.UseDevelopmentEmulator, Is.False, "The development emulator flag should have been unset.");
426428
}
427429

430+
/// <summary>
431+
/// Verifies functionality of the <see cref="EventHubsConnectionStringProperties.Parse" />
432+
/// method.
433+
/// </summary>
434+
///
435+
[Test]
436+
[TestCase("localhost")]
437+
[TestCase("localhost:9084")]
438+
[TestCase("127.0.0.1")]
439+
[TestCase("local.docker.com")]
440+
[TestCase("local.docker.com:8080")]
441+
[TestCase("www.fake.com")]
442+
[TestCase("www.fake.com:443")]
443+
public void ParseRespectsTheEndpointForDevelopmentEmulatorValue(string host)
444+
{
445+
var connectionString = $"Endpoint={ host };SharedAccessKeyName=[name];SharedAccessKey=[value];UseDevelopmentEmulator=true";
446+
var endpoint = new Uri(string.Concat(GetEventHubsEndpointScheme(), host));
447+
var parsed = EventHubsConnectionStringProperties.Parse(connectionString);
448+
449+
Assert.That(parsed.Endpoint.Host, Is.EqualTo(endpoint.Host), "The endpoint hosts should match.");
450+
Assert.That(parsed.Endpoint.Port, Is.EqualTo(endpoint.Port), "The endpoint ports should match.");
451+
Assert.That(parsed.UseDevelopmentEmulator, Is.True, "The development emulator flag should have been set.");
452+
}
453+
454+
/// <summary>
455+
/// Verifies functionality of the <see cref="EventHubsConnectionStringProperties.Parse" />
456+
/// method.
457+
/// </summary>
458+
///
459+
[Test]
460+
[TestCase("sb://localhost")]
461+
[TestCase("http://localhost:9084")]
462+
[TestCase("sb://local.docker.com")]
463+
[TestCase("amqps://local.docker.com:8080")]
464+
[TestCase("sb://www.fake.com")]
465+
[TestCase("amqp://www.fake.com:443")]
466+
public void ParseRespectsTheUrlFormatEndpointForDevelopmentEmulatorValue(string host)
467+
{
468+
var endpoint = new UriBuilder(host)
469+
{
470+
Scheme = GetEventHubsEndpointScheme()
471+
};
472+
473+
var connectionString = $"Endpoint={ host };SharedAccessKeyName=[name];SharedAccessKey=[value];UseDevelopmentEmulator=true";
474+
var parsed = EventHubsConnectionStringProperties.Parse(connectionString);
475+
476+
Assert.That(parsed.Endpoint.Host, Is.EqualTo(endpoint.Host), "The endpoint hosts should match.");
477+
Assert.That(parsed.Endpoint.Port, Is.EqualTo(endpoint.Port), "The endpoint ports should match.");
478+
Assert.That(parsed.UseDevelopmentEmulator, Is.True, "The development emulator flag should have been set.");
479+
}
480+
428481
/// <summary>
429482
/// Verifies functionality of the <see cref="EventHubsConnectionStringProperties.Parse" />
430483
/// method.
@@ -657,33 +710,6 @@ public void ValidateAllowsSharedAccessSignatureAuthorization()
657710
Assert.That(() => properties.Validate(eventHubName, "dummy"), Throws.Nothing, "Validation should accept the shared access signature authorization.");
658711
}
659712

660-
/// <summary>
661-
/// Verifies functionality of the <see cref="EventHubsConnectionStringProperties.Validate" />
662-
/// method.
663-
/// </summary>
664-
///
665-
[Test]
666-
[TestCase("localhost", true)]
667-
[TestCase("127.0.0.1", true)]
668-
[TestCase("www.microsoft.com", false)]
669-
[TestCase("fake.servicebus.windows.net", false)]
670-
[TestCase("weirdname:8080", false)]
671-
public void ValidateRequiresLocalEndpointForDevelopmentEmulator(string endpoint,
672-
bool isValid)
673-
{
674-
var fakeConnection = $"Endpoint=sb://{ endpoint };SharedAccessSignature=[not_real];UseDevelopmentEmulator=true";
675-
var properties = EventHubsConnectionStringProperties.Parse(fakeConnection);
676-
677-
if (isValid)
678-
{
679-
Assert.That(() => properties.Validate("fake", "dummy"), Throws.Nothing, "Validation should allow a local endpoint.");
680-
}
681-
else
682-
{
683-
Assert.That(() => properties.Validate("fake", "dummy"), Throws.ArgumentException.And.Message.StartsWith(Resources.InvalidEmulatorEndpoint), "Validation should enforce that the endpoint is a local address.");
684-
}
685-
}
686-
687713
/// <summary>
688714
/// Compares two <see cref="EventHubsConnectionStringProperties" /> instances for
689715
/// structural equality.

sdk/servicebus/Azure.Messaging.ServiceBus/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@
88

99
### Bugs Fixed
1010

11+
- Fixed an error that caused connection strings using host names without a scheme to fail parsing and be considered invalid.
12+
1113
### Other Changes
1214

15+
- Updated the `Microsoft.Azure.Amqp` dependency to 2.6.7, which contains a fix for decoding messages with a null format code as the body.
16+
1317
## 7.18.0-beta.1 (2024-05-08)
1418

1519
### Features Added

sdk/servicebus/Azure.Messaging.ServiceBus/src/Primitives/ServiceBusConnectionStringProperties.cs

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,17 @@ public static ServiceBusConnectionStringProperties Parse(string connectionString
307307
{
308308
endpointUri = null;
309309
}
310+
else if (string.IsNullOrEmpty(endpointUri.Host) && (CountChar(':', value.AsSpan()) == 1))
311+
{
312+
// If the host was empty after parsing and the value has a single port/scheme separator,
313+
// then the parsing likely failed to recognize the host due to the lack of a scheme. Add
314+
// an artificial scheme and try to parse again.
315+
316+
if (!Uri.TryCreate($"{ServiceBusEndpointSchemeName}://{value}", UriKind.Absolute, out endpointUri))
317+
{
318+
endpointUri = null;
319+
}
320+
}
310321

311322
var endpointBuilder = endpointUri switch
312323
{
@@ -371,14 +382,32 @@ public static ServiceBusConnectionStringProperties Parse(string connectionString
371382
lastPosition = currentPosition;
372383
}
373384

374-
// Enforce that the development emulator can only be used for local development.
385+
return parsedValues;
386+
}
375387

376-
if ((parsedValues.UseDevelopmentEmulator) && (!parsedValues.Endpoint.IsLoopback))
388+
/// <summary>
389+
/// Counts the number of times a character occurs in a given span.
390+
/// </summary>
391+
///
392+
/// <param name="span">The span to evaluate.</param>
393+
/// <param name="value">The character to count.</param>
394+
///
395+
/// <returns>The number of times the <paramref name="value"/> occurs in <paramref name="span"/>.</returns>
396+
///
397+
private static int CountChar(char value,
398+
ReadOnlySpan<char> span)
399+
{
400+
var count = 0;
401+
402+
foreach (var character in span)
377403
{
378-
throw new ArgumentException("The Service Bus emulator is only available locally. The endpoint must reference to the local host.", connectionString);
404+
if (character == value)
405+
{
406+
++count;
407+
}
379408
}
380409

381-
return parsedValues;
410+
return count;
382411
}
383412
}
384413
}

0 commit comments

Comments
 (0)