From b705fa076c1c066a2489e13577a771b185874f3a Mon Sep 17 00:00:00 2001 From: Jesse Squire Date: Thu, 3 Sep 2020 16:39:14 -0400 Subject: [PATCH] [Event Hubs Client] Connection String SAS Support (Preview.3 Base) (#14796) The focus of these changes is to build on the source used for the preview.3 release, adding support for a precomputed shared access signature token to be used as part of the connection string. --- .../CHANGELOG.md | 7 ++ ...Azure.Messaging.EventHubs.Processor.csproj | 4 +- .../Authorization/SharedAccessSignature.cs | 0 .../SharedAccessSignatureCredential.cs | 6 +- .../src/Core/ConnectionStringParser.cs | 13 +++- .../src/Core/ConnectionStringProperties.cs | 31 +++++++-- .../src/Resources.Designer.cs | 11 +++ .../src/Resources.resx | 5 +- .../src/Testing/EventHubsTestEnvironment.cs | 24 ++++++- .../SharedAccessSignatureCredentialTests.cs | 67 ++++++++++++++++--- .../tests/Core/ConnectionStringParserTests.cs | 43 ++++++++---- .../Core/ConnectionStringPropertiesTests.cs | 57 ++++++++++++++-- .../Azure.Messaging.EventHubs/CHANGELOG.md | 5 +- .../src/Azure.Messaging.EventHubs.csproj | 2 +- .../src/EventHubConnection.cs | 33 +++++---- .../Connection/EventHubConnectionLiveTests.cs | 21 ++++++ .../Connection/EventHubConnectionTests.cs | 66 +++++++++++------- 17 files changed, 317 insertions(+), 78 deletions(-) mode change 100755 => 100644 sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Authorization/SharedAccessSignature.cs mode change 100755 => 100644 sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Authorization/SharedAccessSignatureCredential.cs mode change 100755 => 100644 sdk/eventhub/Azure.Messaging.EventHubs.Shared/tests/Authorization/SharedAccessSignatureCredentialTests.cs diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Processor/CHANGELOG.md b/sdk/eventhub/Azure.Messaging.EventHubs.Processor/CHANGELOG.md index f8dfcac52d4a..098cda553ae7 100755 --- a/sdk/eventhub/Azure.Messaging.EventHubs.Processor/CHANGELOG.md +++ b/sdk/eventhub/Azure.Messaging.EventHubs.Processor/CHANGELOG.md @@ -1,5 +1,12 @@ # Release History +## 5.2.0-preview.4 (Unreleased) + +## 5.2.0-preview.3 (2020-08-18) + +### Fixed +- Bug in TaskExtensions.EnsureCompleted method that causes it to unconditionally throw an exception in the environments with synchronization context + ## 5.2.0-preview.2 (2020-08-10) ### Acknowledgments diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Processor/src/Azure.Messaging.EventHubs.Processor.csproj b/sdk/eventhub/Azure.Messaging.EventHubs.Processor/src/Azure.Messaging.EventHubs.Processor.csproj index 097bd5a25b81..f8b27a6f8e70 100644 --- a/sdk/eventhub/Azure.Messaging.EventHubs.Processor/src/Azure.Messaging.EventHubs.Processor.csproj +++ b/sdk/eventhub/Azure.Messaging.EventHubs.Processor/src/Azure.Messaging.EventHubs.Processor.csproj @@ -1,7 +1,7 @@ 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 library extends its Event Processor with durable storage for checkpoint information using Azure Blob storage. For more information about Event Hubs, see https://azure.microsoft.com/en-us/services/event-hubs/ - 5.2.0-preview.2 + 5.2.0-preview.4 5.1.0 Azure;Event Hubs;EventHubs;.NET;Event Processor;EventProcessor;$(PackageCommonTags) $(RequiredTargetFrameworks) @@ -12,7 +12,7 @@ - + diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Authorization/SharedAccessSignature.cs b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Authorization/SharedAccessSignature.cs old mode 100755 new mode 100644 diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Authorization/SharedAccessSignatureCredential.cs b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Authorization/SharedAccessSignatureCredential.cs old mode 100755 new mode 100644 index 6fa72e317e0d..49cc4106e8f5 --- a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Authorization/SharedAccessSignatureCredential.cs +++ b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Authorization/SharedAccessSignatureCredential.cs @@ -58,7 +58,11 @@ public SharedAccessSignatureCredential(SharedAccessSignature signature) public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) { - if (SharedAccessSignature.SignatureExpiration <= DateTimeOffset.UtcNow.Add(SignatureRefreshBuffer)) + // If the signature was derived from a shared key rather than being provided externally, + // determine if the expiration is approaching and attempt to extend the token. + + if ((!string.IsNullOrEmpty(SharedAccessSignature.SharedAccessKey)) + && (SharedAccessSignature.SignatureExpiration <= DateTimeOffset.UtcNow.Add(SignatureRefreshBuffer))) { lock (SignatureSyncRoot) { diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Core/ConnectionStringParser.cs b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Core/ConnectionStringParser.cs index 475b89c4c415..3f366c3c3dda 100755 --- a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Core/ConnectionStringParser.cs +++ b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Core/ConnectionStringParser.cs @@ -24,6 +24,9 @@ internal static class ConnectionStringParser /// The token that identifies the value of a shared access key. private const string SharedAccessKeyValueToken = "SharedAccessKey"; + /// The token that identifies the value of a shared access signature. + private const string SharedAccessSignatureToken = "SharedAccessSignature"; + /// The character used to separate a token and its value in the connection string. private const char TokenValueSeparator = '='; @@ -64,7 +67,8 @@ public static ConnectionStringProperties Parse(string connectionString) EndpointToken: default(UriBuilder), EventHubNameToken: default(string), SharedAccessKeyNameToken: default(string), - SharedAccessKeyValueToken: default(string) + SharedAccessKeyValueToken: default(string), + SharedAccessSignatureToken: default(string) ); while (currentPosition != -1) @@ -140,6 +144,10 @@ public static ConnectionStringProperties Parse(string connectionString) { parsedValues.SharedAccessKeyValueToken = value; } + else if (string.Compare(SharedAccessSignatureToken, token, StringComparison.OrdinalIgnoreCase) == 0) + { + parsedValues.SharedAccessSignatureToken = value; + } } else if ((slice.Length != 1) || (slice[0] != TokenValuePairDelimiter)) { @@ -158,7 +166,8 @@ public static ConnectionStringProperties Parse(string connectionString) parsedValues.EndpointToken?.Uri, parsedValues.EventHubNameToken, parsedValues.SharedAccessKeyNameToken, - parsedValues.SharedAccessKeyValueToken + parsedValues.SharedAccessKeyValueToken, + parsedValues.SharedAccessSignatureToken ); } } diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Core/ConnectionStringProperties.cs b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Core/ConnectionStringProperties.cs index 6f63685eadf3..f1e6401bfdfc 100644 --- a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Core/ConnectionStringProperties.cs +++ b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Core/ConnectionStringProperties.cs @@ -40,6 +40,13 @@ internal struct ConnectionStringProperties /// public string SharedAccessKey { get; } + /// + /// The value of the fully-formed shared access signature, either for the Event Hubs + /// namespace or the Event Hub. + /// + /// + public string SharedAccessSignature { get; } + /// /// Initializes a new instance of the structure. /// @@ -48,16 +55,19 @@ internal struct ConnectionStringProperties /// The name of the specific Event Hub under the namespace. /// The name of the shared access key, to use authorization. /// The shared access key to use for authorization. + /// The precomputed shared access signature to use for authorization. /// public ConnectionStringProperties(Uri endpoint, string eventHubName, string sharedAccessKeyName, - string sharedAccessKey) + string sharedAccessKey, + string sharedAccessSignature) { Endpoint = endpoint; EventHubName = eventHubName; SharedAccessKeyName = sharedAccessKeyName; SharedAccessKey = sharedAccessKey; + SharedAccessSignature = sharedAccessSignature; } /// @@ -84,12 +94,23 @@ public void Validate(string explicitEventHubName, throw new ArgumentException(Resources.OnlyOneEventHubNameMayBeSpecified, connectionStringArgumentName); } + // The connection string may contain a precomputed shared access signature OR a shared key name and value, + // but not both. + + if ((!string.IsNullOrEmpty(SharedAccessSignature)) + && ((!string.IsNullOrEmpty(SharedAccessKeyName)) || (!string.IsNullOrEmpty(SharedAccessKey)))) + { + throw new ArgumentException(Resources.OnlyOneSharedAccessAuthorizationMayBeSpecified, connectionStringArgumentName); + } + // Ensure that each of the needed components are present for connecting. - if ((string.IsNullOrEmpty(explicitEventHubName)) && (string.IsNullOrEmpty(EventHubName)) - || (string.IsNullOrEmpty(Endpoint?.Host)) - || (string.IsNullOrEmpty(SharedAccessKeyName)) - || (string.IsNullOrEmpty(SharedAccessKey))) + var hasSharedKey = ((!string.IsNullOrEmpty(SharedAccessKeyName)) && (!string.IsNullOrEmpty(SharedAccessKey))); + var hasSharedSignature = (!string.IsNullOrEmpty(SharedAccessSignature)); + + if (string.IsNullOrEmpty(Endpoint?.Host) + || ((string.IsNullOrEmpty(explicitEventHubName)) && (string.IsNullOrEmpty(EventHubName))) + || ((!hasSharedKey) && (!hasSharedSignature))) { throw new ArgumentException(Resources.MissingConnectionInformation, connectionStringArgumentName); } diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Resources.Designer.cs b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Resources.Designer.cs index f425081e7a92..71116234a555 100755 --- a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Resources.Designer.cs +++ b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Resources.Designer.cs @@ -692,5 +692,16 @@ internal static string AggregateEventProcessingExceptionMessage return ResourceManager.GetString("AggregateEventProcessingExceptionMessage", resourceCulture); } } + + /// + /// Looks up a localized string similar to The authorization for a connection string may specifiy a shared key or precomputed shared access signature, but not both. Please verify that your connection string does not have the `SharedAccessSignature` token if you are passing the `SharedKeyName` and `SharedKey`.. + /// + internal static string OnlyOneSharedAccessAuthorizationMayBeSpecified + { + get + { + return ResourceManager.GetString("OnlyOneSharedAccessAuthorizationMayBeSpecified", resourceCulture); + } + } } } diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Resources.resx b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Resources.resx index 155e0ef48c77..1ad34df23a30 100755 --- a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Resources.resx +++ b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Resources.resx @@ -151,7 +151,7 @@ The connection string could not be parsed; either it was malformed or contains no well-known tokens. - The connection string used for an Event Hub client must specify the Event Hubs namespace host, and a Shared Access Key (both the name and value) to be valid. The path to an Event Hub must be included in the connection string or specified separately. + The connection string used for an Event Hub client must specify the Event Hubs namespace host, and either a Shared Access Key (both the name and value) or Shared Access Signature to be valid. The path to an Event Hub must be included in the connection string or specified separately. The path to an Event Hub may be specified as part of the connection string or as a separate value, but not both. Please verify that your connection string does not have the `EntityPath` token if you are passing an explicit Event Hub name. @@ -288,4 +288,7 @@ One or more exceptions occured during event processing. Please see the inner exceptions for more detail. + + The authorization for a connection string may specifiy a shared key or precomputed shared access signature, but not both. Please verify that your connection string does not have the `SharedAccessSignature` token if you are passing the `SharedKeyName` and `SharedKey`. + diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Testing/EventHubsTestEnvironment.cs b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Testing/EventHubsTestEnvironment.cs index 2499f7a178ce..959dc33f6ce3 100644 --- a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Testing/EventHubsTestEnvironment.cs +++ b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Testing/EventHubsTestEnvironment.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using Azure.Core.TestFramework; +using Azure.Messaging.EventHubs.Authorization; using Azure.Messaging.EventHubs.Core; namespace Azure.Messaging.EventHubs.Tests @@ -160,10 +161,31 @@ private EventHubsTestEnvironment() : base("eventhub") /// Live tests. /// /// - /// The namespace connection string is based on the dynamic Event Hubs scope. + /// The name of the Event Hub to base the connection string on. + /// + /// The Event Hub-level connection string. /// public string BuildConnectionStringForEventHub(string eventHubName) => $"{ EventHubsConnectionString };EntityPath={ eventHubName }"; + /// + /// Builds a connection string for the Event Hubs namespace used for Live tests, creating a shared access signature + /// in place of the shared key. + /// + /// + /// The name of the Event Hub to base the connection string on. + /// The audience to use for the shared access signature. + /// The duration, in minutes, that the signature should be considered valid for. + /// + /// The namespace connection string with a shared access signature based on the shared key of the current scope. + /// + public string BuildConnectionStringWithSharedAccessSignature(string eventHubName, + string signatureAudience, + int validDurationMinutes = 30) + { + var signature = new SharedAccessSignature(signatureAudience, SharedAccessKeyName, SharedAccessKey, TimeSpan.FromMinutes(validDurationMinutes)); + return $"Endpoint={ ParsedConnectionString.Value.Endpoint };EntityPath={ eventHubName };SharedAccessSignature={ signature.Value }"; + } + /// /// Ensures that an Event Hubs namespace is available for the test run, using one if provided by the /// or creating a new Azure resource specific diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/tests/Authorization/SharedAccessSignatureCredentialTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/tests/Authorization/SharedAccessSignatureCredentialTests.cs old mode 100755 new mode 100644 index 28a38936e00e..5fa5b31aa041 --- a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/tests/Authorization/SharedAccessSignatureCredentialTests.cs +++ b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/tests/Authorization/SharedAccessSignatureCredentialTests.cs @@ -44,7 +44,8 @@ public void ConstructorInitializesProperties() } /// - /// Verifies functionality of the constructor. + /// Verifies functionality of the + /// method. /// /// [Test] @@ -58,7 +59,8 @@ public void GetTokenReturnsTheSignatureValue() } /// - /// Verifies functionality of the constructor. + /// Verifies functionality of the + /// method. /// /// [Test] @@ -72,7 +74,8 @@ public void GetTokenIgnoresScopeAndCancellationToken() } /// - /// Verifies functionality of the constructor. + /// Verifies functionality of the + /// method. /// /// [Test] @@ -88,7 +91,8 @@ public async Task GetTokenAsyncReturnsTheSignatureValue() } /// - /// Verifies functionality of the constructor. + /// Verifies functionality of the + /// method. /// /// [Test] @@ -104,11 +108,12 @@ public async Task GetTokenAsyncIgnoresScopeAndCancellationToken() } /// - /// Verifies functionality of the constructor. + /// Verifies functionality of the + /// method. /// /// [Test] - public void GetTokenExtendsAnExpiredToken() + public void GetTokenExtendsAnExpiredTokenWhenCreatedWithTheSharedKey() { var value = "TOkEn!"; var signature = new SharedAccessSignature("hub-name", "keyName", "key", value, DateTimeOffset.UtcNow.Subtract(TimeSpan.FromHours(2))); @@ -119,11 +124,12 @@ public void GetTokenExtendsAnExpiredToken() } /// - /// Verifies functionality of the constructor. + /// Verifies functionality of the + /// method. /// /// [Test] - public void GetTokenExtendsATokenCloseToExpiring() + public void GetTokenExtendsATokenCloseToExpiringWhenCreatedWithTheSharedKey() { var value = "TOkEn!"; var tokenExpiration = DateTimeOffset.UtcNow.Add(TimeSpan.FromSeconds(GetSignatureRefreshBuffer().TotalSeconds / 2)); @@ -134,6 +140,40 @@ public void GetTokenExtendsATokenCloseToExpiring() Assert.That(credential.GetToken(new TokenRequestContext(), default).ExpiresOn, Is.EqualTo(expectedExpiration).Within(TimeSpan.FromMinutes(1))); } + /// + /// Verifies functionality of the + /// method. + /// + /// + [Test] + public void GetTokenDoesNotExtendAnExpiredTokenWhenCreatedWithoutTheKey() + { + var expectedExpiration = DateTimeOffset.UtcNow.Subtract(TimeSpan.FromHours(2)); + var value = $"SharedAccessSignature sr=https%3A%2F%2Ffake-test.servicebus.windows.net%2F&sig=nNBNavJfBiHuXUzWOLhSvI3bVgqbQUzA7Po8%2F4wQQng%3D&se={ ToUnixTime(expectedExpiration) }&skn=fakeKey"; + var sourceSignature = new SharedAccessSignature("fake-test", "fakeKey", "ABC123", value, expectedExpiration).Value; + var signature = new SharedAccessSignature(sourceSignature); + var credential = new SharedAccessSignatureCredential(signature); + + Assert.That(credential.GetToken(new TokenRequestContext(), default).ExpiresOn, Is.EqualTo(expectedExpiration).Within(TimeSpan.FromMinutes(1))); + } + + /// + /// Verifies functionality of the + /// method. + /// + /// + [Test] + public void GetTokenDoesNotExtendATokenCloseToExpiringWhenCreatedWithoutTheKey() + { + var tokenExpiration = DateTimeOffset.UtcNow.Add(TimeSpan.FromSeconds(GetSignatureRefreshBuffer().TotalSeconds / 2)); + var value = $"SharedAccessSignature sr=https%3A%2F%2Ffake-test.servicebus.windows.net%2F&sig=nNBNavJfBiHuXUzWOLhSvI3bVgqbQUzA7Po8%2F4wQQng%3D&se={ ToUnixTime(tokenExpiration) }&skn=fakeKey"; + var sourceSignature = new SharedAccessSignature("fake-test", "fakeKey", "ABC123", value, tokenExpiration).Value; + var signature = new SharedAccessSignature(sourceSignature); + var credential = new SharedAccessSignatureCredential(signature); + + Assert.That(credential.GetToken(new TokenRequestContext(), default).ExpiresOn, Is.EqualTo(tokenExpiration).Within(TimeSpan.FromMinutes(1))); + } + /// /// Verifies that a signature can be rotated without refreshing its validity. /// @@ -155,6 +195,17 @@ public void ShouldUpdateSharedAccessKey() Assert.That(newSignature.SignatureExpiration, Is.EqualTo(signature.SignatureExpiration)); } + /// + /// Converts a value to the corresponding Unix-style time stamp. + /// + /// + /// The date/time to convert. + /// + /// The Unix-style times tamp which corresponds to the specified date/time. + /// + private static long ToUnixTime(DateTimeOffset timestamp) => + Convert.ToInt64((timestamp - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalSeconds); + /// /// Retrieves the shared access signature from the credential using its private accessor. /// diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/tests/Core/ConnectionStringParserTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/tests/Core/ConnectionStringParserTests.cs index 2870e591536a..b9aab00eae68 100644 --- a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/tests/Core/ConnectionStringParserTests.cs +++ b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/tests/Core/ConnectionStringParserTests.cs @@ -28,12 +28,15 @@ public static IEnumerable ParseDoesNotforceTokenOrderingCases() var eventHub = "some-path"; var sasKey = "sasKey"; var sasKeyName = "sasName"; + var sas = "fullsas"; - yield return new object[] { $"Endpoint=sb://{ endpoint };SharedAccessKeyName={ sasKeyName };SharedAccessKey={ sasKey };EntityPath={ eventHub }", endpoint, eventHub, sasKeyName, sasKey }; - yield return new object[] { $"Endpoint=sb://{ endpoint };SharedAccessKey={ sasKey };EntityPath={ eventHub };SharedAccessKeyName={ sasKeyName }", endpoint, eventHub, sasKeyName, sasKey }; - yield return new object[] { $"Endpoint=sb://{ endpoint };EntityPath={ eventHub };SharedAccessKeyName={ sasKeyName };SharedAccessKey={ sasKey }", endpoint, eventHub, sasKeyName, sasKey }; - yield return new object[] { $"SharedAccessKeyName={ sasKeyName };SharedAccessKey={ sasKey };Endpoint=sb://{ endpoint };EntityPath={ eventHub }", endpoint, eventHub, sasKeyName, sasKey }; - yield return new object[] { $"EntityPath={ eventHub };SharedAccessKey={ sasKey };SharedAccessKeyName={ sasKeyName };Endpoint=sb://{ endpoint }", endpoint, eventHub, sasKeyName, sasKey }; + yield return new object[] { $"Endpoint=sb://{ endpoint };SharedAccessKeyName={ sasKeyName };SharedAccessKey={ sasKey };EntityPath={ eventHub }", endpoint, eventHub, sasKeyName, sasKey, null }; + yield return new object[] { $"Endpoint=sb://{ endpoint };SharedAccessKey={ sasKey };EntityPath={ eventHub };SharedAccessKeyName={ sasKeyName }", endpoint, eventHub, sasKeyName, sasKey, null }; + yield return new object[] { $"Endpoint=sb://{ endpoint };EntityPath={ eventHub };SharedAccessKeyName={ sasKeyName };SharedAccessKey={ sasKey }", endpoint, eventHub, sasKeyName, sasKey, null }; + yield return new object[] { $"SharedAccessKeyName={ sasKeyName };SharedAccessKey={ sasKey };Endpoint=sb://{ endpoint };EntityPath={ eventHub }", endpoint, eventHub, sasKeyName, sasKey, null }; + yield return new object[] { $"EntityPath={ eventHub };SharedAccessKey={ sasKey };SharedAccessKeyName={ sasKeyName };Endpoint=sb://{ endpoint }", endpoint, eventHub, sasKeyName, sasKey, null }; + yield return new object[] { $"EntityPath={ eventHub };SharedAccessSignature={ sas };Endpoint=sb://{ endpoint }", endpoint, eventHub, null, null, sas }; + yield return new object[] { $"SharedAccessKeyName={ sasKeyName };SharedAccessKey={ sasKey };Endpoint=sb://{ endpoint };EntityPath={ eventHub };SharedAccessSignature={ sas }", endpoint, eventHub, sasKeyName, sasKey, sas }; } /// @@ -46,12 +49,16 @@ public static IEnumerable ParseCorrectlyParsesPartialConnectionStrings var eventHub = "some-path"; var sasKey = "sasKey"; var sasKeyName = "sasName"; + var sas = "fullsas"; - yield return new object[] { $"Endpoint=sb://{ endpoint }", endpoint, null, null, null }; - yield return new object[] { $"SharedAccessKey={ sasKey }", null, null, sasKeyName, null }; - yield return new object[] { $"EntityPath={ eventHub };SharedAccessKeyName={ sasKeyName }", null, eventHub, sasKeyName, null }; - yield return new object[] { $"SharedAccessKeyName={ sasKeyName };SharedAccessKey={ sasKey }", null, null, sasKeyName, sasKey }; - yield return new object[] { $"EntityPath={ eventHub };SharedAccessKey={ sasKey };SharedAccessKeyName={ sasKeyName }", null, eventHub, sasKeyName, sasKey }; + yield return new object[] { $"Endpoint=sb://{ endpoint }", endpoint, null, null, null, null }; + yield return new object[] { $"SharedAccessKey={ sasKey }", null, null, sasKeyName, null, null }; + yield return new object[] { $"EntityPath={ eventHub };SharedAccessKeyName={ sasKeyName }", null, eventHub, sasKeyName, null, null }; + yield return new object[] { $"SharedAccessKeyName={ sasKeyName };SharedAccessKey={ sasKey }", null, null, sasKeyName, sasKey, null }; + yield return new object[] { $"EntityPath={ eventHub };SharedAccessKey={ sasKey };SharedAccessKeyName={ sasKeyName }", null, eventHub, sasKeyName, sasKey, null }; + yield return new object[] { $"SharedAccessKeyName={ sasKeyName };SharedAccessSignature={ sas }", null, null, null, null, sas }; + yield return new object[] { $"EntityPath={ eventHub };SharedAccessSignature={ sas }", null, eventHub, null, null, sas }; + yield return new object[] { $"EntityPath={ eventHub };SharedAccessKey={ sasKey };SharedAccessKeyName={ sasKeyName };SharedAccessSignature={ sas }", null, eventHub, sasKeyName, sasKey, sas }; } /// @@ -80,12 +87,14 @@ public void ParseCorrectlyParsesANamespaceConnectionString() var endpoint = "test.endpoint.com"; var sasKey = "sasKey"; var sasKeyName = "sasName"; - var connectionString = $"Endpoint=sb://{ endpoint };SharedAccessKeyName={ sasKeyName };SharedAccessKey={ sasKey }"; + var sharedAccessSignature = "fakeSAS"; + var connectionString = $"Endpoint=sb://{ endpoint };SharedAccessKeyName={ sasKeyName };SharedAccessKey={ sasKey };SharedAccessSignature={ sharedAccessSignature }"; ConnectionStringProperties parsed = ConnectionStringParser.Parse(connectionString); Assert.That(parsed.Endpoint?.Host, Is.EqualTo(endpoint).Using((IComparer)StringComparer.OrdinalIgnoreCase), "The endpoint host should match."); Assert.That(parsed.SharedAccessKeyName, Is.EqualTo(sasKeyName), "The SAS key name should match."); Assert.That(parsed.SharedAccessKey, Is.EqualTo(sasKey), "The SAS key value should match."); + Assert.That(parsed.SharedAccessSignature, Is.EqualTo(sharedAccessSignature), "The precomputed SAS should match."); Assert.That(parsed.EventHubName, Is.Null, "The Event Hub path was not included in the connection string"); } @@ -101,12 +110,14 @@ public void ParseCorrectlyParsesAnEventHubConnectionString() var eventHub = "some-path"; var sasKey = "sasKey"; var sasKeyName = "sasName"; - var connectionString = $"Endpoint=sb://{ endpoint };SharedAccessKeyName={ sasKeyName };SharedAccessKey={ sasKey };EntityPath={ eventHub }"; + var sharedAccessSignature = "fakeSAS"; + var connectionString = $"Endpoint=sb://{ endpoint };SharedAccessKeyName={ sasKeyName };SharedAccessKey={ sasKey };EntityPath={ eventHub };SharedAccessSignature={ sharedAccessSignature }"; ConnectionStringProperties parsed = ConnectionStringParser.Parse(connectionString); Assert.That(parsed.Endpoint?.Host, Is.EqualTo(endpoint).Using((IComparer)StringComparer.OrdinalIgnoreCase), "The endpoint host should match."); Assert.That(parsed.SharedAccessKeyName, Is.EqualTo(sasKeyName), "The SAS key name should match."); Assert.That(parsed.SharedAccessKey, Is.EqualTo(sasKey), "The SAS key value should match."); + Assert.That(parsed.SharedAccessSignature, Is.EqualTo(sharedAccessSignature), "The precomputed SAS should match."); Assert.That(parsed.EventHubName, Is.EqualTo(eventHub), "The Event Hub path should match."); } @@ -121,13 +132,15 @@ public void ParseCorrectlyParsesPartialConnectionStrings(string connectionString string endpoint, string eventHub, string sasKeyName, - string sasKey) + string sasKey, + string sharedAccessSignature) { ConnectionStringProperties parsed = ConnectionStringParser.Parse(connectionString); Assert.That(parsed.Endpoint?.Host, Is.EqualTo(endpoint).Using((IComparer)StringComparer.OrdinalIgnoreCase), "The endpoint host should match."); Assert.That(parsed.SharedAccessKeyName, Is.EqualTo(sasKeyName), "The SAS key name should match."); Assert.That(parsed.SharedAccessKey, Is.EqualTo(sasKey), "The SAS key value should match."); + Assert.That(parsed.SharedAccessSignature, Is.EqualTo(sharedAccessSignature), "The precomputed SAS should match."); Assert.That(parsed.EventHubName, Is.EqualTo(eventHub), "The Event Hub path should match."); } @@ -226,13 +239,15 @@ public void ParseDoesNotForceTokenOrdering(string connectionString, string endpoint, string eventHub, string sasKeyName, - string sasKey) + string sasKey, + string shardAccessSignature) { ConnectionStringProperties parsed = ConnectionStringParser.Parse(connectionString); Assert.That(parsed.Endpoint?.Host, Is.EqualTo(endpoint).Using((IComparer)StringComparer.OrdinalIgnoreCase), "The endpoint host should match."); Assert.That(parsed.SharedAccessKeyName, Is.EqualTo(sasKeyName), "The SAS key name should match."); Assert.That(parsed.SharedAccessKey, Is.EqualTo(sasKey), "The SAS key value should match."); + Assert.That(parsed.SharedAccessSignature, Is.EqualTo(shardAccessSignature), "The precomputed SAS should match."); Assert.That(parsed.EventHubName, Is.EqualTo(eventHub), "The Event Hub path should match."); } diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/tests/Core/ConnectionStringPropertiesTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/tests/Core/ConnectionStringPropertiesTests.cs index 942b2d425440..390d9269eab4 100644 --- a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/tests/Core/ConnectionStringPropertiesTests.cs +++ b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/tests/Core/ConnectionStringPropertiesTests.cs @@ -21,12 +21,12 @@ public class ConnectionStringPropertiesTests /// [Test] [TestCase("SharedAccessKeyName=[value];SharedAccessKey=[value];EntityPath=[value]")] - [TestCase("Endpoint=value.com;SharedAccessKey=[value];EntityPath=[value]")] - [TestCase("Endpoint=value.com;SharedAccessKeyName=[value];EntityPath=[value]")] - [TestCase("Endpoint=value.com;SharedAccessKeyName=[value];SharedAccessKey=[value]")] + [TestCase("Endpoint=sb://value.com;SharedAccessKey=[value];EntityPath=[value]")] + [TestCase("Endpoint=sb://value.com;SharedAccessKeyName=[value];EntityPath=[value]")] + [TestCase("Endpoint=sb://value.com;SharedAccessKeyName=[value];SharedAccessKey=[value]")] [TestCase("HostName=value.azure-devices.net;SharedAccessKeyName=[value];SharedAccessKey=[value]")] [TestCase("HostName=value.azure-devices.net;SharedAccessKeyName=[value];SharedAccessKey=[value];EntityPath=[value]")] - public void ValidateDetectsAnInvalidConnectionString(string connectionString) + public void ValidateDetectsMissingConnectionStringInformation(string connectionString) { var properties = ConnectionStringParser.Parse(connectionString); Assert.That(() => properties.Validate(null, "Dummy"), Throws.ArgumentException.And.Message.StartsWith(Resources.MissingConnectionInformation)); @@ -41,7 +41,7 @@ public void ValidateDetectsAnInvalidConnectionString(string connectionString) public void ValidateDetectsMultipleEventHubNames() { var eventHubName = "myHub"; - var fakeConnection = $"Endpoint=sb://not-real.servicebus.windows.net/;SharedAccessKeyName=DummyKey;SharedAccessKey=[not_real];EntityPath=[unique_fake]"; + var fakeConnection = "Endpoint=sb://not-real.servicebus.windows.net/;SharedAccessKeyName=DummyKey;SharedAccessKey=[not_real];EntityPath=[unique_fake]"; var properties = ConnectionStringParser.Parse(fakeConnection); Assert.That(() => properties.Validate(eventHubName, "Dummy"), Throws.ArgumentException.And.Message.StartsWith(Resources.OnlyOneEventHubNameMayBeSpecified)); @@ -59,7 +59,52 @@ public void ValidateAllowsMultipleEventHubNamesIfEqual() var fakeConnection = $"Endpoint=sb://not-real.servicebus.windows.net/;SharedAccessKeyName=DummyKey;SharedAccessKey=[not_real];EntityPath={ eventHubName }"; var properties = ConnectionStringParser.Parse(fakeConnection); - Assert.That(() => properties.Validate(eventHubName, "dummy"), Throws.Nothing, "Validation should accept the same Event Hub in multiple places"); + Assert.That(() => properties.Validate(eventHubName, "dummy"), Throws.Nothing, "Validation should accept the same Event Hub in multiple places."); + } + + /// + /// Verifies functionality of the + /// method. + /// + /// + [Test] + [TestCase("Endpoint=sb://not-real.servicebus.windows.net/;SharedAccessKeyName=DummyKey;SharedAccessKey=[not_real];EntityPath=[unique_fake];SharedAccessSignature=[not_real]")] + [TestCase("Endpoint=sb://not-real.servicebus.windows.net/;SharedAccessKeyName=DummyKey;EntityPath=[unique_fake];SharedAccessSignature=[not_real]")] + [TestCase("Endpoint=sb://not-real.servicebus.windows.net/;SharedAccessKey=[not_real];EntityPath=[unique_fake];SharedAccessSignature=[not_real]")] + public void ValidateDetectsMultipleAuthorizationCredentials(string connectionString) + { + var properties = ConnectionStringParser.Parse(connectionString); + Assert.That(() => properties.Validate(null, "Dummy"), Throws.ArgumentException.And.Message.StartsWith(Resources.OnlyOneSharedAccessAuthorizationMayBeSpecified)); + } + + /// + /// Verifies functionality of the + /// method. + /// + /// + [Test] + public void ValidateAllowsSharedAccessKeyAuthorization() + { + var eventHubName = "myHub"; + var fakeConnection = "Endpoint=sb://not-real.servicebus.windows.net/;SharedAccessKeyName=DummyKey;SharedAccessKey=[not_real]"; + var properties = ConnectionStringParser.Parse(fakeConnection); + + Assert.That(() => properties.Validate(eventHubName, "dummy"), Throws.Nothing, "Validation should accept the shared access key authorization."); + } + + /// + /// Verifies functionality of the + /// method. + /// + /// + [Test] + public void ValidateAllowsSharedAccessSignatureAuthorization() + { + var eventHubName = "myHub"; + var fakeConnection = "Endpoint=sb://not-real.servicebus.windows.net/;SharedAccessSignature=[not_real]"; + var properties = ConnectionStringParser.Parse(fakeConnection); + + Assert.That(() => properties.Validate(eventHubName, "dummy"), Throws.Nothing, "Validation should accept the shared access signature authorization."); } } } diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/CHANGELOG.md b/sdk/eventhub/Azure.Messaging.EventHubs/CHANGELOG.md index 4a55b12e60d4..98ced42129fd 100755 --- a/sdk/eventhub/Azure.Messaging.EventHubs/CHANGELOG.md +++ b/sdk/eventhub/Azure.Messaging.EventHubs/CHANGELOG.md @@ -1,4 +1,7 @@ -# Release History +# Release History + +## 5.2.0-preview.4 (Unreleased) + ## 5.2.0-preview.3 (2020-08-18) diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/Azure.Messaging.EventHubs.csproj b/sdk/eventhub/Azure.Messaging.EventHubs/src/Azure.Messaging.EventHubs.csproj index 3eee9d40c803..75fb29bc8309 100644 --- a/sdk/eventhub/Azure.Messaging.EventHubs/src/Azure.Messaging.EventHubs.csproj +++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/Azure.Messaging.EventHubs.csproj @@ -1,7 +1,7 @@ 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/ - 5.2.0-preview.3 + 5.2.0-preview.4 5.1.0 Azure;Event Hubs;EventHubs;.NET;AMQP;IoT;$(PackageCommonTags) $(RequiredTargetFrameworks) diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubConnection.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubConnection.cs index ecbab4bd7c91..164ee677c09e 100644 --- a/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubConnection.cs +++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubConnection.cs @@ -159,15 +159,22 @@ public EventHubConnection(string connectionString, eventHubName = connectionStringProperties.EventHubName; } - var sharedAccessSignature = new SharedAccessSignature - ( - BuildAudienceResource(connectionOptions.TransportType, fullyQualifiedNamespace, eventHubName), - connectionStringProperties.SharedAccessKeyName, - connectionStringProperties.SharedAccessKey - ); + SharedAccessSignature sharedAccessSignature; + + if (string.IsNullOrEmpty(connectionStringProperties.SharedAccessSignature)) + { + sharedAccessSignature = new SharedAccessSignature( + BuildConnectionAudience(connectionOptions.TransportType, fullyQualifiedNamespace, eventHubName), + connectionStringProperties.SharedAccessKeyName, + connectionStringProperties.SharedAccessKey); + } + else + { + sharedAccessSignature = new SharedAccessSignature(connectionStringProperties.SharedAccessSignature); + } var sharedCredentials = new SharedAccessSignatureCredential(sharedAccessSignature); - var tokenCredentials = new EventHubTokenCredential(sharedCredentials, BuildAudienceResource(connectionOptions.TransportType, fullyQualifiedNamespace, eventHubName)); + var tokenCredentials = new EventHubTokenCredential(sharedCredentials, BuildConnectionAudience(connectionOptions.TransportType, fullyQualifiedNamespace, eventHubName)); FullyQualifiedNamespace = fullyQualifiedNamespace; EventHubName = eventHubName; @@ -205,11 +212,11 @@ public EventHubConnection(string fullyQualifiedNamespace, break; case EventHubSharedKeyCredential sharedKeyCredential: - credential = sharedKeyCredential.AsSharedAccessSignatureCredential(BuildAudienceResource(connectionOptions.TransportType, fullyQualifiedNamespace, eventHubName)); + credential = sharedKeyCredential.AsSharedAccessSignatureCredential(BuildConnectionAudience(connectionOptions.TransportType, fullyQualifiedNamespace, eventHubName)); break; } - var tokenCredential = new EventHubTokenCredential(credential, BuildAudienceResource(connectionOptions.TransportType, fullyQualifiedNamespace, eventHubName)); + var tokenCredential = new EventHubTokenCredential(credential, BuildConnectionAudience(connectionOptions.TransportType, fullyQualifiedNamespace, eventHubName)); FullyQualifiedNamespace = fullyQualifiedNamespace; EventHubName = eventHubName; @@ -439,7 +446,7 @@ internal virtual TransportClient CreateTransportClient(string fullyQualifiedName } /// - /// Builds the audience for use in the signature. + /// Builds the audience of the connection for use in the signature. /// /// /// The type of protocol and transport that will be used for communicating with the Event Hubs service. @@ -448,9 +455,9 @@ internal virtual TransportClient CreateTransportClient(string fullyQualifiedName /// /// The value to use as the audience of the signature. /// - private static string BuildAudienceResource(EventHubsTransportType transportType, - string fullyQualifiedNamespace, - string eventHubName) + internal static string BuildConnectionAudience(EventHubsTransportType transportType, + string fullyQualifiedNamespace, + string eventHubName) { var builder = new UriBuilder(fullyQualifiedNamespace) { diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Connection/EventHubConnectionLiveTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Connection/EventHubConnectionLiveTests.cs index 10e9cfadc4a2..43b41702177c 100755 --- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Connection/EventHubConnectionLiveTests.cs +++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Connection/EventHubConnectionLiveTests.cs @@ -69,6 +69,27 @@ public async Task ConnectionCanConnectToEventHubsUsingConnectionStringAndEventHu } } + /// + /// Verifies that the is able to + /// connect to the Event Hubs service. + /// + /// + [Test] + public async Task ConnectionCanConnectToEventHubsUsingSharedAccessSignatureConnectionString() + { + await using (EventHubScope scope = await EventHubScope.CreateAsync(1)) + { + var options = new EventHubConnectionOptions(); + var audience = EventHubConnection.BuildConnectionAudience(options.TransportType, EventHubsTestEnvironment.Instance.FullyQualifiedNamespace, scope.EventHubName); + var connectionString = EventHubsTestEnvironment.Instance.BuildConnectionStringWithSharedAccessSignature(scope.EventHubName, audience); + + await using (var connection = new TestConnectionWithTransport(connectionString, options)) + { + Assert.That(() => connection.GetPropertiesAsync(), Throws.Nothing); + } + } + } + /// /// Verifies that the is able to /// connect to the Event Hubs service. diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Connection/EventHubConnectionTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Connection/EventHubConnectionTests.cs index 46133adb6f63..7d4dcbbb1741 100644 --- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Connection/EventHubConnectionTests.cs +++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Connection/EventHubConnectionTests.cs @@ -133,16 +133,33 @@ public void ConstructorDoesNotRequireEventHubInConnectionStringWhenPassedSeparat /// [Test] [TestCase("SharedAccessKeyName=[value];SharedAccessKey=[value];EntityPath=[value]")] + [TestCase("SharedAccessSignature=[value];EntityPath=[value]")] + [TestCase("SharedAccessSignature=[value]")] [TestCase("Endpoint=value.com;SharedAccessKey=[value];EntityPath=[value]")] [TestCase("Endpoint=value.com;SharedAccessKeyName=[value];EntityPath=[value]")] [TestCase("Endpoint=value.com;SharedAccessKeyName=[value];SharedAccessKey=[value]")] [TestCase("HostName=value.azure-devices.net;SharedAccessKeyName=[value];SharedAccessKey=[value]")] [TestCase("HostName=value.azure-devices.net;SharedAccessKeyName=[value];SharedAccessKey=[value];EntityPath=[value]")] - public void ConstructorValidatesConnectionString(string connectionString) + public void ConstructorValidatesConnectionStringForMissingInformation(string connectionString) { Assert.That(() => new EventHubConnection(connectionString), Throws.ArgumentException.And.Message.StartsWith(Resources.MissingConnectionInformation)); } + /// + /// Verifies functionality of the + /// constructor. + /// + /// + [Test] + + [TestCase("Endpoint=value.azure-devices.net;SharedAccessKeyName=[value];SharedAccessSignature=[sas];EntityPath=[value]")] + [TestCase("Endpoint=value.azure-devices.net;SharedAccessKey=[value];SharedAccessSignature=[sas];EntityPath=[value]")] + [TestCase("Endpoint=value.azure-devices.net;SharedAccessKeyName=[value];SharedAccessKey=[value];SharedAccessSignature=[sas];EntityPath=[value]")] + public void ConstructorValidatesConnectionStringForDuplicateAuthorization(string connectionString) + { + Assert.That(() => new EventHubConnection(connectionString), Throws.ArgumentException.And.Message.StartsWith(Resources.OnlyOneSharedAccessAuthorizationMayBeSpecified)); + } + /// /// Verifies functionality of the /// constructor. @@ -307,6 +324,21 @@ public void ContructorWithConnectionStringCreatesTheTransportClient() Assert.That(GetTransportClient(client), Is.Not.Null); } + /// + /// Verifies functionality of the + /// constructor. + /// + /// + [Test] + public void ContructorWithConnectionStringUsingSharedAccessSignatureCreatesTheCorrectTransportCredential() + { + var sasToken = new SharedAccessSignature("hub", "root", "abc1234").Value; + var client = new InjectableTransportClientMock(Mock.Of(), $"Endpoint=sb://not-real.servicebus.windows.net/;EntityPath=fake;SharedAccessSignature={ sasToken }"); + + Assert.That(client.TransportClientCredential, Is.Not.Null, "The transport client should have been given a credential."); + Assert.That(client.TransportClientCredential.GetToken(default, default).Token, Is.EqualTo(sasToken), "The transport client credential should use the provided SAS token."); + } + /// /// Verifies functionality of the /// constructor. @@ -603,13 +635,13 @@ public async Task CloseAsyncClosesTheTransportClient() /// /// [Test] - public void BuildResourceNormalizesTheResource() + public void BuildConnectionAudienceNormalizesTheResource() { var fullyQualifiedNamespace = "my.eventhub.com"; var path = "someHub/"; var transportClient = new ObservableTransportClientMock(); var client = new InjectableTransportClientMock(transportClient, "Endpoint=sb://not-real.servicebus.windows.net/;SharedAccessKeyName=DummyKey;SharedAccessKey=[not_real];EntityPath=fake"); - var resource = BuildResource(client, EventHubsTransportType.AmqpWebSockets, fullyQualifiedNamespace, path); + var resource = EventHubConnection.BuildConnectionAudience(EventHubsTransportType.AmqpWebSockets, fullyQualifiedNamespace, path); Assert.That(resource, Is.Not.Null.Or.Empty, "The resource should have been populated."); Assert.That(resource, Is.EqualTo(resource.ToLowerInvariant()), "The resource should have been normalized to lower case."); @@ -626,14 +658,14 @@ public void BuildResourceNormalizesTheResource() /// /// [Test] - public void BuildResourceConstructsFromNamespaceAndPath() + public void BuildConnectionAudienceConstructsFromNamespaceAndPath() { var fullyQualifiedNamespace = "my.eventhub.com"; var path = "someHub"; var transportClient = new ObservableTransportClientMock(); var client = new InjectableTransportClientMock(transportClient, "Endpoint=sb://not-real.servicebus.windows.net/;SharedAccessKeyName=DummyKey;SharedAccessKey=[not_real];EntityPath=fake"); var expectedPath = $"/{ path.ToLowerInvariant() }"; - var resource = BuildResource(client, EventHubsTransportType.AmqpTcp, fullyQualifiedNamespace, path); + var resource = EventHubConnection.BuildConnectionAudience(EventHubsTransportType.AmqpTcp, fullyQualifiedNamespace, path); Assert.That(resource, Is.Not.Null.Or.Empty, "The resource should have been populated."); @@ -643,23 +675,6 @@ public void BuildResourceConstructsFromNamespaceAndPath() Assert.That(uri.AbsolutePath, Is.EqualTo(expectedPath), "The resource path should match the Event Hub path."); } - /// - /// Provides a test shim for retrieving the credential that a client was - /// created with. - /// - /// - /// The client to retrieve the credential for. - /// - /// The credential with which the client was created. - /// - private string BuildResource(EventHubConnection client, - EventHubsTransportType transportType, - string fullyQualifiedNamespace, - string eventHubName) => - typeof(EventHubConnection) - .GetMethod("BuildAudienceResource", BindingFlags.Static | BindingFlags.NonPublic) - .Invoke(client, new object[] { transportType, fullyQualifiedNamespace, eventHubName }) as string; - /// /// Provides a test shim for retrieving the transport client contained by an /// Event Hub client instance. @@ -743,6 +758,7 @@ public override Task CloseAsync(CancellationToken cancellationToken = default) private class InjectableTransportClientMock : EventHubConnection { public TransportClient TransportClient; + public EventHubTokenCredential TransportClientCredential; public InjectableTransportClientMock(TransportClient transportClient, string connectionString, @@ -766,7 +782,11 @@ public InjectableTransportClientMock(TransportClient transportClient, internal override TransportClient CreateTransportClient(string fullyQualifiedNamespace, string eventHubName, EventHubTokenCredential credential, - EventHubConnectionOptions options) => TransportClient; + EventHubConnectionOptions options) + { + TransportClientCredential = credential; + return TransportClient; + } private void SetTransportClient(TransportClient transportClient) => typeof(EventHubConnection)