Skip to content

Commit

Permalink
(service-client): Refactor and add sas credential (#1786)
Browse files Browse the repository at this point in the history
  • Loading branch information
vinagesh committed Mar 22, 2021
1 parent bcb45ff commit 189dff2
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 35 deletions.
21 changes: 3 additions & 18 deletions common/src/service/IotHubConnectionString.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ namespace Microsoft.Azure.Devices
/// <summary>
/// The properties required for authentication to IoT hub using a connection string.
/// </summary>
internal sealed class IotHubConnectionString : IotHubCredential
internal sealed class IotHubConnectionString
: IotHubConnectionProperties
{
private static readonly TimeSpan _tokenTimeToLive = TimeSpan.FromHours(1);

public IotHubConnectionString(IotHubConnectionStringBuilder builder) : base(builder.HostName)
public IotHubConnectionString(IotHubConnectionStringBuilder builder) : base(builder?.HostName)
{
if (builder == null)
{
Expand All @@ -29,9 +30,6 @@ public IotHubConnectionString(IotHubConnectionStringBuilder builder) : base(buil
SharedAccessKeyName = builder.SharedAccessKeyName;
SharedAccessKey = builder.SharedAccessKey;
SharedAccessSignature = builder.SharedAccessSignature;
DeviceId = builder.DeviceId;
ModuleId = builder.ModuleId;
GatewayHostName = builder.GatewayHostName;
}

public string Audience { get; private set; }
Expand All @@ -42,12 +40,6 @@ public IotHubConnectionString(IotHubConnectionStringBuilder builder) : base(buil

public string SharedAccessSignature { get; private set; }

public string DeviceId { get; private set; }

public string ModuleId { get; private set; }

public string GatewayHostName { get; private set; }

public string GetPassword()
{
return string.IsNullOrWhiteSpace(SharedAccessSignature) ? BuildToken(out _) : SharedAccessSignature;
Expand Down Expand Up @@ -92,13 +84,6 @@ private string BuildToken(out TimeSpan ttl)
Target = Audience
};

if (DeviceId != null)
{
builder.Target = string.IsNullOrEmpty(ModuleId)
? "{0}/devices/{1}".FormatInvariant(Audience, WebUtility.UrlEncode(DeviceId))
: "{0}/devices/{1}/modules/{2}".FormatInvariant(Audience, WebUtility.UrlEncode(DeviceId), WebUtility.UrlEncode(ModuleId));
}

ttl = builder.TimeToLive;

return builder.ToSignature();
Expand Down
48 changes: 48 additions & 0 deletions iothub/service/src/IoTHubSasCredential.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// 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
{
/// <summary>
/// Shared access signature credential used to authenticate with IoT hub.
/// </summary>
public class IotHubSasCredential
: AzureSasCredential
{
/// <summary>
/// Creates an instance of <see cref="IotHubSasCredential"/>.
/// </summary>
/// <param name="signature">Shared access signature used to authenticate with IoT hub.</param>
/// <param name="expiresOnUtc">The shared access signature expiry in UTC.</param>
public IotHubSasCredential(string signature, DateTime expiresOnUtc) : base(signature)
{
ExpiresOnUtc = expiresOnUtc;
}

/// <summary>
/// The shared access signature expiry in UTC.
/// </summary>
public DateTime ExpiresOnUtc { get; private set; }

/// <summary>
/// Updates the shared access signature. This is intended to be used when you've
/// regenerated your shared access signature and want to update clients.
/// </summary>
/// <param name="signature">Shared access signature used to authenticate with IoT hub.</param>
/// <param name="expiresOnUtc">The shared access signature expiry in UTC.</param>
public void Update(string signature, DateTime expiresOnUtc)
{
Update(signature);
ExpiresOnUtc = expiresOnUtc;
}
}
}

#endif
4 changes: 2 additions & 2 deletions iothub/service/src/IotHubConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ internal sealed class IotHubConnection : IDisposable
private IOThreadTimer _refreshTokenTimer;
#endif

public IotHubConnection(IotHubCredential credential, AccessRights accessRights, bool useWebSocketOnly, ServiceClientTransportSettings transportSettings)
public IotHubConnection(IotHubConnectionProperties credential, AccessRights accessRights, bool useWebSocketOnly, ServiceClientTransportSettings transportSettings)
{
#if !NET451
_refreshTokenTimer = new IOThreadTimerSlim(s => ((IotHubConnection)s).OnRefreshTokenAsync(), this);
Expand All @@ -71,7 +71,7 @@ internal IotHubConnection(Func<TimeSpan, Task<AmqpSession>> onCreate, Action<Amq
_faultTolerantSession = new FaultTolerantAmqpObject<AmqpSession>(onCreate, onClose);
}

internal IotHubCredential Credential { get; private set; }
internal IotHubConnectionProperties Credential { get; private set; }

public Task OpenAsync(TimeSpan timeout)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,25 @@ namespace Microsoft.Azure.Devices
/// <summary>
/// The properties required for authentication to IoT hub that are independent of the authentication type.
/// </summary>
internal abstract class IotHubCredential
internal abstract class IotHubConnectionProperties
: IAuthorizationHeaderProvider, ICbsTokenProvider
{
private const string HostNameSeparator = ".";
private const string HttpsEndpointPrefix = "https";

// Azure.Core (used in IotHubTokenCredential) is not available in NET451.
// So we need this constructor for the build to pass.
protected IotHubCredential()
protected IotHubConnectionProperties()
{
}

protected IotHubCredential(string hostName)
protected IotHubConnectionProperties(string hostName)
{
if (string.IsNullOrWhiteSpace(hostName))
{
throw new ArgumentNullException(nameof(hostName));
}

HostName = hostName;
IotHubName = GetIotHubName(hostName);
AmqpEndpoint = new UriBuilder(CommonConstants.AmqpsScheme, HostName, AmqpConstants.DefaultSecurePort).Uri;
Expand Down Expand Up @@ -54,7 +59,7 @@ public Uri BuildLinkAddress(string path)
return builder.Uri;
}

private static string GetIotHubName(string hostName)
internal static string GetIotHubName(string hostName)
{
if (string.IsNullOrWhiteSpace(hostName))
{
Expand Down
59 changes: 59 additions & 0 deletions iothub/service/src/IotHubSasCredentialProperties.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// 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 System.Threading.Tasks;
using Microsoft.Azure.Amqp;

#if !NET451

using Azure;

#endif

namespace Microsoft.Azure.Devices
{
internal class IotHubSasCredentialProperties : IotHubConnectionProperties
{
#if NET451

public IotHubSasCredentialProperties()
{
throw new InvalidOperationException("IotHubSasCredential is not supported on NET451");
}
#else
private readonly IotHubSasCredential _credential;

public IotHubSasCredentialProperties(string hostName, IotHubSasCredential credential) : base(hostName)
{
_credential = credential;
}

#endif

public override string GetAuthorizationHeader()
{
#if NET451
throw new InvalidOperationException($"IotHubSasCredential is not supported on NET451");

#else
return _credential.Signature;
#endif
}

public override Task<CbsToken> GetTokenAsync(Uri namespaceAddress, string appliesTo, string[] requiredClaims)
{
#if NET451
throw new InvalidOperationException($"IotHubSasCredential is not supported on NET451");

#else
var token = new CbsToken(
_credential.Signature,
CbsConstants.IotHubSasTokenType,
_credential.ExpiresOnUtc);
return Task.FromResult(token);
#endif
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,23 @@ namespace Microsoft.Azure.Devices
/// <summary>
/// The properties required for authentication to IoT hub using a token credential.
/// </summary>
internal class IotHubTokenCredential : IotHubCredential
internal class IotHubTokenCrendentialProperties
: IotHubConnectionProperties
{
#if !NET451
private const string _tokenType = "jwt";
private readonly TokenCredential _credential;
#endif

#if NET451

public IotHubTokenCredential()
public IotHubTokenCrendentialProperties()
{
throw new InvalidOperationException("nameof(TokenCredential) is not supported in NET451");
throw new InvalidOperationException("TokenCredential is not supported on NET451");
}
#else
private const string _tokenType = "jwt";
private readonly TokenCredential _credential;

public IotHubTokenCredential(string hostName, TokenCredential credential) : base(hostName)
public IotHubTokenCrendentialProperties(string hostName, TokenCredential credential) : base(hostName)
{
_credential = credential;
}
Expand All @@ -40,7 +44,7 @@ public IotHubTokenCredential(string hostName, TokenCredential credential) : base
public override string GetAuthorizationHeader()
{
#if NET451
throw new InvalidOperationException($"{nameof(GetAuthorizationHeader)} is not supported on NET451");
throw new InvalidOperationException($"TokenCredential is not supported on NET451");

#else
AccessToken token = _credential.GetToken(new TokenRequestContext(), new CancellationToken());
Expand All @@ -55,7 +59,7 @@ public async override Task<CbsToken> GetTokenAsync(Uri namespaceAddress, string
{
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
#if NET451
throw new InvalidOperationException($"{nameof(GetTokenAsync)} is not supported on NET451");
throw new InvalidOperationException($"TokenCredential is not supported on NET451");

#else
AccessToken token = await _credential.GetTokenAsync(new TokenRequestContext(), new CancellationToken()).ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,7 @@ public void ServiceClient_ConnectionString_ModuleIdentity_SharedAccessKeyCredent
Assert.IsNotNull(iotHubConnectionString);
Assert.AreEqual("testhub.azure-devices-int.net", iotHubConnectionString.Audience);
Assert.AreEqual("testhub.azure-devices-int.net", iotHubConnectionString.HostName);
Assert.AreEqual("edgecapabledevice1", iotHubConnectionString.DeviceId);
Assert.AreEqual("testModule", iotHubConnectionString.ModuleId);
Assert.AreEqual("dGVzdFN0cmluZzE=", iotHubConnectionString.SharedAccessKey);
Assert.AreEqual("edgehub1.ms.com", iotHubConnectionString.GatewayHostName);
Assert.IsNotNull(iotHubConnectionString.GetPassword());
}
}
Expand Down
30 changes: 30 additions & 0 deletions iothub/service/tests/IotHubConnectionPropertiesTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// 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 FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Microsoft.Azure.Devices.Tests
{
[TestClass]
[TestCategory("Unit")]
public class IotHubConnectionPropertiesTests
{
[TestMethod]
[DataRow("acme.azure-devices.net", "acme")]
[DataRow("Acme-1.azure-devices.net", "Acme-1")]
[DataRow("acme2.azure-devices.net", "acme2")]
[DataRow("3acme.azure-devices.net", "3acme")]
[DataRow("4-acme.azure-devices.net", "4-acme")]
public void IotHubConnectionPropertiesGetHubNameTest(string hostName, string expectedHubName)
{
// act
string hubName = IotHubConnectionProperties.GetIotHubName(hostName);

// assert
hubName.Should().Be(expectedHubName);
}
}
}

0 comments on commit 189dff2

Please sign in to comment.