diff --git a/iothub/service/src/IoTHubSasCredential.cs b/iothub/service/src/IoTHubSasCredential.cs
deleted file mode 100644
index a21179f98b..0000000000
--- a/iothub/service/src/IoTHubSasCredential.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-#if !NET451
-
-using Azure;
-
-namespace Microsoft.Azure.Devices
-{
- ///
- /// Shared access signature credential used to authenticate with IoT hub.
- ///
- public class IotHubSasCredential
- : AzureSasCredential
- {
- ///
- /// Creates an instance of .
- ///
- /// Shared access signature used to authenticate with IoT hub.
- /// The shared access signature expiry in UTC.
- public IotHubSasCredential(string signature, DateTime expiresOnUtc) : base(signature)
- {
- ExpiresOnUtc = expiresOnUtc;
- }
-
- ///
- /// The shared access signature expiry in UTC.
- ///
- public DateTime ExpiresOnUtc { get; private set; }
-
- ///
- /// Updates the shared access signature. This is intended to be used when you've
- /// regenerated your shared access signature and want to update clients.
- ///
- /// Shared access signature used to authenticate with IoT hub.
- /// The shared access signature expiry in UTC.
- public void Update(string signature, DateTime expiresOnUtc)
- {
- Update(signature);
- ExpiresOnUtc = expiresOnUtc;
- }
- }
-}
-
-#endif
diff --git a/iothub/service/src/IotHubSasCredentialProperties.cs b/iothub/service/src/IotHubSasCredentialProperties.cs
index 44b37bf9c8..756fe3865a 100644
--- a/iothub/service/src/IotHubSasCredentialProperties.cs
+++ b/iothub/service/src/IotHubSasCredentialProperties.cs
@@ -5,6 +5,8 @@
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.Amqp;
+using System.Globalization;
+using System.Linq;
#if !NET451
@@ -23,9 +25,9 @@ public IotHubSasCredentialProperties()
throw new InvalidOperationException("IotHubSasCredential is not supported on NET451");
}
#else
- private readonly IotHubSasCredential _credential;
+ private readonly AzureSasCredential _credential;
- public IotHubSasCredentialProperties(string hostName, IotHubSasCredential credential) : base(hostName)
+ public IotHubSasCredentialProperties(string hostName, AzureSasCredential credential) : base(hostName)
{
_credential = credential;
}
@@ -48,10 +50,28 @@ public override Task GetTokenAsync(Uri namespaceAddress, string applie
throw new InvalidOperationException($"IotHubSasCredential is not supported on NET451");
#else
+ // Parse the SAS token to find the expiration date and time.
+ // SharedAccessSignature sr=ENCODED(dh://myiothub.azure-devices.net/a/b/c?myvalue1=a)&sig=&se=[&skn=]
+ var tokenParts = _credential.Signature.Split('&').ToList();
+ var expiresAtTokenPart = tokenParts.Where(tokenPart => tokenPart.StartsWith("se=", StringComparison.OrdinalIgnoreCase));
+
+ if (!expiresAtTokenPart.Any())
+ {
+ throw new InvalidOperationException($"There is no expiration time on {nameof(AzureSasCredential)} signature.");
+ }
+
+ string expiresAtStr = expiresAtTokenPart.First().Split('=')[1];
+ bool isSuccess = DateTime.TryParse(expiresAtStr, out DateTime expiresAt);
+
+ if (!isSuccess)
+ {
+ throw new InvalidOperationException($"Invalid expiration time on {nameof(AzureSasCredential)} signature.");
+ }
+
var token = new CbsToken(
_credential.Signature,
CbsConstants.IotHubSasTokenType,
- _credential.ExpiresOnUtc);
+ expiresAt);
return Task.FromResult(token);
#endif
}
diff --git a/iothub/service/src/JobClient/JobClient.cs b/iothub/service/src/JobClient/JobClient.cs
index 671790e72e..6c963c30c9 100644
--- a/iothub/service/src/JobClient/JobClient.cs
+++ b/iothub/service/src/JobClient/JobClient.cs
@@ -79,12 +79,12 @@ public static JobClient Create(
/// Creates an instance of .
///
/// IoT hub host name.
- /// Credential that generates a SAS token to authenticate with IoT hub. See .
+ /// Credential that generates a SAS token to authenticate with IoT hub. See .
/// The HTTP transport settings.
/// An instance of .
public static JobClient Create(
string hostName,
- IotHubSasCredential credential,
+ AzureSasCredential credential,
HttpTransportSettings transportSettings = default)
{
if (string.IsNullOrEmpty(hostName))
diff --git a/iothub/service/src/RegistryManager.cs b/iothub/service/src/RegistryManager.cs
index 1aca479b39..3683a0e0ca 100644
--- a/iothub/service/src/RegistryManager.cs
+++ b/iothub/service/src/RegistryManager.cs
@@ -85,12 +85,12 @@ public static RegistryManager Create(
/// Creates an instance of .
///
/// IoT hub host name.
- /// Credential that generates a SAS token to authenticate with IoT hub. See .
+ /// Credential that generates a SAS token to authenticate with IoT hub. See .
/// The HTTP transport settings.
/// An instance of .
public static RegistryManager Create(
string hostName,
- IotHubSasCredential credential,
+ AzureSasCredential credential,
HttpTransportSettings transportSettings = default)
{
if (string.IsNullOrEmpty(hostName))
diff --git a/iothub/service/src/ServiceClient.cs b/iothub/service/src/ServiceClient.cs
index d1a0650735..b93224fcba 100644
--- a/iothub/service/src/ServiceClient.cs
+++ b/iothub/service/src/ServiceClient.cs
@@ -101,14 +101,14 @@ public static ServiceClient Create(
/// Creates a using SAS token and the specified transport type.
///
/// IoT hub host name.
- /// Credential that generates a SAS token to authenticate with IoT hub. See .
+ /// Credential that generates a SAS token to authenticate with IoT hub. See .
/// Specifies whether Amqp or Amqp_WebSocket_Only transport is used.
/// Specifies the AMQP_WS and HTTP proxy settings for service client.
/// The options that allow configuration of the service client instance during initialization.
/// An instance of .
public static ServiceClient Create(
string hostName,
- IotHubSasCredential credential,
+ AzureSasCredential credential,
TransportType transportType,
ServiceClientTransportSettings transportSettings = default,
ServiceClientOptions options = default)
diff --git a/iothub/service/tests/IotHubSasCredentialPropertiesTests.cs b/iothub/service/tests/IotHubSasCredentialPropertiesTests.cs
new file mode 100644
index 0000000000..45d5ed8f6e
--- /dev/null
+++ b/iothub/service/tests/IotHubSasCredentialPropertiesTests.cs
@@ -0,0 +1,121 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.Net;
+using System.Globalization;
+using System.Threading.Tasks;
+using Microsoft.Azure.Amqp;
+using FluentAssertions;
+
+#if !NET451
+
+using Azure;
+
+#endif
+
+namespace Microsoft.Azure.Devices.Tests
+{
+ [TestClass]
+ [TestCategory("Unit")]
+ public class IotHubSasCredentialPropertiesTests
+ {
+ private const string _hostName = "myiothub.azure-devices.net";
+
+#if !NET451
+
+ [TestMethod]
+ public async Task TestCbsTokenGeneration_Succeeds()
+ {
+ // arrange
+ DateTime expiresAtUtc = DateTime.UtcNow;
+ DateTime updatedExpiresAtUtc = DateTime.UtcNow.AddDays(1);
+
+ string token = string.Format(
+ CultureInfo.InvariantCulture,
+ "SharedAccessSignature sr={0}&sig={1}&se={2}",
+ WebUtility.UrlEncode(_hostName),
+ WebUtility.UrlEncode("signature"),
+ expiresAtUtc);
+
+ string updatedToken = string.Format(
+ CultureInfo.InvariantCulture,
+ "SharedAccessSignature sr={0}&sig={1}&se={2}",
+ WebUtility.UrlEncode(_hostName),
+ WebUtility.UrlEncode("signature"),
+ updatedExpiresAtUtc);
+
+ var azureSasCredential = new AzureSasCredential(token);
+ var iotHubSasCredentialProperties = new IotHubSasCredentialProperties(_hostName, azureSasCredential);
+
+ // act
+
+ CbsToken cbsToken = await iotHubSasCredentialProperties.GetTokenAsync(null, null, null).ConfigureAwait(false);
+ azureSasCredential.Update(updatedToken);
+ CbsToken updatedCbsToken = await iotHubSasCredentialProperties.GetTokenAsync(null, null, null).ConfigureAwait(false);
+
+ // assert
+ cbsToken.ExpiresAtUtc.ToString().Should().Be(expiresAtUtc.ToString());
+ updatedCbsToken.ExpiresAtUtc.ToString().Should().Be(updatedExpiresAtUtc.ToString());
+ }
+
+ [TestMethod]
+ public async Task TestCbsTokenGeneration_InvalidExpirationDateTimeFormat_Fails()
+ {
+ // arrange
+ string token = string.Format(
+ CultureInfo.InvariantCulture,
+ "SharedAccessSignature sr={0}&sig={1}&se={2}",
+ WebUtility.UrlEncode(_hostName),
+ WebUtility.UrlEncode("signature"),
+ "01:01:2021");
+
+ var azureSasCredential = new AzureSasCredential(token);
+ var iotHubSasCredentialProperties = new IotHubSasCredentialProperties(_hostName, azureSasCredential);
+
+ try
+ {
+ // act
+ await iotHubSasCredentialProperties.GetTokenAsync(null, null, null).ConfigureAwait(false);
+
+ Assert.Fail("The parsing of date time in invalid format on the SAS token should have caused an exception.");
+ }
+ catch (InvalidOperationException ex)
+ {
+ // assert
+ ex.Message.Should().Be($"Invalid expiration time on {nameof(AzureSasCredential)} signature.");
+ }
+ }
+
+ [TestMethod]
+ public async Task TestCbsTokenGeneration_MissingExpiration_Fails()
+ {
+ // arrange
+ string token = string.Format(
+ CultureInfo.InvariantCulture,
+ "SharedAccessSignature sr={0}&sig={1}",
+ WebUtility.UrlEncode(_hostName),
+ WebUtility.UrlEncode("signature"));
+
+ var azureSasCredential = new AzureSasCredential(token);
+ var iotHubSasCredentialProperties = new IotHubSasCredentialProperties(_hostName, azureSasCredential);
+
+ try
+ {
+ // act
+ await iotHubSasCredentialProperties.GetTokenAsync(null, null, null).ConfigureAwait(false);
+
+ Assert.Fail("The missing expiry on the SAS token should have caused an exception.");
+ }
+ catch (InvalidOperationException ex)
+ {
+ // assert
+ ex.Message.Should().Be($"There is no expiration time on {nameof(AzureSasCredential)} signature.");
+ }
+ }
+
+#endif
+ }
+}