From 7a26eda629b6b1b564535d2476423ef7fad56dc6 Mon Sep 17 00:00:00 2001 From: dylanbulfinMS <95251881+dylanbulfinMS@users.noreply.github.com> Date: Mon, 10 Jan 2022 17:21:11 -0500 Subject: [PATCH] Adding RBAC support for provisioning SDK (#2262) * Added types for different credentials * Altered code to work with new types * Fixed Managers * removed unneeded #if !NET451 * Filled in method summaries * refactored GetHeaderProvider * Added documentation * Formatting fixes to address comments --- .../src/Auth/ProvisioningSasCredential.cs | 27 +++ .../src/Auth/ProvisioningTokenCredential.cs | 43 +++++ provisioning/service/src/Auth/TokenHelper.cs | 21 +++ .../src/Manager/EnrollmentGroupManager.cs | 5 +- .../Manager/IndividualEnrollmentManager.cs | 5 +- .../src/Manager/RegistrationStatusManager.cs | 6 +- ....Azure.Devices.Provisioning.Service.csproj | 1 + .../service/src/ProvisioningServiceClient.cs | 168 ++++++++++++++++-- provisioning/service/src/Query.cs | 65 +++++-- 9 files changed, 298 insertions(+), 43 deletions(-) create mode 100644 provisioning/service/src/Auth/ProvisioningSasCredential.cs create mode 100644 provisioning/service/src/Auth/ProvisioningTokenCredential.cs create mode 100644 provisioning/service/src/Auth/TokenHelper.cs diff --git a/provisioning/service/src/Auth/ProvisioningSasCredential.cs b/provisioning/service/src/Auth/ProvisioningSasCredential.cs new file mode 100644 index 0000000000..c003148f6c --- /dev/null +++ b/provisioning/service/src/Auth/ProvisioningSasCredential.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +using Azure; +using Microsoft.Azure.Devices.Common.Service.Auth; + +namespace Microsoft.Azure.Devices.Provisioning.Service.Auth +{ + /// + /// Allows authentication to the API using a Shared Access Key provided by custom implementation. + /// The PnP client is auto generated from swagger and needs to implement a specific class to pass to the protocol layer + /// unlike the rest of the clients which are hand-written. So, this implementation for authentication is specific to digital twin (PnP). + /// + internal class ProvisioningSasCredential: IAuthorizationHeaderProvider + { + private readonly AzureSasCredential _azureSasCredential; + + public ProvisioningSasCredential(AzureSasCredential azureSasCredential) + { + _azureSasCredential = azureSasCredential; + } + + public string GetAuthorizationHeader() + { + return _azureSasCredential.Signature; + } + } +} diff --git a/provisioning/service/src/Auth/ProvisioningTokenCredential.cs b/provisioning/service/src/Auth/ProvisioningTokenCredential.cs new file mode 100644 index 0000000000..fe05a70ae6 --- /dev/null +++ b/provisioning/service/src/Auth/ProvisioningTokenCredential.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Threading; +using Azure.Core; +using Microsoft.Azure.Devices.Common.Service.Auth; + +namespace Microsoft.Azure.Devices.Provisioning.Service.Auth +{ + /// + /// Allows authentication to the API using a JWT token generated for Azure active directory. + /// The PnP client is auto generated from swagger and needs to implement a specific class to pass to the protocol layer + /// unlike the rest of the clients which are hand-written. so, this implementation for authentication is specific to digital twin (PnP). + /// + internal class ProvisioningTokenCredential : IAuthorizationHeaderProvider + { + private readonly TokenCredential _credential; + private readonly object _tokenLock = new object(); + private AccessToken? _cachedAccessToken; + + public ProvisioningTokenCredential(TokenCredential credential) + { + _credential = credential; + } + + // The HTTP protocol uses this method to get the bearer token for authentication. + public string GetAuthorizationHeader() + { + lock (_tokenLock) + { + // A new token is generated if it is the first time or the cached token is close to expiry. + if (!_cachedAccessToken.HasValue + || TokenHelper.IsCloseToExpiry(_cachedAccessToken.Value.ExpiresOn)) + { + _cachedAccessToken = _credential.GetToken( + new TokenRequestContext(new string[] { "https://azure-devices-provisioning.net/.default" }), + new CancellationToken()); + } + } + + return $"Bearer {_cachedAccessToken.Value.Token}"; + } + } +} diff --git a/provisioning/service/src/Auth/TokenHelper.cs b/provisioning/service/src/Auth/TokenHelper.cs new file mode 100644 index 0000000000..f98c07eac3 --- /dev/null +++ b/provisioning/service/src/Auth/TokenHelper.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; + +namespace Microsoft.Azure.Devices.Provisioning.Service.Auth +{ + internal static class TokenHelper + { + /// + /// Determines if the given token expiry date time is close to expiry. The date and time is + /// considered close to expiry if it has less than 10 minutes relative to the current time. + /// + /// The token expiration date and time. + /// True if the token expiry has less than 10 minutes relative to the current time, otherwise false. + public static bool IsCloseToExpiry(DateTimeOffset expiry) + { + TimeSpan timeToExpiry = expiry - DateTimeOffset.UtcNow; + return timeToExpiry.TotalMinutes < 10; + } + } +} diff --git a/provisioning/service/src/Manager/EnrollmentGroupManager.cs b/provisioning/service/src/Manager/EnrollmentGroupManager.cs index 9cddfe1fe7..08052420f1 100644 --- a/provisioning/service/src/Manager/EnrollmentGroupManager.cs +++ b/provisioning/service/src/Manager/EnrollmentGroupManager.cs @@ -109,7 +109,8 @@ await contractApiHttp.RequestAsync( } internal static Query CreateQuery( - ServiceConnectionString provisioningConnectionString, + string hostName, + IAuthorizationHeaderProvider headerProvider, QuerySpecification querySpecification, HttpTransportSettings httpTransportSettings, CancellationToken cancellationToken, @@ -128,7 +129,7 @@ internal static Query CreateQuery( /* SRS_ENROLLMENT_GROUP_MANAGER_28_015: [The CreateQuery shall return a new Query for EnrollmentGroup.] */ - return new Query(provisioningConnectionString, ServiceName, querySpecification, httpTransportSettings, pageSize, cancellationToken); + return new Query(hostName, headerProvider, ServiceName, querySpecification, httpTransportSettings, pageSize, cancellationToken); } private static Uri GetEnrollmentUri(string enrollmentGroupId) diff --git a/provisioning/service/src/Manager/IndividualEnrollmentManager.cs b/provisioning/service/src/Manager/IndividualEnrollmentManager.cs index c4c5d40c22..35f9bad5e3 100644 --- a/provisioning/service/src/Manager/IndividualEnrollmentManager.cs +++ b/provisioning/service/src/Manager/IndividualEnrollmentManager.cs @@ -150,7 +150,8 @@ await contractApiHttp.RequestAsync( } internal static Query CreateQuery( - ServiceConnectionString provisioningConnectionString, + string hostName, + IAuthorizationHeaderProvider headerProvider, QuerySpecification querySpecification, HttpTransportSettings httpTransportSettings, CancellationToken cancellationToken, @@ -168,7 +169,7 @@ internal static Query CreateQuery( } /* SRS_INDIVIDUAL_ENROLLMENT_MANAGER_21_015: [The CreateQuery shall return a new Query for IndividualEnrollments.] */ - return new Query(provisioningConnectionString, ServiceName, querySpecification, httpTransportSettings, pageSize, cancellationToken); + return new Query(hostName, headerProvider, ServiceName, querySpecification, httpTransportSettings, pageSize, cancellationToken); } private static Uri GetEnrollmentUri(string registrationId) diff --git a/provisioning/service/src/Manager/RegistrationStatusManager.cs b/provisioning/service/src/Manager/RegistrationStatusManager.cs index cd786744ff..044b7128f2 100644 --- a/provisioning/service/src/Manager/RegistrationStatusManager.cs +++ b/provisioning/service/src/Manager/RegistrationStatusManager.cs @@ -82,7 +82,8 @@ await contractApiHttp.RequestAsync( [SuppressMessage("Microsoft.Design", "CA1068", Justification = "Public API cannot change parameter order.")] internal static Query CreateEnrollmentGroupQuery( - ServiceConnectionString provisioningConnectionString, + string hostName, + IAuthorizationHeaderProvider headerProvider, QuerySpecification querySpecification, HttpTransportSettings httpTransportSettings, CancellationToken cancellationToken, @@ -102,7 +103,8 @@ internal static Query CreateEnrollmentGroupQuery( /* SRS_REGISTRATION_STATUS_MANAGER_28_010: [The CreateQuery shall return a new Query for DeviceRegistrationState.] */ return new Query( - provisioningConnectionString, + hostName, + headerProvider, GetGetDeviceRegistrationStatus(enrollmentGroupId), querySpecification, httpTransportSettings, diff --git a/provisioning/service/src/Microsoft.Azure.Devices.Provisioning.Service.csproj b/provisioning/service/src/Microsoft.Azure.Devices.Provisioning.Service.csproj index 9e25d16aba..d1fa2e37d0 100644 --- a/provisioning/service/src/Microsoft.Azure.Devices.Provisioning.Service.csproj +++ b/provisioning/service/src/Microsoft.Azure.Devices.Provisioning.Service.csproj @@ -56,6 +56,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/provisioning/service/src/ProvisioningServiceClient.cs b/provisioning/service/src/ProvisioningServiceClient.cs index df4c53e24e..216627b733 100644 --- a/provisioning/service/src/ProvisioningServiceClient.cs +++ b/provisioning/service/src/ProvisioningServiceClient.cs @@ -5,7 +5,10 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Azure; +using Azure.Core; using Microsoft.Azure.Devices.Common.Service.Auth; +using Microsoft.Azure.Devices.Provisioning.Service.Auth; namespace Microsoft.Azure.Devices.Provisioning.Service { @@ -65,8 +68,12 @@ namespace Microsoft.Azure.Devices.Provisioning.Service public class ProvisioningServiceClient : IDisposable { private readonly ServiceConnectionString _provisioningConnectionString; + private readonly ProvisioningTokenCredential _provisioningTokenCredential; + private readonly ProvisioningSasCredential _provisioningSasCredential; private readonly IContractApiHttp _contractApiHttp; + private string _hostName; + /// /// Create a new instance of the ProvisioningServiceClient that exposes /// the API to the Device Provisioning Service. @@ -104,6 +111,75 @@ public static ProvisioningServiceClient CreateFromConnectionString(string connec return new ProvisioningServiceClient(connectionString, httpTransportSettings); } + /// + /// Create a new instance of the ProvisioningServiceClient from the provided hostName and TokenCredential + /// that exposes the API to the Device Provisioning Service. + /// + /// + /// The string that carries the hostName that will be used for this object + /// The TokenCredential that provides authentication for this object + /// The ProvisioningServiceClient with the new instance of this object. + /// if the credential is null + public static ProvisioningServiceClient Create(string hostName, TokenCredential credential) + { + return ProvisioningServiceClient.Create(hostName, credential, new HttpTransportSettings()); + } + + /// + /// Create a new instance of the ProvisioningServiceClient from the provided hostName and TokenCredential + /// that exposes the API to the Device Provisioning Service. + /// + /// + /// The string that carries the hostName that will be used for this object + /// The TokenCredential that provides authentication for this object + /// Specifies the HTTP transport settings for the request + /// The ProvisioningServiceClient with the new instance of this object. + /// if the credential is null + public static ProvisioningServiceClient Create(string hostName, TokenCredential credential, HttpTransportSettings httpTransportSettings) + { + if (credential == null) + { + throw new ArgumentNullException(nameof(credential), "Parameter cannot be null"); + } + + return new ProvisioningServiceClient(hostName, credential, httpTransportSettings); + } + + /// + /// Create a new instance of the ProvisioningServiceClient from the provided hostName and TokenCredential + /// that exposes the API to the Device Provisioning Service. + /// + /// + /// The string that carries the host name that will be used for this object + /// The AzureSasCredential that provides authentication for this object + /// The ProvisioningServiceClient with the new instance of this object. + /// if the azureSasCredential is null + public static ProvisioningServiceClient Create(string hostName, AzureSasCredential azureSasCredential) + { + return ProvisioningServiceClient.Create(hostName, azureSasCredential, new HttpTransportSettings()); + } + + /// + /// Create a new instance of the ProvisioningServiceClient from the provided hostName and TokenCredential + /// that exposes the API to the Device Provisioning Service. + /// + /// + /// The string that carries the host name that will be used for this object + /// The AzureSasCredential that provides authentication for this object + /// Specifies the HTTP transport settings for the request + /// The ProvisioningServiceClient with the new instance of this object. + /// if the azureSasCredential is null + public static ProvisioningServiceClient Create(string hostName, AzureSasCredential azureSasCredential, HttpTransportSettings httpTransportSettings) + { + if (azureSasCredential == null) + { + throw new ArgumentNullException(nameof(azureSasCredential), "Parameter cannot be null"); + } + + return new ProvisioningServiceClient(hostName, azureSasCredential, httpTransportSettings); + } + + /// /// PRIVATE CONSTRUCTOR /// @@ -121,12 +197,43 @@ private ProvisioningServiceClient(string connectionString, HttpTransportSettings /* SRS_PROVISIONING_SERVICE_CLIENT_21_003: [The constructor shall throw ArgumentException if the ProvisioningConnectionString or one of the inner Managers failed to create a new instance.] */ /* SRS_PROVISIONING_SERVICE_CLIENT_21_004: [The constructor shall create a new instance of the ContractApiHttp class using the provided connectionString.] */ _provisioningConnectionString = ServiceConnectionString.Parse(connectionString); + _hostName = _provisioningConnectionString.HostName; _contractApiHttp = new ContractApiHttp( _provisioningConnectionString.HttpsEndpoint, _provisioningConnectionString, httpTransportSettings); } + private ProvisioningServiceClient(string hostName, TokenCredential credential, HttpTransportSettings httpTransportSettings) + { + if (string.IsNullOrWhiteSpace(hostName ?? throw new ArgumentNullException(nameof(hostName)))) + { + throw new ArgumentException($"{nameof(hostName)} cannot be empty string"); + } + + _provisioningTokenCredential = new ProvisioningTokenCredential(credential); + _hostName = hostName; + _contractApiHttp = new ContractApiHttp( + new UriBuilder("https", _hostName).Uri, + _provisioningTokenCredential, + httpTransportSettings); + } + + private ProvisioningServiceClient(string hostName, AzureSasCredential azureSasCredential, HttpTransportSettings httpTransportSettings) + { + if (string.IsNullOrWhiteSpace(hostName ?? throw new ArgumentNullException(nameof(hostName)))) + { + throw new ArgumentException($"{nameof(hostName)} cannot be empty string"); + } + + _provisioningSasCredential = new ProvisioningSasCredential(azureSasCredential); + _hostName = hostName; + _contractApiHttp = new ContractApiHttp( + new UriBuilder("https", _hostName).Uri, + _provisioningSasCredential, + httpTransportSettings); + } + /// /// Dispose the Provisioning Service Client and its dependencies. /// @@ -472,7 +579,8 @@ public Query CreateIndividualEnrollmentQuery(QuerySpecification querySpecificati { /* SRS_PROVISIONING_SERVICE_CLIENT_21_014: [The CreateIndividualEnrollmentQuery shall create a new individual enrollment query by calling the CreateQuery in the IndividualEnrollmentManager.] */ return IndividualEnrollmentManager.CreateQuery( - _provisioningConnectionString, + _hostName, + GetHeaderProvider(), querySpecification, new HttpTransportSettings(), CancellationToken.None); @@ -496,7 +604,8 @@ public Query CreateIndividualEnrollmentQuery(QuerySpecification querySpecificati { /* SRS_PROVISIONING_SERVICE_CLIENT_21_014: [The CreateIndividualEnrollmentQuery shall create a new individual enrollment query by calling the CreateQuery in the IndividualEnrollmentManager.] */ return IndividualEnrollmentManager.CreateQuery( - _provisioningConnectionString, + _hostName, + GetHeaderProvider(), querySpecification, httpTransportSettings, CancellationToken.None); @@ -520,7 +629,8 @@ public Query CreateIndividualEnrollmentQuery(QuerySpecification querySpecificati { /* SRS_PROVISIONING_SERVICE_CLIENT_21_014: [The CreateIndividualEnrollmentQuery shall create a new individual enrollment query by calling the CreateQuery in the IndividualEnrollmentManager.] */ return IndividualEnrollmentManager.CreateQuery( - _provisioningConnectionString, + _hostName, + GetHeaderProvider(), querySpecification, new HttpTransportSettings(), cancellationToken); @@ -548,7 +658,8 @@ public Query CreateIndividualEnrollmentQuery(QuerySpecification querySpecificati { /* SRS_PROVISIONING_SERVICE_CLIENT_21_015: [The CreateIndividualEnrollmentQuery shall create a new individual enrollment query by calling the CreateQuery in the IndividualEnrollmentManager.] */ return IndividualEnrollmentManager.CreateQuery( - _provisioningConnectionString, + _hostName, + GetHeaderProvider(), querySpecification, new HttpTransportSettings(), CancellationToken.None, @@ -578,7 +689,8 @@ public Query CreateIndividualEnrollmentQuery(QuerySpecification querySpecificati { /* SRS_PROVISIONING_SERVICE_CLIENT_21_015: [The CreateIndividualEnrollmentQuery shall create a new individual enrollment query by calling the CreateQuery in the IndividualEnrollmentManager.] */ return IndividualEnrollmentManager.CreateQuery( - _provisioningConnectionString, + _hostName, + GetHeaderProvider(), querySpecification, new HttpTransportSettings(), cancellationToken, @@ -608,7 +720,8 @@ public Query CreateIndividualEnrollmentQuery(QuerySpecification querySpecificati { /* SRS_PROVISIONING_SERVICE_CLIENT_21_015: [The CreateIndividualEnrollmentQuery shall create a new individual enrollment query by calling the CreateQuery in the IndividualEnrollmentManager.] */ return IndividualEnrollmentManager.CreateQuery( - _provisioningConnectionString, + _hostName, + GetHeaderProvider(), querySpecification, httpTransportSettings, CancellationToken.None, @@ -855,7 +968,8 @@ public Query CreateEnrollmentGroupQuery(QuerySpecification querySpecification) { /* SRS_PROVISIONING_SERVICE_CLIENT_21_021: [The createEnrollmentGroupQuery shall create a new enrolmentGroup query by calling the createQuery in the EnrollmentGroupManager.] */ return EnrollmentGroupManager.CreateQuery( - _provisioningConnectionString, + _hostName, + GetHeaderProvider(), querySpecification, new HttpTransportSettings(), CancellationToken.None); @@ -879,7 +993,8 @@ public Query CreateEnrollmentGroupQuery(QuerySpecification querySpecification, H { /* SRS_PROVISIONING_SERVICE_CLIENT_21_021: [The createEnrollmentGroupQuery shall create a new enrolmentGroup query by calling the createQuery in the EnrollmentGroupManager.] */ return EnrollmentGroupManager.CreateQuery( - _provisioningConnectionString, + _hostName, + GetHeaderProvider(), querySpecification, httpTransportSettings, CancellationToken.None); @@ -903,7 +1018,8 @@ public Query CreateEnrollmentGroupQuery(QuerySpecification querySpecification, C { /* SRS_PROVISIONING_SERVICE_CLIENT_21_021: [The createEnrollmentGroupQuery shall create a new enrolmentGroup query by calling the createQuery in the EnrollmentGroupManager.] */ return EnrollmentGroupManager.CreateQuery( - _provisioningConnectionString, + _hostName, + GetHeaderProvider(), querySpecification, new HttpTransportSettings(), cancellationToken); @@ -931,7 +1047,8 @@ public Query CreateEnrollmentGroupQuery(QuerySpecification querySpecification, i { /* SRS_PROVISIONING_SERVICE_CLIENT_21_022: [The createEnrollmentGroupQuery shall create a new enrolmentGroup query by calling the createQuery in the EnrollmentGroupManager.] */ return EnrollmentGroupManager.CreateQuery( - _provisioningConnectionString, + _hostName, + GetHeaderProvider(), querySpecification, new HttpTransportSettings(), CancellationToken.None, @@ -961,7 +1078,8 @@ public Query CreateEnrollmentGroupQuery(QuerySpecification querySpecification, i { /* SRS_PROVISIONING_SERVICE_CLIENT_21_022: [The createEnrollmentGroupQuery shall create a new enrolmentGroup query by calling the createQuery in the EnrollmentGroupManager.] */ return EnrollmentGroupManager.CreateQuery( - _provisioningConnectionString, + _hostName, + GetHeaderProvider(), querySpecification, new HttpTransportSettings(), cancellationToken, @@ -991,7 +1109,8 @@ public Query CreateEnrollmentGroupQuery(QuerySpecification querySpecification, i { /* SRS_PROVISIONING_SERVICE_CLIENT_21_022: [The createEnrollmentGroupQuery shall create a new enrolmentGroup query by calling the createQuery in the EnrollmentGroupManager.] */ return EnrollmentGroupManager.CreateQuery( - _provisioningConnectionString, + _hostName, + GetHeaderProvider(), querySpecification, httpTransportSettings, CancellationToken.None, @@ -1178,7 +1297,8 @@ public Query CreateEnrollmentGroupRegistrationStateQuery(QuerySpecification quer { /* SRS_PROVISIONING_SERVICE_CLIENT_21_027: [The createEnrollmentGroupRegistrationStateQuery shall create a new DeviceRegistrationState query by calling the createQuery in the registrationStatusManager.] */ return RegistrationStatusManager.CreateEnrollmentGroupQuery( - _provisioningConnectionString, + _hostName, + GetHeaderProvider(), querySpecification, new HttpTransportSettings(), CancellationToken.None, @@ -1203,7 +1323,8 @@ public Query CreateEnrollmentGroupRegistrationStateQuery(QuerySpecification quer { /* SRS_PROVISIONING_SERVICE_CLIENT_21_027: [The createEnrollmentGroupRegistrationStateQuery shall create a new DeviceRegistrationState query by calling the createQuery in the registrationStatusManager.] */ return RegistrationStatusManager.CreateEnrollmentGroupQuery( - _provisioningConnectionString, + _hostName, + GetHeaderProvider(), querySpecification, httpTransportSettings, CancellationToken.None, @@ -1231,7 +1352,8 @@ public Query CreateEnrollmentGroupRegistrationStateQuery( { /* SRS_PROVISIONING_SERVICE_CLIENT_21_027: [The createEnrollmentGroupRegistrationStateQuery shall create a new DeviceRegistrationState query by calling the createQuery in the registrationStatusManager.] */ return RegistrationStatusManager.CreateEnrollmentGroupQuery( - _provisioningConnectionString, + _hostName, + GetHeaderProvider(), querySpecification, new HttpTransportSettings(), cancellationToken, @@ -1261,7 +1383,8 @@ public Query CreateEnrollmentGroupRegistrationStateQuery(QuerySpecification quer { /* SRS_PROVISIONING_SERVICE_CLIENT_21_028: [The createEnrollmentGroupRegistrationStateQuery shall create a new DeviceRegistrationState query by calling the createQuery in the registrationStatusManager.] */ return RegistrationStatusManager.CreateEnrollmentGroupQuery( - _provisioningConnectionString, + _hostName, + GetHeaderProvider(), querySpecification, new HttpTransportSettings(), CancellationToken.None, @@ -1297,7 +1420,8 @@ public Query CreateEnrollmentGroupRegistrationStateQuery( { /* SRS_PROVISIONING_SERVICE_CLIENT_21_028: [The createEnrollmentGroupRegistrationStateQuery shall create a new DeviceRegistrationState query by calling the createQuery in the registrationStatusManager.] */ return RegistrationStatusManager.CreateEnrollmentGroupQuery( - _provisioningConnectionString, + _hostName, + GetHeaderProvider(), querySpecification, httpTransportSettings, CancellationToken.None, @@ -1333,7 +1457,8 @@ public Query CreateEnrollmentGroupRegistrationStateQuery( { /* SRS_PROVISIONING_SERVICE_CLIENT_21_028: [The createEnrollmentGroupRegistrationStateQuery shall create a new DeviceRegistrationState query by calling the createQuery in the registrationStatusManager.] */ return RegistrationStatusManager.CreateEnrollmentGroupQuery( - _provisioningConnectionString, + _hostName, + GetHeaderProvider(), querySpecification, new HttpTransportSettings(), cancellationToken, @@ -1368,5 +1493,12 @@ public Task GetEnrollmentGroupAttestationAsync(string enro { return EnrollmentGroupManager.GetEnrollmentAttestationAsync(_contractApiHttp, enrollmentGroupId, cancellationToken); } + + internal IAuthorizationHeaderProvider GetHeaderProvider() + { + return _provisioningConnectionString + ?? (IAuthorizationHeaderProvider) _provisioningTokenCredential + ?? _provisioningSasCredential; + } } } diff --git a/provisioning/service/src/Query.cs b/provisioning/service/src/Query.cs index 4b449710ab..183556ec06 100644 --- a/provisioning/service/src/Query.cs +++ b/provisioning/service/src/Query.cs @@ -75,50 +75,86 @@ internal Query( int pageSize, CancellationToken cancellationToken) { - /* SRS_QUERY_21_001: [The constructor shall throw ArgumentNullException if the provided serviceConnectionString is null.] */ if (serviceConnectionString == null) { throw new ArgumentNullException(nameof(serviceConnectionString)); } - /* SRS_QUERY_21_002: [The constructor shall throw ArgumentException if the provided serviceName is null or empty.] */ if (string.IsNullOrWhiteSpace(serviceName ?? throw new ArgumentNullException(nameof(serviceName)))) { throw new ArgumentException($"{nameof(serviceName)} cannot be an empty string"); } - /* SRS_QUERY_21_003: [The constructor shall throw ArgumentException if the provided querySpecification is null.] */ if (querySpecification == null) { throw new ArgumentNullException(nameof(querySpecification)); } - /* SRS_QUERY_21_004: [The constructor shall throw ArgumentException if the provided pageSize is negative.] */ if (pageSize < 0) { throw new ArgumentException($"{nameof(pageSize)} cannot be negative."); } // TODO: Refactor ContractApiHttp being created again - /* SRS_QUERY_21_005: [The constructor shall create and store a `contractApiHttp` using the provided Service Connection String.] */ _contractApiHttp = new ContractApiHttp( serviceConnectionString.HttpsEndpoint, serviceConnectionString, httpTransportSettings); - /* SRS_QUERY_21_006: [The constructor shall store the provided `pageSize`, and `cancelationToken`.] */ PageSize = pageSize; _cancellationToken = cancellationToken; - /* SRS_QUERY_21_007: [The constructor shall create and store a JSON from the provided querySpecification.] */ _querySpecificationJson = JsonConvert.SerializeObject(querySpecification); - /* SRS_QUERY_21_008: [The constructor shall create and store a queryPath adding `/query` to the provided `targetPath`.] */ _queryPath = GetQueryUri(serviceName); - /* SRS_QUERY_21_009: [The constructor shall set continuationToken and current as null.] */ ContinuationToken = null; - /* SRS_QUERY_21_010: [The constructor shall set hasNext as true.] */ + _hasNext = true; + } + + internal Query( + string hostName, + IAuthorizationHeaderProvider headerProvider, + string serviceName, + QuerySpecification querySpecification, + HttpTransportSettings httpTransportSettings, + int pageSize, + CancellationToken cancellationToken) + { + if (hostName == null) + { + throw new ArgumentNullException(nameof(hostName)); + } + + if (string.IsNullOrWhiteSpace(serviceName ?? throw new ArgumentNullException(nameof(serviceName)))) + { + throw new ArgumentException($"{nameof(serviceName)} cannot be an empty string"); + } + + if (querySpecification == null) + { + throw new ArgumentNullException(nameof(querySpecification)); + } + + if (pageSize < 0) + { + throw new ArgumentException($"{nameof(pageSize)} cannot be negative."); + } + + // TODO: Refactor ContractApiHttp being created again + _contractApiHttp = new ContractApiHttp( + new UriBuilder("https", hostName).Uri, + headerProvider, httpTransportSettings); + + PageSize = pageSize; + _cancellationToken = cancellationToken; + + _querySpecificationJson = JsonConvert.SerializeObject(querySpecification); + + _queryPath = GetQueryUri(serviceName); + + ContinuationToken = null; + _hasNext = true; } @@ -155,17 +191,14 @@ public bool HasNext() /// if the query does no have more pages to return. public async Task NextAsync(string continuationToken) { - /* SRS_QUERY_21_011: [The next shall throw IndexOutOfRangeException if the provided continuationToken is null or empty.] */ if (string.IsNullOrWhiteSpace(continuationToken)) { throw new IndexOutOfRangeException($"There is no {nameof(continuationToken)} to get pending elements."); } - /* SRS_QUERY_21_012: [The next shall store the provided continuationToken.] */ ContinuationToken = continuationToken; _hasNext = true; - /* SRS_QUERY_21_013: [The next shall return the next page of results by calling the next().] */ return await NextAsync().ConfigureAwait(false); } @@ -176,25 +209,21 @@ public async Task NextAsync(string continuationToken) /// if the query does no have more pages to return. public async Task NextAsync() { - /* SRS_QUERY_21_014: [The next shall throw IndexOutOfRangeException if the hasNext is false.] */ if (!_hasNext) { throw new IndexOutOfRangeException("There are no more pending elements"); } - /* SRS_QUERY_21_015: [If the pageSize is not 0, the next shall send the HTTP request with `x-ms-max-item-count=[pageSize]` in the header.] */ IDictionary headerParameters = new Dictionary(); if (PageSize != 0) { headerParameters.Add(PageSizeHeaderKey, PageSize.ToString(CultureInfo.InvariantCulture)); } - /* SRS_QUERY_21_016: [If the continuationToken is not null or empty, the next shall send the HTTP request with `x-ms-continuation=[continuationToken]` in the header.] */ if (!string.IsNullOrWhiteSpace(ContinuationToken)) { headerParameters.Add(ContinuationTokenHeaderKey, ContinuationToken); } - /* SRS_QUERY_21_017: [The next shall send a HTTP request with a HTTP verb `POST`.] */ ContractApiResponse httpResponse = await _contractApiHttp.RequestAsync( HttpMethod.Post, _queryPath, @@ -203,12 +232,10 @@ public async Task NextAsync() null, _cancellationToken).ConfigureAwait(false); - /* SRS_QUERY_21_018: [The next shall create and return a new instance of the QueryResult using the `x-ms-item-type` as type, `x-ms-continuation` as the next continuationToken, and the message body.] */ httpResponse.Fields.TryGetValue(ItemTypeHeaderKey, out string type); httpResponse.Fields.TryGetValue(ContinuationTokenHeaderKey, out string continuationToken); ContinuationToken = continuationToken; - /* SRS_QUERY_21_017: [The next shall set hasNext as true if the continuationToken is not null, or false if it is null.] */ _hasNext = (ContinuationToken != null); var result = new QueryResult(type, httpResponse.Body, ContinuationToken);