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 SharedAccessSignatureCredentialTestCases() { TokenCredential credentialMock = Mock.Of(); - var signature = new SharedAccessSignature(string.Empty, "keyName", "key", "TOkEn!", DateTimeOffset.UtcNow.AddHours(4)); + var signature = new SharedAccessSignature("hub", "keyName", "key", "TOkEn!", DateTimeOffset.UtcNow.AddHours(4)); yield return new object[] { new SharedAccessSignatureCredential(signature), true }; yield return new object[] { new EventHubSharedKeyCredential("blah", "foo"), true }; diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/SharedAccessSignatureCredentialTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/SharedAccessSignatureCredentialTests.cs index 7c70ecc247e2..287a178aff6a 100755 --- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/SharedAccessSignatureCredentialTests.cs +++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/SharedAccessSignatureCredentialTests.cs @@ -34,13 +34,13 @@ public void ConstructorValidatesTheSignature() /// /// [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 SignatureConstructorTestCases() public void ToStringReflectsTheValue() { var expected = "This is the value of the SAS"; - var signature = new SharedAccessSignature(string.Empty, "keyName", "key", expected, DateTimeOffset.UtcNow.AddHours(4)); + var signature = new SharedAccessSignature("hub", "keyName", "key", expected, DateTimeOffset.UtcNow.AddHours(4)); Assert.That(signature.ToString(), Is.EqualTo(expected)); } @@ -369,33 +369,33 @@ public void ParseExtractsValues() } /// - /// Validates functionality of the + /// Validates functionality of the /// method. /// [Test] public void ExtendValidityValidatesTheDuration() { var signature = new SharedAccessSignature("SharedAccessSignature sr=amqps%3A%2F%2Fmy.eh.com%2Fsomepath%2F&sig=%2BLsuqDlN8Us5lp%2FGdyEUMnU1XA4HdXx%2BJUdtkRNr7qI%3D&se=1562258488&skn=keykeykey¬real=123"); - Assert.That(() => signature.ExtendExpiration(TimeSpan.FromMilliseconds(-1)), Throws.InstanceOf()); + Assert.That(() => signature.CloneWithNewExpiration(TimeSpan.FromMilliseconds(-1)), Throws.InstanceOf()); } /// - /// Validates functionality of the + /// Validates functionality of the /// method. /// [Test] public void ExtendValidityValidatesTheKey() { var signature = new SharedAccessSignature("SharedAccessSignature sr=amqps%3A%2F%2Fmy.eh.com%2Fsomepath%2F&sig=%2BLsuqDlN8Us5lp%2FGdyEUMnU1XA4HdXx%2BJUdtkRNr7qI%3D&se=1562258488&skn=keykeykey¬real=123"); - Assert.That(() => signature.ExtendExpiration(TimeSpan.FromMilliseconds(21)), Throws.InvalidOperationException); + Assert.That(() => signature.CloneWithNewExpiration(TimeSpan.FromMilliseconds(21)), Throws.InvalidOperationException); } /// - /// Validates functionality of the + /// Validates functionality of the /// method. /// [Test] - public void ExtendExpirationUpdatesTheSignatureValue() + public void CloneAndExtendExpirationUpdatesTheSignatureValue() { var keyName = "rootShared"; var key = "ABC123FFF333"; @@ -404,16 +404,16 @@ public void ExtendExpirationUpdatesTheSignatureValue() var parsedSignature = new SharedAccessSignature(composedSignature.ToString(), keyName) as SharedAccessSignature; var initialParsedValue = parsedSignature.Value; - parsedSignature.ExtendExpiration(TimeSpan.FromHours(4)); + parsedSignature = parsedSignature.CloneWithNewExpiration(TimeSpan.FromHours(4)); Assert.That(parsedSignature.Value, Is.Not.EqualTo(initialParsedValue)); } /// - /// Validates functionality of the + /// Validates functionality of the /// method. /// [Test] - public void ExtendExpirationUpdatesTheExpirationTime() + public void CloneAndExtendExpirationUpdatesTheExpirationTime() { var keyName = "rootShared"; var key = "ABC123FFF333"; @@ -423,53 +423,56 @@ public void ExtendExpirationUpdatesTheExpirationTime() var composedSignature = new SharedAccessSignature("amqps://some.namespace.com/hubName", keyName, key, validFor); var parsedSignature = new SharedAccessSignature(composedSignature.ToString(), keyName) as SharedAccessSignature; - parsedSignature.ExtendExpiration(extendBy); + parsedSignature = parsedSignature.CloneWithNewExpiration(extendBy); Assert.That(parsedSignature.SignatureExpiration, Is.EqualTo(expiration).Within(TimeSpan.FromSeconds(5))); } /// - /// Verifies functionality of the + /// Verifies functionality of the /// method. /// /// [Test] - public void ParseProducesCorrectValues() + public void CloneProducesACopy() { var resource = "amqps://some.namespace.com/hubName"; var keyName = "rootShared"; var key = "ABC123FFF333"; var validFor = TimeSpan.FromMinutes(30); - DateTimeOffset expiration = DateTimeOffset.UtcNow.Add(validFor); + var extendBy = TimeSpan.FromMinutes(12); var signature = new SharedAccessSignature(resource, keyName, key, validFor); - (string KeyName, string Resource, DateTimeOffset ExpirationTime) parsed = ParseSignature(signature.ToString()); - Assert.That(parsed, Is.Not.Null, "There should have been a result returned."); - Assert.That(parsed.Resource, Is.EqualTo("amqps://some.namespace.com/hubName"), "The resource should match."); - Assert.That(parsed.KeyName, Is.EqualTo(keyName), "The key name should have been parsed."); - Assert.That(parsed.ExpirationTime, Is.EqualTo(expiration).Within(TimeSpan.FromSeconds(5)), "The expiration should be parsed."); + var clone = signature.CloneWithNewExpiration(extendBy); + var expectedCloneExpiration = DateTimeOffset.UtcNow.Add(extendBy); + + Assert.That(clone, Is.Not.Null, "There should have been a copy produced."); + Assert.That(clone, Is.Not.SameAs(signature), "The clone should be a unique instance."); + Assert.That(clone.Resource, Is.EqualTo(signature.Resource), "The resource should match."); + Assert.That(clone.SharedAccessKeyName, Is.EqualTo(signature.SharedAccessKeyName), "The key name should match."); + Assert.That(clone.SharedAccessKey, Is.EqualTo(signature.SharedAccessKey), "The key should match."); + Assert.That(clone.SignatureExpiration, Is.EqualTo(expectedCloneExpiration).Within(TimeSpan.FromSeconds(5)), "The expiration should have been extended."); } /// - /// Verifies functionality of the + /// Verifies functionality of the /// method. /// /// [Test] - public void CloneProducesACopy() + public void ParseProducesCorrectValues() { var resource = "amqps://some.namespace.com/hubName"; var keyName = "rootShared"; var key = "ABC123FFF333"; var validFor = TimeSpan.FromMinutes(30); + var expiration = DateTimeOffset.UtcNow.Add(validFor); var signature = new SharedAccessSignature(resource, keyName, key, validFor); - SharedAccessSignature clone = signature.Clone(); + (string KeyName, string Resource, DateTimeOffset ExpirationTime) parsed = ParseSignature(signature.ToString()); - Assert.That(clone, Is.Not.Null, "There should have been a copy produced."); - Assert.That(clone, Is.Not.SameAs(signature), "The clone should be a unique instance."); - Assert.That(clone.Resource, Is.EqualTo(signature.Resource), "The resource should match."); - Assert.That(clone.SharedAccessKeyName, Is.EqualTo(signature.SharedAccessKeyName), "The key name should match."); - Assert.That(clone.SharedAccessKey, Is.EqualTo(signature.SharedAccessKey), "The key should match."); - Assert.That(clone.SignatureExpiration, Is.EqualTo(signature.SignatureExpiration), "The expiration should match."); + Assert.That(parsed, Is.Not.Null, "There should have been a result returned."); + Assert.That(parsed.Resource, Is.EqualTo("amqps://some.namespace.com/hubName"), "The resource should match."); + Assert.That(parsed.KeyName, Is.EqualTo(keyName), "The key name should have been parsed."); + Assert.That(parsed.ExpirationTime, Is.EqualTo(expiration).Within(TimeSpan.FromSeconds(5)), "The expiration should be parsed."); } ///