diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/Authorization/SharedAccessSignature.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/Authorization/SharedAccessSignature.cs
index ccdccd8a3afc..5a0026c970ed 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/Authorization/SharedAccessSignature.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/Authorization/SharedAccessSignature.cs
@@ -158,17 +158,19 @@ public SharedAccessSignature(string sharedAccessSignature) : this(sharedAccessSi
/// The shared access signature to be used for authorization.
/// The date and time that the shared access signature expires, in UTC.
///
- ///
- /// This constructor is intended to support cloning of the signature and internal testing,
- /// allowing for direct setting of the property values without validation or adjustment.
- ///
- ///
- internal SharedAccessSignature(string eventHubResource,
- string sharedAccessKeyName,
- string sharedAccessKey,
- string value,
- DateTimeOffset signatureExpiration)
+ public SharedAccessSignature(string eventHubResource,
+ string sharedAccessKeyName,
+ string sharedAccessKey,
+ string value,
+ DateTimeOffset signatureExpiration)
{
+ Argument.AssertNotNullOrEmpty(eventHubResource, nameof(eventHubResource));
+ Argument.AssertNotNullOrEmpty(sharedAccessKeyName, nameof(sharedAccessKeyName));
+ Argument.AssertNotNullOrEmpty(sharedAccessKey, nameof(sharedAccessKey));
+
+ Argument.AssertNotTooLong(sharedAccessKeyName, MaximumKeyNameLength, nameof(sharedAccessKeyName));
+ Argument.AssertNotTooLong(sharedAccessKey, MaximumKeyLength, nameof(sharedAccessKey));
+
Resource = eventHubResource;
SharedAccessKeyName = sharedAccessKeyName;
SharedAccessKey = sharedAccessKey;
@@ -177,14 +179,14 @@ internal SharedAccessSignature(string eventHubResource,
}
///
- /// Extends the period for which the shared access signature is considered valid by adjusting the
- /// calculated expiration time. Upon successful extension, the of the signature will
- /// be updated with the new expiration.
+ /// Creates a new signature with the specified period for which the shared access signature is considered valid.
///
///
/// The duration that the signature should be considered valid.
///
- public void ExtendExpiration(TimeSpan signatureValidityDuration)
+ /// A new based on the same key, but with a new expiration time.
+ ///
+ public SharedAccessSignature CloneWithNewExpiration(TimeSpan signatureValidityDuration)
{
Argument.AssertNotNegative(signatureValidityDuration, nameof(signatureValidityDuration));
@@ -195,30 +197,9 @@ public void ExtendExpiration(TimeSpan signatureValidityDuration)
throw new InvalidOperationException(Resources.SharedAccessKeyIsRequired);
}
- SignatureExpiration = DateTimeOffset.UtcNow.Add(signatureValidityDuration);
- Value = BuildSignature(Resource, SharedAccessKeyName, SharedAccessKey, SignatureExpiration);
+ return new SharedAccessSignature(Resource, SharedAccessKeyName, SharedAccessKey, signatureValidityDuration);
}
- ///
- /// Returns a hash code for this instance.
- ///
- ///
- /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public override int GetHashCode() => base.GetHashCode();
-
- ///
- /// Determines whether the specified is equal to this instance.
- ///
- ///
- /// The to compare with this instance.
- ///
- /// true if the specified is equal to this instance; otherwise, false.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public override bool Equals(object obj) => base.Equals(obj);
-
///
/// Converts the instance to string representation.
///
@@ -227,15 +208,6 @@ public void ExtendExpiration(TimeSpan signatureValidityDuration)
///
public override string ToString() => Value;
- ///
- /// Creates a new copy of the current , cloning its attributes into a new instance.
- ///
- ///
- /// A new copy of .
- ///
- internal SharedAccessSignature Clone() =>
- new SharedAccessSignature(Resource, SharedAccessKeyName, SharedAccessKey, Value, SignatureExpiration);
-
///
/// Parses a shared access signature into its component parts.
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/Authorization/SharedAccessSignatureCredential.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/Authorization/SharedAccessSignatureCredential.cs
index bd8218393d4c..aa53d3a3e2b4 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/Authorization/SharedAccessSignatureCredential.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/Authorization/SharedAccessSignatureCredential.cs
@@ -19,16 +19,19 @@ namespace Azure.Messaging.EventHubs.Authorization
internal class SharedAccessSignatureCredential : TokenCredential
{
/// The buffer to apply when considering refreshing; signatures that expire less than this duration will be refreshed.
- private static readonly TimeSpan SignatureRefreshBuffer = TimeSpan.FromMinutes(5);
+ private static readonly TimeSpan SignatureRefreshBuffer = TimeSpan.FromMinutes(10);
/// The length of time extend signature validity, if a token was requested.
private static readonly TimeSpan SignatureExtensionDuration = TimeSpan.FromMinutes(30);
+ /// Provides a target for synchronization to guard against concurrent token expirations.
+ private readonly object ExtensionSyncRoot = new object();
+
///
/// The shared access signature that forms the basis of this security token.
///
///
- public SharedAccessSignature SharedAccessSignature { get; }
+ private SharedAccessSignature SharedAccessSignature { get; set; }
///
/// Initializes a new instance of the class.
@@ -57,7 +60,13 @@ public override AccessToken GetToken(TokenRequestContext requestContext,
{
if (SharedAccessSignature.SignatureExpiration <= DateTimeOffset.UtcNow.Add(SignatureRefreshBuffer))
{
- SharedAccessSignature.ExtendExpiration(SignatureExtensionDuration);
+ lock (ExtensionSyncRoot)
+ {
+ if (SharedAccessSignature.SignatureExpiration <= DateTimeOffset.UtcNow.Add(SignatureRefreshBuffer))
+ {
+ SharedAccessSignature = SharedAccessSignature.CloneWithNewExpiration(SignatureExtensionDuration);
+ }
+ }
}
return new AccessToken(SharedAccessSignature.Value, SharedAccessSignature.SignatureExpiration);
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Amqp/CbsTokenProviderTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Amqp/CbsTokenProviderTests.cs
index fa680db4506d..652be770b608 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Amqp/CbsTokenProviderTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Amqp/CbsTokenProviderTests.cs
@@ -66,7 +66,7 @@ public async Task GetTokenAsyncPopulatesUsingTheCredentialAccessToken()
public async Task GetTokenAsyncSetsTheCorrectTypeForSharedAccessSignatureTokens()
{
var value = "TOkEn!";
- var signature = new SharedAccessSignature(string.Empty, "keyName", "key", value, DateTimeOffset.Parse("2015-10-27T00:00:00Z"));
+ var signature = new SharedAccessSignature("hub", "keyName", "key", value, DateTimeOffset.Parse("2015-10-27T00:00:00Z"));
var sasCredential = new SharedAccessSignatureCredential(signature);
var credential = new EventHubTokenCredential(sasCredential, "test");
var provider = new CbsTokenProvider(credential, default);
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/EventHubSharedKeyCredentialTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/EventHubSharedKeyCredentialTests.cs
index c34b7f443473..13856ed8ba4b 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/EventHubSharedKeyCredentialTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/EventHubSharedKeyCredentialTests.cs
@@ -51,10 +51,7 @@ public void ConstructorValidatesInitializesProperties()
var name = "KeyName";
var value = "KeyValue";
var credential = new EventHubSharedKeyCredential(name, value);
-
- var initializedValue = typeof(EventHubSharedKeyCredential)
- .GetProperty("SharedAccessKey", BindingFlags.Instance | BindingFlags.NonPublic)
- .GetValue(credential, null);
+ var initializedValue = GetSharedAccessKey(credential);
Assert.That(credential.SharedAccessKeyName, Is.EqualTo(name), "The shared key name should have been set.");
Assert.That(initializedValue, Is.EqualTo(value), "The shared key should have been set.");
@@ -93,14 +90,44 @@ public void CovertToSharedAccessSignatureCredentialProducesTheExpectedCredential
var validSpan = TimeSpan.FromHours(4);
var signature = new SharedAccessSignature(resource, keyName, keyValue, validSpan);
var keyCredential = new EventHubSharedKeyCredential(keyName, keyValue);
- SharedAccessSignatureCredential sasCredential = keyCredential.ConvertToSharedAccessSignatureCredential(resource, validSpan);
+ var sasCredential = keyCredential.ConvertToSharedAccessSignatureCredential(resource, validSpan);
Assert.That(sasCredential, Is.Not.Null, "A shared access signature credential should have been created.");
- Assert.That(sasCredential.SharedAccessSignature, Is.Not.Null, "The SAS credential should contain a shared access signature.");
- Assert.That(sasCredential.SharedAccessSignature.Resource, Is.EqualTo(signature.Resource), "The resource should match.");
- Assert.That(sasCredential.SharedAccessSignature.SharedAccessKeyName, Is.EqualTo(signature.SharedAccessKeyName), "The shared access key name should match.");
- Assert.That(sasCredential.SharedAccessSignature.SharedAccessKey, Is.EqualTo(signature.SharedAccessKey), "The shared access key should match.");
- Assert.That(sasCredential.SharedAccessSignature.SignatureExpiration, Is.EqualTo(signature.SignatureExpiration).Within(TimeSpan.FromSeconds(5)), "The expiration should match.");
+
+ var credentialSignature = GetSharedAccessSignature(sasCredential);
+ Assert.That(credentialSignature, Is.Not.Null, "The SAS credential should contain a shared access signature.");
+ Assert.That(credentialSignature.Resource, Is.EqualTo(signature.Resource), "The resource should match.");
+ Assert.That(credentialSignature.SharedAccessKeyName, Is.EqualTo(signature.SharedAccessKeyName), "The shared access key name should match.");
+ Assert.That(credentialSignature.SharedAccessKey, Is.EqualTo(signature.SharedAccessKey), "The shared access key should match.");
+ Assert.That(credentialSignature.SignatureExpiration, Is.EqualTo(signature.SignatureExpiration).Within(TimeSpan.FromSeconds(5)), "The expiration should match.");
}
+
+ ///
+ /// Retrieves the shared access key from the credential using its private accessor.
+ ///
+ ///
+ /// The instance to retrieve the key from.
+ ///
+ /// The shared access key.
+ ///
+ private static string GetSharedAccessKey(EventHubSharedKeyCredential instance) =>
+ (string)
+ typeof(EventHubSharedKeyCredential)
+ .GetProperty("SharedAccessKey", BindingFlags.Instance | BindingFlags.NonPublic)
+ .GetValue(instance, null);
+
+ ///
+ /// Retrieves the shared access signature from the credential using its private accessor.
+ ///
+ ///
+ /// The instance to retrieve the key from.
+ ///
+ /// The shared access key.
+ ///
+ private static SharedAccessSignature GetSharedAccessSignature(SharedAccessSignatureCredential instance) =>
+ (SharedAccessSignature)
+ typeof(SharedAccessSignatureCredential)
+ .GetProperty("SharedAccessSignature", BindingFlags.Instance | BindingFlags.NonPublic)
+ .GetValue(instance, null);
}
}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/EventHubTokenCredentialTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/EventHubTokenCredentialTests.cs
index 567bdf3a3951..7de82da1f25c 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/EventHubTokenCredentialTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/EventHubTokenCredentialTests.cs
@@ -29,7 +29,7 @@ public class EventHubTokenCredentialTests
public static IEnumerable
///
[Test]
- public void ConstructorValidatesInitializesProperties()
+ public void ConstructorInitializesProperties()
{
var value = "TOkEn!";
- var signature = new SharedAccessSignature(string.Empty, "keyName", "key", value, DateTimeOffset.UtcNow.AddHours(4));
+ var signature = new SharedAccessSignature("hub-name", "keyName", "key", value, DateTimeOffset.UtcNow.AddHours(4));
var credential = new SharedAccessSignatureCredential(signature);
- Assert.That(credential.SharedAccessSignature, Is.SameAs(signature), "The credential should allow the signature to be accessed.");
+ Assert.That(GetSharedAccessSignature(credential), Is.SameAs(signature), "The credential should allow the signature to be accessed.");
}
///
@@ -51,7 +51,7 @@ public void ConstructorValidatesInitializesProperties()
public void GetTokenReturnsTheSignatureValue()
{
var value = "TOkEn!";
- var signature = new SharedAccessSignature(string.Empty, "keyName", "key", value, DateTimeOffset.UtcNow.AddHours(4));
+ var signature = new SharedAccessSignature("hub-name", "keyName", "key", value, DateTimeOffset.UtcNow.AddHours(4));
var credential = new SharedAccessSignatureCredential(signature);
Assert.That(credential.GetToken(new TokenRequestContext(), default).Token, Is.SameAs(signature.Value), "The credential should return the signature as the token.");
@@ -65,7 +65,7 @@ public void GetTokenReturnsTheSignatureValue()
public void GetTokenIgnoresScopeAndCancellationToken()
{
var value = "TOkEn!";
- var signature = new SharedAccessSignature(string.Empty, "keyName", "key", value, DateTimeOffset.UtcNow.AddHours(4));
+ var signature = new SharedAccessSignature("hub-name", "keyName", "key", value, DateTimeOffset.UtcNow.AddHours(4));
var credential = new SharedAccessSignatureCredential(signature);
Assert.That(credential.GetToken(new TokenRequestContext(new[] { "test", "this" }), CancellationToken.None).Token, Is.SameAs(signature.Value), "The credential should return the signature as the token.");
@@ -79,7 +79,7 @@ public void GetTokenIgnoresScopeAndCancellationToken()
public async Task GetTokenAsyncReturnsTheSignatureValue()
{
var value = "TOkEn!";
- var signature = new SharedAccessSignature(string.Empty, "keyName", "key", value, DateTimeOffset.UtcNow.AddHours(4));
+ var signature = new SharedAccessSignature("hub-name", "keyName", "key", value, DateTimeOffset.UtcNow.AddHours(4));
var credential = new SharedAccessSignatureCredential(signature);
var cancellation = new CancellationTokenSource();
AccessToken token = await credential.GetTokenAsync(new TokenRequestContext(), cancellation.Token);
@@ -95,7 +95,7 @@ public async Task GetTokenAsyncReturnsTheSignatureValue()
public async Task GetTokenAsyncIgnoresScopeAndCancellationToken()
{
var value = "TOkEn!";
- var signature = new SharedAccessSignature(string.Empty, "keyName", "key", value, DateTimeOffset.UtcNow.AddHours(4));
+ var signature = new SharedAccessSignature("hub-name", "keyName", "key", value, DateTimeOffset.UtcNow.AddHours(4));
var credential = new SharedAccessSignatureCredential(signature);
var cancellation = new CancellationTokenSource();
AccessToken token = await credential.GetTokenAsync(new TokenRequestContext(new string[0]), cancellation.Token);
@@ -111,7 +111,7 @@ public async Task GetTokenAsyncIgnoresScopeAndCancellationToken()
public void GetTokenExtendsAnExpiredToken()
{
var value = "TOkEn!";
- var signature = new SharedAccessSignature(string.Empty, "keyName", "key", value, DateTimeOffset.UtcNow.Subtract(TimeSpan.FromHours(2)));
+ var signature = new SharedAccessSignature("hub-name", "keyName", "key", value, DateTimeOffset.UtcNow.Subtract(TimeSpan.FromHours(2)));
var credential = new SharedAccessSignatureCredential(signature);
var expectedExpiration = DateTimeOffset.Now.Add(GetSignatureExtensionDuration());
@@ -127,13 +127,27 @@ public void GetTokenExtendsATokenCloseToExpiring()
{
var value = "TOkEn!";
var tokenExpiration = DateTimeOffset.UtcNow.Add(TimeSpan.FromSeconds(GetSignatureRefreshBuffer().TotalSeconds / 2));
- var signature = new SharedAccessSignature(string.Empty, "keyName", "key", value, tokenExpiration);
+ var signature = new SharedAccessSignature("hub-name", "keyName", "key", value, tokenExpiration);
var credential = new SharedAccessSignatureCredential(signature);
var expectedExpiration = DateTimeOffset.Now.Add(GetSignatureExtensionDuration());
Assert.That(credential.GetToken(new TokenRequestContext(), default).ExpiresOn, Is.EqualTo(expectedExpiration).Within(TimeSpan.FromMinutes(1)));
}
+ ///
+ /// Retrieves the shared access signature from the credential using its private accessor.
+ ///
+ ///
+ /// The instance to retrieve the key from.
+ ///
+ /// The shared access key
+ ///
+ private static SharedAccessSignature GetSharedAccessSignature(SharedAccessSignatureCredential instance) =>
+ (SharedAccessSignature)
+ typeof(SharedAccessSignatureCredential)
+ .GetProperty("SharedAccessSignature", BindingFlags.Instance | BindingFlags.NonPublic)
+ .GetValue(instance, null);
+
///
/// Gets the refresh buffer for the using
/// its private field.
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/SharedAccessSignatureTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/SharedAccessSignatureTests.cs
index 63189c997c79..4cb96c1f027d 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/SharedAccessSignatureTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/SharedAccessSignatureTests.cs
@@ -43,7 +43,7 @@ public static IEnumerable