Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(service-client): Update support for AzureSasCredential for a better user experience #1797

Merged
merged 1 commit into from
Feb 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty cool use of linq here!

{
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 @@ -101,14 +101,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
}
}