Skip to content

Commit

Permalink
fix(service-client): Support for AzureSasCredential for a better user…
Browse files Browse the repository at this point in the history
… experience (#1797)
  • Loading branch information
vinagesh committed Mar 24, 2021
1 parent bd2b67b commit 14b370f
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 57 deletions.
48 changes: 0 additions & 48 deletions iothub/service/src/IoTHubSasCredential.cs

This file was deleted.

26 changes: 23 additions & 3 deletions iothub/service/src/IotHubSasCredentialProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.Amqp;
using System.Globalization;
using System.Linq;

#if !NET451

Expand All @@ -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;
}
Expand All @@ -48,10 +50,28 @@ public override Task<CbsToken> 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=<Signature>&se=<ExpiresOnValue>[&skn=<KeyName>]
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
}
Expand Down
4 changes: 2 additions & 2 deletions iothub/service/src/JobClient/JobClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,12 @@ public static JobClient Create(
/// Creates an instance of <see cref="JobClient"/>.
/// </summary>
/// <param name="hostName">IoT hub host name.</param>
/// <param name="credential">Credential that generates a SAS token to authenticate with IoT hub. See <see cref="IotHubSasCredential"/>.</param>
/// <param name="credential">Credential that generates a SAS token to authenticate with IoT hub. See <see cref="AzureSasCredential"/>.</param>
/// <param name="transportSettings">The HTTP transport settings.</param>
/// <returns>An instance of <see cref="JobClient"/>.</returns>
public static JobClient Create(
string hostName,
IotHubSasCredential credential,
AzureSasCredential credential,
HttpTransportSettings transportSettings = default)
{
if (string.IsNullOrEmpty(hostName))
Expand Down
4 changes: 2 additions & 2 deletions iothub/service/src/RegistryManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,12 @@ public static RegistryManager Create(
/// Creates an instance of <see cref="RegistryManager"/>.
/// </summary>
/// <param name="hostName">IoT hub host name.</param>
/// <param name="credential">Credential that generates a SAS token to authenticate with IoT hub. See <see cref="IotHubSasCredential"/>.</param>
/// <param name="credential">Credential that generates a SAS token to authenticate with IoT hub. See <see cref="AzureSasCredential"/>.</param>
/// <param name="transportSettings">The HTTP transport settings.</param>
/// <returns>An instance of <see cref="RegistryManager"/>.</returns>
public static RegistryManager Create(
string hostName,
IotHubSasCredential credential,
AzureSasCredential credential,
HttpTransportSettings transportSettings = default)
{
if (string.IsNullOrEmpty(hostName))
Expand Down
4 changes: 2 additions & 2 deletions iothub/service/src/ServiceClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,14 @@ public static ServiceClient Create(
/// Creates a <see cref="ServiceClient"/> using SAS token and the specified transport type.
/// </summary>
/// <param name="hostName">IoT hub host name.</param>
/// <param name="credential">Credential that generates a SAS token to authenticate with IoT hub. See <see cref="IotHubSasCredential"/>.</param>
/// <param name="credential">Credential that generates a SAS token to authenticate with IoT hub. See <see cref="AzureSasCredential"/>.</param>
/// <param name="transportType">Specifies whether Amqp or Amqp_WebSocket_Only transport is used.</param>
/// <param name="transportSettings">Specifies the AMQP_WS and HTTP proxy settings for service client.</param>
/// <param name="options">The options that allow configuration of the service client instance during initialization.</param>
/// <returns>An instance of <see cref="ServiceClient"/>.</returns>
public static ServiceClient Create(
string hostName,
IotHubSasCredential credential,
AzureSasCredential credential,
TransportType transportType,
ServiceClientTransportSettings transportSettings = default,
ServiceClientOptions options = default)
Expand Down
121 changes: 121 additions & 0 deletions iothub/service/tests/IotHubSasCredentialPropertiesTests.cs
Original file line number Diff line number Diff line change
@@ -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
}
}

0 comments on commit 14b370f

Please sign in to comment.