From ed49d3206d378429156d41059074ebd7c13357b9 Mon Sep 17 00:00:00 2001 From: dylanbulfinMS <95251881+dylanbulfinMS@users.noreply.github.com> Date: Thu, 3 Mar 2022 13:24:37 -0500 Subject: [PATCH] Feature/DPS RBAC support (#2297) * Revert "Revert "Adding RBAC support for provisioning SDK (#2262)"" This reverts commit 1a0b9de942fff85cbc76384da297914f71cdcce0. * Revert "Revert "Adding RBAC support for provisioning SDK (#2262)"" This reverts commit 1a0b9de942fff85cbc76384da297914f71cdcce0. * Basic test layout * API update * Style updates * Fix namespace issue * Misc cleanup * Added new field * Reverted e2e script changes * Changing to * Replaced the rest * Updating ArgumentException arguments * Removed digital twin comments * Revert "Added new field" This reverts commit 60a3475ea9b50821b8949b05e932371fb007f0bb. * Added URL encoding * Added a few comments * Fixed syntax error Co-authored-by: Azad Abbasi Co-authored-by: David R. Williamson --- .../config/TestConfiguration.Provisioning.cs | 72 ++- .../provisioning/ConnectionStringParser.cs | 61 +++ .../ProvisioningServiceClientE2ETests.cs | 59 +++ .../src/Auth/ProvisioningSasCredential.cs | 25 + .../src/Auth/ProvisioningTokenCredential.cs | 41 ++ provisioning/service/src/Auth/TokenHelper.cs | 21 + provisioning/service/src/Contract/SDKUtils.cs | 2 +- .../src/Manager/EnrollmentGroupManager.cs | 5 +- .../Manager/IndividualEnrollmentManager.cs | 5 +- .../src/Manager/RegistrationStatusManager.cs | 6 +- ....Azure.Devices.Provisioning.Service.csproj | 1 + .../service/src/ProvisioningServiceClient.cs | 426 ++++++++++++------ provisioning/service/src/Query.cs | 69 ++- 13 files changed, 619 insertions(+), 174 deletions(-) create mode 100644 e2e/test/provisioning/ConnectionStringParser.cs 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/e2e/test/config/TestConfiguration.Provisioning.cs b/e2e/test/config/TestConfiguration.Provisioning.cs index a65f27e557..371e330761 100644 --- a/e2e/test/config/TestConfiguration.Provisioning.cs +++ b/e2e/test/config/TestConfiguration.Provisioning.cs @@ -1,8 +1,19 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Globalization; +using System.Net; +using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; +using System.Text; +using Microsoft.Azure.Devices.E2ETests.Provisioning; + +#if !NET451 + +using Azure.Identity; + +#endif namespace Microsoft.Azure.Devices.E2ETests { @@ -14,6 +25,34 @@ public static partial class Provisioning public static string ConnectionString => GetValue("PROVISIONING_CONNECTION_STRING"); + public static string GetProvisioningHostName() + { + var connectionString = new ConnectionStringParser(ConnectionString); + return connectionString.ProvisioningHostName; + } + +#if !NET451 + + public static ClientSecretCredential GetClientSecretCredential() + { + return new ClientSecretCredential( + GetValue("MSFT_TENANT_ID"), + GetValue("IOTHUB_CLIENT_ID"), + GetValue("IOTHUB_CLIENT_SECRET")); + } + +#endif + + public static string GetProvisioningSharedAccessSignature(TimeSpan timeToLive) + { + var connectionString = new ConnectionStringParser(ConnectionString); + return GenerateSasToken( + connectionString.ProvisioningHostName, + connectionString.SharedAccessKey, + timeToLive, + connectionString.SharedAccessKeyName); + } + public static string GlobalDeviceEndpoint => GetValue("DPS_GLOBALDEVICEENDPOINT", "global.azure-devices-provisioning.net"); @@ -38,6 +77,37 @@ public static X509Certificate2Collection GetGroupEnrollmentChain() public static string FarAwayIotHubHostName => GetValue("FAR_AWAY_IOTHUB_HOSTNAME"); public static string CustomAllocationPolicyWebhook => GetValue("CUSTOM_ALLOCATION_POLICY_WEBHOOK"); + + private static string GenerateSasToken(string resourceUri, string sharedAccessKey, TimeSpan timeToLive, string policyName = default) + { + // Calculate expiry value for token + var epochTime = new DateTime(1970, 1, 1); + DateTime expiresOn = DateTime.UtcNow.Add(timeToLive); + TimeSpan secondsFromEpochTime = expiresOn.Subtract(epochTime); + long seconds = Convert.ToInt64(secondsFromEpochTime.TotalSeconds, CultureInfo.InvariantCulture); + string expiry = Convert.ToString(seconds, CultureInfo.InvariantCulture); + + string stringToSign = WebUtility.UrlEncode(resourceUri) + "\n" + expiry; + + using var hmac = new HMACSHA256(Convert.FromBase64String(sharedAccessKey)); + string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign))); + + // SharedAccessSignature sr=ENCODED(dh://mydps.azure-devices-provisioning.net/a/b/c?myvalue1=a)&sig=&se=[&skn=] + string token = string.Format( + CultureInfo.InvariantCulture, + "SharedAccessSignature sr={0}&sig={1}&se={2}", + WebUtility.UrlEncode(resourceUri), + WebUtility.UrlEncode(signature), + expiry); + + // add policy name only if user chooses to include it + if (!string.IsNullOrWhiteSpace(policyName)) + { + token += "&skn=" + WebUtility.UrlEncode(policyName); + } + + return token; + } } } } diff --git a/e2e/test/provisioning/ConnectionStringParser.cs b/e2e/test/provisioning/ConnectionStringParser.cs new file mode 100644 index 0000000000..f3df244da7 --- /dev/null +++ b/e2e/test/provisioning/ConnectionStringParser.cs @@ -0,0 +1,61 @@ +using System; + +namespace Microsoft.Azure.Devices.E2ETests.Provisioning +{ + internal class ConnectionStringParser + { + public string ProvisioningHostName { get; private set; } + + public string DeviceId { get; private set; } + + public string SharedAccessKey { get; private set; } + + public string SharedAccessKeyName { get; private set; } + + public ConnectionStringParser(string connectionString) + { + if (string.IsNullOrWhiteSpace(connectionString)) + { + throw new ArgumentException(nameof(connectionString), "Parameter cannot be null, empty, or whitespace."); + } + + // Connection string sections are demarcated with semicolon + string[] parts = connectionString.Split(';'); + + foreach (string part in parts) + { + int separatorIndex = part.IndexOf('='); + if (separatorIndex < 0) + { + throw new ArgumentException($"Improperly formatted key/value pair: {part}."); + } + + string key = part.Substring(0, separatorIndex); + string value = part.Substring(separatorIndex + 1); + + switch (key.ToUpperInvariant()) + { + case "HOSTNAME": + // Gives the correct Host Name to send requests to + ProvisioningHostName = value; + break; + + case "SHAREDACCESSKEY": + SharedAccessKey = value; + break; + + case "DEVICEID": + DeviceId = value; + break; + + case "SHAREDACCESSKEYNAME": + SharedAccessKeyName = value; + break; + + default: + throw new NotSupportedException($"Unrecognized tag found in parameter {nameof(connectionString)}."); + } + } + } + } +} diff --git a/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs b/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs index 1ada9be022..e847566278 100644 --- a/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs +++ b/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.Net; using System.Threading.Tasks; +using Azure; +using FluentAssertions; using Microsoft.Azure.Devices.Provisioning.Security.Samples; using Microsoft.Azure.Devices.Provisioning.Service; using Microsoft.Azure.Devices.Shared; @@ -110,6 +112,63 @@ public async Task ProvisioningServiceClient_GetEnrollmentGroupAttestation_Symmet await ProvisioningServiceClient_GetEnrollmentGroupAttestation(AttestationMechanismType.SymmetricKey); } + [LoggedTestMethod] + public async Task ProvisioningServiceClient_TokenCredentialAuth_Success() + { + // arrange + using var provisioningServiceClient = ProvisioningServiceClient.Create( + TestConfiguration.Provisioning.GetProvisioningHostName(), + TestConfiguration.Provisioning.GetClientSecretCredential()); + + IndividualEnrollment individualEnrollment = await CreateIndividualEnrollment( + provisioningServiceClient, + AttestationMechanismType.SymmetricKey, + null, + AllocationPolicy.Static, + null, + null, + null) + .ConfigureAwait(false); + + // act + IndividualEnrollment individualEnrollmentResult = await provisioningServiceClient.CreateOrUpdateIndividualEnrollmentAsync(individualEnrollment); + + // assert + individualEnrollmentResult.Should().NotBeNull(); + + // cleanup + await provisioningServiceClient.DeleteIndividualEnrollmentAsync(individualEnrollment.RegistrationId); + } + + [LoggedTestMethod] + public async Task ProvisioningServiceClient_AzureSasCredentialAuth_Success() + { + // arrange + string signature = TestConfiguration.Provisioning.GetProvisioningSharedAccessSignature(TimeSpan.FromHours(1)); + using var provisioningServiceClient = ProvisioningServiceClient.Create( + TestConfiguration.Provisioning.GetProvisioningHostName(), + new AzureSasCredential(signature)); + + IndividualEnrollment individualEnrollment = await CreateIndividualEnrollment( + provisioningServiceClient, + AttestationMechanismType.SymmetricKey, + null, + AllocationPolicy.Static, + null, + null, + null) + .ConfigureAwait(false); + + // act + IndividualEnrollment individualEnrollmentResult = await provisioningServiceClient.CreateOrUpdateIndividualEnrollmentAsync(individualEnrollment); + + // assert + individualEnrollmentResult.Should().NotBeNull(); + + // cleanup + await provisioningServiceClient.DeleteIndividualEnrollmentAsync(individualEnrollment.RegistrationId); + } + public async Task ProvisioningServiceClient_GetIndividualEnrollmentAttestation(AttestationMechanismType attestationType) { using var provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); diff --git a/provisioning/service/src/Auth/ProvisioningSasCredential.cs b/provisioning/service/src/Auth/ProvisioningSasCredential.cs new file mode 100644 index 0000000000..6fae68dc38 --- /dev/null +++ b/provisioning/service/src/Auth/ProvisioningSasCredential.cs @@ -0,0 +1,25 @@ +// 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. + /// + 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..699be86b49 --- /dev/null +++ b/provisioning/service/src/Auth/ProvisioningTokenCredential.cs @@ -0,0 +1,41 @@ +// 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. + /// + 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/Contract/SDKUtils.cs b/provisioning/service/src/Contract/SDKUtils.cs index c21488deaf..82a2b31a4e 100644 --- a/provisioning/service/src/Contract/SDKUtils.cs +++ b/provisioning/service/src/Contract/SDKUtils.cs @@ -5,7 +5,7 @@ namespace Microsoft.Azure.Devices.Provisioning.Service { internal class SDKUtils { - private const string ApiVersionProvisioning = "2019-03-31"; + private const string ApiVersionProvisioning = "2021-10-01"; public const string ApiVersionQueryString = CustomHeaderConstants.ApiVersion + "=" + ApiVersionProvisioning; } } 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 0034f9dbe8..27116e9e94 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..ac8ad09830 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 { @@ -21,10 +24,10 @@ namespace Microsoft.Azure.Devices.Provisioning.Service /// Provisioning Service, it translate the rest API in C# Objects and Methods. /// /// To use the this SDK, you must include the follow package on your application. - /// + /// /// // Include the following using to use the Device Provisioning Service APIs. /// using Microsoft.Azure.Devices.Provisioning.Service; - /// + /// /// /// The main APIs are exposed by the , it contains the public Methods that the /// application shall call to create and maintain the Enrollments. The Objects in the configs package shall @@ -40,7 +43,7 @@ namespace Microsoft.Azure.Devices.Provisioning.Service /// This C# SDK can be represented in the follow diagram, the first layer are the public APIs the your application /// shall use: /// - /// + /// /// +===============+ +==========================================+ +============+ +===+ /// | configs |------>| ProvisioningServiceClient | +->| Query | | | /// +===============+ +==+=================+==================+==+ | +======+=====+ | e | @@ -60,15 +63,19 @@ namespace Microsoft.Azure.Devices.Provisioning.Service /// +-------------------------------------+------------------------------------------+ /// | System.Net.Http | /// +--------------------------------------------------------------------------------+ - /// + /// /// 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 + /// Create a new instance of the ProvisioningServiceClient that exposes /// the API to the Device Provisioning Service. /// /// @@ -76,9 +83,9 @@ public class ProvisioningServiceClient : IDisposable /// Once you create a Device Provisioning Service on Azure, you can get the connection string on the Azure portal. /// /// - /// the string that cares the connection string of the Device Provisioning Service. - /// The ProvisioningServiceClient with the new instance of this object. - /// if the connectionString is null or empty. + /// the string that cares the connection string of the Device Provisioning Service. + /// The ProvisioningServiceClient with the new instance of this object. + /// if the connectionString is null or empty. public static ProvisioningServiceClient CreateFromConnectionString(string connectionString) { /* SRS_PROVISIONING_SERVICE_CLIENT_21_001: [The createFromConnectionString shall create a new instance of this class using the provided connectionString.] */ @@ -86,7 +93,7 @@ public static ProvisioningServiceClient CreateFromConnectionString(string connec } /// - /// Create a new instance of the ProvisioningServiceClient that exposes + /// Create a new instance of the ProvisioningServiceClient that exposes /// the API to the Device Provisioning Service. /// /// @@ -94,39 +101,143 @@ public static ProvisioningServiceClient CreateFromConnectionString(string connec /// Once you create a Device Provisioning Service on Azure, you can get the connection string on the Azure portal. /// /// - /// the string that cares the connection string of the Device Provisioning Service. + /// the string that cares the connection string of the Device Provisioning Service. /// Specifies the HTTP transport settings for the request - /// The ProvisioningServiceClient with the new instance of this object. - /// if the connectionString is null or empty. + /// The ProvisioningServiceClient with the new instance of this object. + /// if the connectionString is null or empty. public static ProvisioningServiceClient CreateFromConnectionString(string connectionString, HttpTransportSettings httpTransportSettings) { /* SRS_PROVISIONING_SERVICE_CLIENT_21_001: [The createFromConnectionString shall create a new instance of this class using the provided connectionString.] */ 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 /// - /// the string that contains the connection string for the Provisioning service. + /// the string that contains the connection string for the Provisioning service. /// Specifies the HTTP transport settings for the request - /// if the connectionString is null, empty, or invalid. + /// if the connectionString is null, empty, or invalid. private ProvisioningServiceClient(string connectionString, HttpTransportSettings httpTransportSettings) { /* SRS_PROVISIONING_SERVICE_CLIENT_21_002: [The constructor shall throw ArgumentException if the provided connectionString is null or empty.] */ if (string.IsNullOrWhiteSpace(connectionString ?? throw new ArgumentNullException(nameof(connectionString)))) { - throw new ArgumentException($"{nameof(connectionString)} cannot be empty string"); + throw new ArgumentException("Connection string cannot be empty", nameof(connectionString)); } /* 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("Host name cannot be empty", nameof(hostName)); + } + + _provisioningTokenCredential = new ProvisioningTokenCredential(credential); + _hostName = hostName; + + // Create new ContractApiHttp using provided TokenCredential and hostname instead of standard connection string + _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("Host name cannot be empty", nameof(hostName)); + } + + _provisioningSasCredential = new ProvisioningSasCredential(azureSasCredential); + _hostName = hostName; + + // Create new ContractApiHttp using provided AzureSasCredential and hostname instead of standard connection string + _contractApiHttp = new ContractApiHttp( + new UriBuilder("https", _hostName).Uri, + _provisioningSasCredential, + httpTransportSettings); + } + /// /// Dispose the Provisioning Service Client and its dependencies. /// @@ -164,14 +275,14 @@ protected virtual void Dispose(bool disposing) /// individualEnrollment will not change the status of the device that was already registered using the old individualEnrollment. /// /// To use the Device Provisioning Service API, you must include the follow package on your application. - /// + /// /// // Include the following using to use the Device Provisioning Service APIs. /// using Microsoft.Azure.Devices.Provisioning.Service; - /// + /// /// /// /// The follow code will create a new individualEnrollment that will provisioning the registrationid-1 using TPM attestation. - /// + /// /// // IndividualEnrollment information. /// private const string PROVISIONING_CONNECTION_STRING = "HostName=ContosoProvisioning.azure-devices-provisioning.net;" + /// "SharedAccessKeyName=contosoprovisioningserviceowner;" + @@ -202,10 +313,10 @@ protected virtual void Dispose(bool disposing) /// Console.WriteLine("\nIndividualEnrollment created with success..."); /// } /// } - /// + /// /// /// The follow code will update the provisioningStatus of the previous individualEnrollment from disabled to enabled. - /// + /// /// // IndividualEnrollment information. /// private const string PROVISIONING_CONNECTION_STRING = "HostName=ContosoProvisioning.azure-devices-provisioning.net;" + /// "SharedAccessKeyName=contosoprovisioningserviceowner;" + @@ -233,10 +344,10 @@ protected virtual void Dispose(bool disposing) /// Console.WriteLine("\nIndividualEnrollment updated with success..."); /// } /// } - /// + /// /// /// - /// the object that describes the individualEnrollment that will be created of updated. It cannot be null. + /// the object that describes the individualEnrollment that will be created of updated. It cannot be null. /// An object with the result of the create or update requested. /// if the provided parameter is not correct. /// if the SDK failed to send the request to the Device Provisioning Service. @@ -267,8 +378,8 @@ public Task CreateOrUpdateIndividualEnrollmentAsync(Indivi /// is determined by , and can be 'create', 'update', 'updateIfMatchETag', or 'delete'. /// /// - /// the that defines the single operation to do over the individualEnrollments. It cannot be null. - /// the collection of that contains the description of each individualEnrollment. It cannot be null or empty. + /// the that defines the single operation to do over the individualEnrollments. It cannot be null. + /// the collection of that contains the description of each individualEnrollment. It cannot be null or empty. /// A object with the result of operation for each enrollment. /// if the provided parameters are not correct. /// if the SDK failed to send the request to the Device Provisioning Service. @@ -288,8 +399,8 @@ public Task RunBulkEnrollmentOperationAsync( /// is determined by , and can be 'create', 'update', 'updateIfMatchETag', or 'delete'. /// /// - /// the that defines the single operation to do over the individualEnrollments. It cannot be null. - /// the collection of that contains the description of each individualEnrollment. It cannot be null or empty. + /// the that defines the single operation to do over the individualEnrollments. It cannot be null. + /// the collection of that contains the description of each individualEnrollment. It cannot be null or empty. /// The cancellation token. /// A object with the result of operation for each enrollment. /// if the provided parameters are not correct. @@ -312,7 +423,7 @@ public Task RunBulkEnrollmentOperationAsync( /// /// If the registrationId do not exists, this method will throw . /// - /// the string that identifies the individualEnrollment. It cannot be null or empty. + /// the string that identifies the individualEnrollment. It cannot be null or empty. /// The with the content of the individualEnrollment in the Provisioning Device Service. /// if the provided parameter is not correct. /// if the SDK failed to send the request to the Device Provisioning Service. @@ -342,14 +453,14 @@ public Task GetIndividualEnrollmentAsync(string registrati /// This method will remove the individualEnrollment from the Device Provisioning Service using the /// provided information. The Device Provisioning Service will care about the /// registrationId and the eTag on the individualEnrollment. If you want to delete the individualEnrollment regardless the - /// eTag, you can set the eTag="*"} into the individualEnrollment, or use the {@link #deleteIndividualEnrollment(string) + /// eTag, you can set the eTag="*"} into the individualEnrollment, or use the {@link #deleteIndividualEnrollment(string) /// passing only the registrationId. /// /// Note that delete the individualEnrollment will not remove the Device itself from the IotHub. /// /// If the registrationId does not exists or the eTag not matches, this method will throw . /// - /// the that identifies the individualEnrollment. It cannot be null. + /// the that identifies the individualEnrollment. It cannot be null. /// if the provided parameter is not correct. /// if the SDK failed to send the request to the Device Provisioning Service. /// if the Device Provisioning Service was not able to execute the bulk operation. @@ -377,13 +488,13 @@ public Task DeleteIndividualEnrollmentAsync(IndividualEnrollment individualEnrol /// /// This method will remove the individualEnrollment from the Device Provisioning Service using the /// provided registrationId. It will delete the enrollment regardless the eTag. It means that this API - /// correspond to the with the eTag="*". + /// correspond to the with the eTag="*". /// /// Note that delete the enrollment will not remove the Device itself from the IotHub. /// /// If the registrationId does not exists, this method will throw . /// - /// the string} that identifies the individualEnrollment. It cannot be {@code null or empty. + /// the string} that identifies the individualEnrollment. It cannot be {@code null or empty. /// if the provided registrationId is not correct. /// if the SDK failed to send the request to the Device Provisioning Service. /// if the Device Provisioning Service was not able to execute the bulk operation. @@ -399,13 +510,13 @@ public Task DeleteIndividualEnrollmentAsync(string registrationId) /// /// This method will remove the individualEnrollment from the Device Provisioning Service using the /// provided registrationId. It will delete the enrollment regardless the eTag. It means that this API - /// correspond to the with the eTag="*". + /// correspond to the with the eTag="*". /// /// Note that delete the enrollment will not remove the Device itself from the IotHub. /// /// If the registrationId does not exists, this method will throw . /// - /// the string} that identifies the individualEnrollment. It cannot be {@code null or empty. + /// the string} that identifies the individualEnrollment. It cannot be {@code null or empty. /// The cancellation token. /// if the provided registrationId is not correct. /// if the SDK failed to send the request to the Device Provisioning Service. @@ -422,16 +533,16 @@ public Task DeleteIndividualEnrollmentAsync(string registrationId, CancellationT /// /// This method will remove the individualEnrollment from the Device Provisioning Service using the /// provided registrationId and eTag. If you want to delete the enrollment regardless the eTag, you can - /// use or you can pass the eTag as null, empty, or - /// "*". + /// use or you can pass the eTag as null, empty, or + /// "*". /// /// Note that delete the enrollment will not remove the Device itself from the IotHub. /// /// If the registrationId does not exists or the eTag does not matches, this method will throw /// . For more exceptions that this method can throw, please see /// - /// the string that identifies the individualEnrollment. It cannot be null or empty. - /// the string with the IndividualEnrollment eTag. It can be null or empty. + /// the string that identifies the individualEnrollment. It cannot be null or empty. + /// the string with the IndividualEnrollment eTag. It can be null or empty. /// The Device Provisioning Service will ignore it in all of these cases. /// if the provided registrationId is not correct. /// if the SDK failed to send the request to the Device Provisioning Service. @@ -463,16 +574,17 @@ public Task DeleteIndividualEnrollmentAsync(string registrationId, string eTag, /// as a iterator. /// /// The Device Provisioning Service expects a SQL query in the , for instance - /// "SELECT * FROM enrollments". + /// "SELECT * FROM enrollments". /// - /// the with the SQL query. It cannot be null. + /// the with the SQL query. It cannot be null. /// The iterator. /// if the provided parameter is not correct. public Query CreateIndividualEnrollmentQuery(QuerySpecification querySpecification) { /* 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); @@ -486,9 +598,9 @@ public Query CreateIndividualEnrollmentQuery(QuerySpecification querySpecificati /// as a iterator. /// /// The Device Provisioning Service expects a SQL query in the , for instance - /// "SELECT * FROM enrollments". + /// "SELECT * FROM enrollments". /// - /// the with the SQL query. It cannot be null. + /// the with the SQL query. It cannot be null. /// Specifies the HTTP transport settings /// The iterator. /// if the provided parameter is not correct. @@ -496,7 +608,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); @@ -510,9 +623,9 @@ public Query CreateIndividualEnrollmentQuery(QuerySpecification querySpecificati /// as a iterator. /// /// The Device Provisioning Service expects a SQL query in the , for instance - /// "SELECT * FROM enrollments". + /// "SELECT * FROM enrollments". /// - /// the with the SQL query. It cannot be null. + /// the with the SQL query. It cannot be null. /// The cancellation token. /// The iterator. /// if the provided parameter is not correct. @@ -520,7 +633,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); @@ -534,21 +648,22 @@ public Query CreateIndividualEnrollmentQuery(QuerySpecification querySpecificati /// as a iterator. /// /// The Device Provisioning Service expects a SQL query in the , for instance - /// "SELECT * FROM enrollments". + /// "SELECT * FROM enrollments". /// /// For each iteration, the Query will return a List of objects correspondent to the query result. The maximum /// number of items per iteration can be specified by the pageSize. It is optional, you can provide 0 for /// default pageSize or use the API . /// - /// the with the SQL query. It cannot be null. - /// the int with the maximum number of items per iteration. It can be 0 for default, but not negative. + /// the with the SQL query. It cannot be null. + /// the int with the maximum number of items per iteration. It can be 0 for default, but not negative. /// The iterator. /// if the provided parameters are not correct. public Query CreateIndividualEnrollmentQuery(QuerySpecification querySpecification, int pageSize) { /* 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, @@ -563,14 +678,14 @@ public Query CreateIndividualEnrollmentQuery(QuerySpecification querySpecificati /// as a iterator. /// /// The Device Provisioning Service expects a SQL query in the , for instance - /// "SELECT * FROM enrollments". + /// "SELECT * FROM enrollments". /// /// For each iteration, the Query will return a List of objects correspondent to the query result. The maximum /// number of items per iteration can be specified by the pageSize. It is optional, you can provide 0 for /// default pageSize or use the API . /// - /// the with the SQL query. It cannot be null. - /// the int with the maximum number of items per iteration. It can be 0 for default, but not negative. + /// the with the SQL query. It cannot be null. + /// the int with the maximum number of items per iteration. It can be 0 for default, but not negative. /// The cancellation token. /// The iterator. /// if the provided parameters are not correct. @@ -578,7 +693,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, @@ -593,14 +709,14 @@ public Query CreateIndividualEnrollmentQuery(QuerySpecification querySpecificati /// as a iterator. /// /// The Device Provisioning Service expects a SQL query in the , for instance - /// "SELECT * FROM enrollments". + /// "SELECT * FROM enrollments". /// /// For each iteration, the Query will return a List of objects correspondent to the query result. The maximum /// number of items per iteration can be specified by the pageSize. It is optional, you can provide 0 for /// default pageSize or use the API . /// - /// the with the SQL query. It cannot be null. - /// the int with the maximum number of items per iteration. It can be 0 for default, but not negative. + /// the with the SQL query. It cannot be null. + /// the int with the maximum number of items per iteration. It can be 0 for default, but not negative. /// Specifies the HTTP transport settings /// The iterator. /// if the provided parameters are not correct. @@ -608,7 +724,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, @@ -625,10 +742,10 @@ public Query CreateIndividualEnrollmentQuery(QuerySpecification querySpecificati /// by the new one. On the other hand, if the enrollmentGroupId does not exit, it will be created. /// /// To use the Device Provisioning Service API, you must include the follow package on your application. - /// + /// /// // Include the following using to use the Device Provisioning Service APIs. /// using Microsoft.Azure.Devices.Provisioning.Service; - /// + /// /// /// /// the object that describes the individualEnrollment that will be created of updated. @@ -650,10 +767,10 @@ public Task CreateOrUpdateEnrollmentGroupAsync(EnrollmentGroup /// by the new one. On the other hand, if the enrollmentGroupId does not exit, it will be created. /// /// To use the Device Provisioning Service API, you must include the follow package on your application. - /// + /// /// // Include the following using to use the Device Provisioning Service APIs. /// using Microsoft.Azure.Devices.Provisioning.Service; - /// + /// /// /// /// the object that describes the individualEnrollment that will be created of updated. @@ -676,7 +793,7 @@ public Task CreateOrUpdateEnrollmentGroupAsync(EnrollmentGroup /// /// If the enrollmentGroupId does not exists, this method will throw . /// - /// the string that identifies the enrollmentGroup. It cannot be null or empty. + /// the string that identifies the enrollmentGroup. It cannot be null or empty. /// The with the content of the enrollmentGroup in the Provisioning Device Service. /// if the Provisioning Device Service was not able to retrieve the enrollmentGroup information for the provided enrollmentGroupId. public Task GetEnrollmentGroupAsync(string enrollmentGroupId) @@ -695,7 +812,7 @@ public Task GetEnrollmentGroupAsync(string enrollmentGroupId) /// /// If the enrollmentGroupId does not exists, this method will throw . /// - /// the string that identifies the enrollmentGroup. It cannot be null or empty. + /// the string that identifies the enrollmentGroup. It cannot be null or empty. /// The cancellation token. /// The with the content of the enrollmentGroup in the Provisioning Device Service. /// if the Provisioning Device Service was not able to retrieve the enrollmentGroup information for the provided enrollmentGroupId. @@ -712,14 +829,14 @@ public Task GetEnrollmentGroupAsync(string enrollmentGroupId, C /// This method will remove the enrollmentGroup from the Device Provisioning Service using the /// provided information. The Device Provisioning Service will care about the /// enrollmentGroupId and the eTag on the enrollmentGroup. If you want to delete the enrollment regardless the - /// eTag, you can set the eTag="*" into the enrollmentGroup, or use the . + /// eTag, you can set the eTag="*" into the enrollmentGroup, or use the . /// passing only the enrollmentGroupId. /// /// Note that delete the enrollmentGroup will not remove the Devices itself from the IotHub. /// /// If the enrollmentGroupId does not exists or the eTag does not matches, this method will throw . /// - /// the that identifies the enrollmentGroup. It cannot be null. + /// the that identifies the enrollmentGroup. It cannot be null. /// if the Provisioning Device Service was not able to delete the enrollmentGroup information for the provided enrollmentGroup. public Task DeleteEnrollmentGroupAsync(EnrollmentGroup enrollmentGroup) { @@ -734,14 +851,14 @@ public Task DeleteEnrollmentGroupAsync(EnrollmentGroup enrollmentGroup) /// This method will remove the enrollmentGroup from the Device Provisioning Service using the /// provided information. The Device Provisioning Service will care about the /// enrollmentGroupId and the eTag on the enrollmentGroup. If you want to delete the enrollment regardless the - /// eTag, you can set the eTag="*" into the enrollmentGroup, or use the . + /// eTag, you can set the eTag="*" into the enrollmentGroup, or use the . /// passing only the enrollmentGroupId. /// /// Note that delete the enrollmentGroup will not remove the Devices itself from the IotHub. /// /// If the enrollmentGroupId does not exists or the eTag does not matches, this method will throw . /// - /// the that identifies the enrollmentGroup. It cannot be null. + /// the that identifies the enrollmentGroup. It cannot be null. /// The cancellation token. /// if the Provisioning Device Service was not able to delete the enrollmentGroup information for the provided enrollmentGroup. public Task DeleteEnrollmentGroupAsync(EnrollmentGroup enrollmentGroup, CancellationToken cancellationToken) @@ -756,13 +873,13 @@ public Task DeleteEnrollmentGroupAsync(EnrollmentGroup enrollmentGroup, Cancella /// /// This method will remove the enrollmentGroup from the Device Provisioning Service using the /// provided enrollmentGroupId. It will delete the enrollmentGroup regardless the eTag. It means that this API - /// correspond to the with the eTag="*". + /// correspond to the with the eTag="*". /// /// Note that delete the enrollmentGroup will not remove the Devices itself from the IotHub. /// /// If the enrollmentGroupId does not exists, this method will throw . /// - /// the string that identifies the enrollmentGroup. It cannot be null or empty. + /// the string that identifies the enrollmentGroup. It cannot be null or empty. /// if the Provisioning Device Service was not able to delete the enrollmentGroup information for the provided enrollmentGroupId. public Task DeleteEnrollmentGroupAsync(string enrollmentGroupId) { @@ -776,13 +893,13 @@ public Task DeleteEnrollmentGroupAsync(string enrollmentGroupId) /// /// This method will remove the enrollmentGroup from the Device Provisioning Service using the /// provided enrollmentGroupId. It will delete the enrollmentGroup regardless the eTag. It means that this API - /// correspond to the with the eTag="*". + /// correspond to the with the eTag="*". /// /// Note that delete the enrollmentGroup will not remove the Devices itself from the IotHub. /// /// If the enrollmentGroupId does not exists, this method will throw . /// - /// the string that identifies the enrollmentGroup. It cannot be null or empty. + /// the string that identifies the enrollmentGroup. It cannot be null or empty. /// The cancellation token. /// if the Provisioning Device Service was not able to delete the enrollmentGroup information for the provided enrollmentGroupId. public Task DeleteEnrollmentGroupAsync(string enrollmentGroupId, CancellationToken cancellationToken) @@ -797,15 +914,15 @@ public Task DeleteEnrollmentGroupAsync(string enrollmentGroupId, CancellationTok /// /// This method will remove the enrollmentGroup from the Device Provisioning Service using the /// provided enrollmentGroupId and eTag. If you want to delete the enrollmentGroup regardless the eTag, you can - /// use or you can pass the eTag as null, empty, or - /// "*". + /// use or you can pass the eTag as null, empty, or + /// "*". /// /// Note that delete the enrollmentGroup will not remove the Device itself from the IotHub. /// /// If the enrollmentGroupId does not exists or eTag does not matches, this method will throw . /// - /// the string that identifies the enrollmentGroup. It cannot be null or empty. - /// the string with the enrollmentGroup eTag. It can be null or empty. + /// the string that identifies the enrollmentGroup. It cannot be null or empty. + /// the string with the enrollmentGroup eTag. It can be null or empty. /// The Device Provisioning Service will ignore it in all of these cases. /// if the Provisioning Device Service was not able to delete the enrollmentGroup information for the provided enrollmentGroupId and eTag. public Task DeleteEnrollmentGroupAsync(string enrollmentGroupId, string eTag) @@ -820,15 +937,15 @@ public Task DeleteEnrollmentGroupAsync(string enrollmentGroupId, string eTag) /// /// This method will remove the enrollmentGroup from the Device Provisioning Service using the /// provided enrollmentGroupId and eTag. If you want to delete the enrollmentGroup regardless the eTag, you can - /// use or you can pass the eTag as null, empty, or - /// "*". + /// use or you can pass the eTag as null, empty, or + /// "*". /// /// Note that delete the enrollmentGroup will not remove the Device itself from the IotHub. /// /// If the enrollmentGroupId does not exists or eTag does not matches, this method will throw . /// - /// the string that identifies the enrollmentGroup. It cannot be null or empty. - /// the string with the enrollmentGroup eTag. It can be null or empty. + /// the string that identifies the enrollmentGroup. It cannot be null or empty. + /// the string with the enrollmentGroup eTag. It can be null or empty. /// The Device Provisioning Service will ignore it in all of these cases. /// The cancellation token. /// if the Provisioning Device Service was not able to delete the enrollmentGroup information for the provided enrollmentGroupId and eTag. @@ -846,16 +963,17 @@ public Task DeleteEnrollmentGroupAsync(string enrollmentGroupId, string eTag, Ca /// a iterator. /// /// The Device Provisioning Service expects a SQL query in the , for instance - /// "SELECT * FROM enrollments". + /// "SELECT * FROM enrollments". /// - /// the with the SQL query. It cannot be null. + /// the with the SQL query. It cannot be null. /// The iterator. /// if the provided parameter is not correct. 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); @@ -869,9 +987,9 @@ public Query CreateEnrollmentGroupQuery(QuerySpecification querySpecification) /// a iterator. /// /// The Device Provisioning Service expects a SQL query in the , for instance - /// "SELECT * FROM enrollments". + /// "SELECT * FROM enrollments". /// - /// the with the SQL query. It cannot be null. + /// the with the SQL query. It cannot be null. /// Specifies the HTTP transport settings /// The iterator. /// if the provided parameter is not correct. @@ -879,7 +997,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); @@ -893,9 +1012,9 @@ public Query CreateEnrollmentGroupQuery(QuerySpecification querySpecification, H /// a iterator. /// /// The Device Provisioning Service expects a SQL query in the , for instance - /// "SELECT * FROM enrollments". + /// "SELECT * FROM enrollments". /// - /// the with the SQL query. It cannot be null. + /// the with the SQL query. It cannot be null. /// The cancellation token. /// The iterator. /// if the provided parameter is not correct. @@ -903,7 +1022,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); @@ -917,21 +1037,22 @@ public Query CreateEnrollmentGroupQuery(QuerySpecification querySpecification, C /// a iterator. /// /// The Device Provisioning Service expects a SQL query in the , for instance - /// "SELECT * FROM enrollments". + /// "SELECT * FROM enrollments". /// /// For each iteration, the Query will return a List of objects correspondent to the query result. The maximum /// number of items per iteration can be specified by the pageSize. It is optional, you can provide 0 for /// default pageSize or use the API . /// - /// the with the SQL query. It cannot be null. - /// the int with the maximum number of items per iteration. It can be 0 for default, but not negative. + /// the with the SQL query. It cannot be null. + /// the int with the maximum number of items per iteration. It can be 0 for default, but not negative. /// The iterator. /// if the provided parameters are not correct. public Query CreateEnrollmentGroupQuery(QuerySpecification querySpecification, int pageSize) { /* 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, @@ -946,14 +1067,14 @@ public Query CreateEnrollmentGroupQuery(QuerySpecification querySpecification, i /// a iterator. /// /// The Device Provisioning Service expects a SQL query in the , for instance - /// "SELECT * FROM enrollments". + /// "SELECT * FROM enrollments". /// /// For each iteration, the Query will return a List of objects correspondent to the query result. The maximum /// number of items per iteration can be specified by the pageSize. It is optional, you can provide 0 for /// default pageSize or use the API . /// - /// the with the SQL query. It cannot be null. - /// the int with the maximum number of items per iteration. It can be 0 for default, but not negative. + /// the with the SQL query. It cannot be null. + /// the int with the maximum number of items per iteration. It can be 0 for default, but not negative. /// The cancellation token. /// The iterator. /// if the provided parameters are not correct. @@ -961,7 +1082,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, @@ -976,14 +1098,14 @@ public Query CreateEnrollmentGroupQuery(QuerySpecification querySpecification, i /// a iterator. /// /// The Device Provisioning Service expects a SQL query in the , for instance - /// "SELECT * FROM enrollments". + /// "SELECT * FROM enrollments". /// /// For each iteration, the Query will return a List of objects correspondent to the query result. The maximum /// number of items per iteration can be specified by the pageSize. It is optional, you can provide 0 for /// default pageSize or use the API . /// - /// the with the SQL query. It cannot be null. - /// the int with the maximum number of items per iteration. It can be 0 for default, but not negative. + /// the with the SQL query. It cannot be null. + /// the int with the maximum number of items per iteration. It can be 0 for default, but not negative. /// Specifies the HTTP transport settings /// The iterator. /// if the provided parameters are not correct. @@ -991,7 +1113,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, @@ -1008,7 +1131,7 @@ public Query CreateEnrollmentGroupQuery(QuerySpecification querySpecification, i /// /// If the id does not exist, this method will throw . /// - /// the string that identifies the DeviceRegistrationState. It cannot be null or empty. + /// the string that identifies the DeviceRegistrationState. It cannot be null or empty. /// The with the content of the DeviceRegistrationState in the Provisioning Device Service. /// if the Provisioning Device Service was not able to retrieve the DeviceRegistrationState information for the provided registrationId. public Task GetDeviceRegistrationStateAsync(string id) @@ -1027,7 +1150,7 @@ public Task GetDeviceRegistrationStateAsync(string id) /// /// If the id does not exist, this method will throw . /// - /// the string that identifies the DeviceRegistrationState. It cannot be null or empty. + /// the string that identifies the DeviceRegistrationState. It cannot be null or empty. /// The cancellation token. /// The with the content of the DeviceRegistrationState in the Provisioning Device Service. /// if the Provisioning Device Service was not able to retrieve the DeviceRegistrationState information for the provided registrationId. @@ -1049,7 +1172,7 @@ public Task GetDeviceRegistrationStateAsync(string id, /// If the id does not exists or the eTag does not matches, this method will throw /// . /// - /// the that identifies the DeviceRegistrationState. It cannot be null. + /// the that identifies the DeviceRegistrationState. It cannot be null. /// if the Provisioning Device Service was not able to delete the registration status information for the provided DeviceRegistrationState. public Task DeleteDeviceRegistrationStateAsync(DeviceRegistrationState deviceRegistrationState) { @@ -1069,7 +1192,7 @@ public Task DeleteDeviceRegistrationStateAsync(DeviceRegistrationState deviceReg /// If the id does not exists or the eTag does not matches, this method will throw /// . /// - /// the that identifies the DeviceRegistrationState. It cannot be null. + /// the that identifies the DeviceRegistrationState. It cannot be null. /// The cancellation token. /// if the Provisioning Device Service was not able to delete the registration status information for the provided DeviceRegistrationState. @@ -1085,11 +1208,11 @@ public Task DeleteDeviceRegistrationStateAsync(DeviceRegistrationState deviceReg /// /// This method will remove the DeviceRegistrationState from the Device Provisioning Service using the /// provided id. It will delete the registration status regardless the eTag. It means that this API - /// correspond to the with the eTag="*". + /// correspond to the with the eTag="*". /// /// If the id does not exists, this method will throw . /// - /// the string that identifies the DeviceRegistrationState. It cannot be null or empty. + /// the string that identifies the DeviceRegistrationState. It cannot be null or empty. /// if the Provisioning Device Service was not able to delete the DeviceRegistrationState information for the provided registrationId. public Task DeleteDeviceRegistrationStateAsync(string id) { @@ -1103,11 +1226,11 @@ public Task DeleteDeviceRegistrationStateAsync(string id) /// /// This method will remove the DeviceRegistrationState from the Device Provisioning Service using the /// provided id. It will delete the registration status regardless the eTag. It means that this API - /// correspond to the with the eTag="*". + /// correspond to the with the eTag="*". /// /// If the id does not exists, this method will throw . /// - /// the string that identifies the DeviceRegistrationState. It cannot be null or empty. + /// the string that identifies the DeviceRegistrationState. It cannot be null or empty. /// The cancellation token. /// if the Provisioning Device Service was not able to delete the DeviceRegistrationState information for the provided registrationId. public Task DeleteDeviceRegistrationStateAsync(string id, CancellationToken cancellationToken) @@ -1122,14 +1245,14 @@ public Task DeleteDeviceRegistrationStateAsync(string id, CancellationToken canc /// /// This method will remove the registration status from the Device Provisioning Service using the /// provided id and eTag. If you want to delete the registration status regardless the eTag, you can - /// use or you can pass the eTag as null, empty, or - /// "*". + /// use or you can pass the eTag as null, empty, or + /// "*". /// /// If the id does not exists or the eTag does not matches, this method will throw /// . /// - /// the string that identifies the DeviceRegistrationState. It cannot be null or empty. - /// the string with the DeviceRegistrationState eTag. It can be null or empty. + /// the string that identifies the DeviceRegistrationState. It cannot be null or empty. + /// the string with the DeviceRegistrationState eTag. It can be null or empty. /// The Device Provisioning Service will ignore it in all of these cases. /// if the Provisioning Device Service was not able to delete the DeviceRegistrationState information for the provided registrationId and eTag. public Task DeleteDeviceRegistrationStateAsync(string id, string eTag) @@ -1144,14 +1267,14 @@ public Task DeleteDeviceRegistrationStateAsync(string id, string eTag) /// /// This method will remove the registration status from the Device Provisioning Service using the /// provided id and eTag. If you want to delete the registration status regardless the eTag, you can - /// use or you can pass the eTag as null, empty, or - /// "*". + /// use or you can pass the eTag as null, empty, or + /// "*". /// /// If the id does not exists or the eTag does not matches, this method will throw /// . /// - /// the string that identifies the DeviceRegistrationState. It cannot be null or empty. - /// the string with the DeviceRegistrationState eTag. It can be null or empty. + /// the string that identifies the DeviceRegistrationState. It cannot be null or empty. + /// the string with the DeviceRegistrationState eTag. It can be null or empty. /// The Device Provisioning Service will ignore it in all of these cases. /// The cancellation token. /// if the Provisioning Device Service was not able to delete the DeviceRegistrationState information for the provided registrationId and eTag. @@ -1169,16 +1292,17 @@ public Task DeleteDeviceRegistrationStateAsync(string id, string eTag, Cancellat /// Provisioning Service and return it as a iterator. /// /// The Device Provisioning Service expects a SQL query in the , for instance - /// "SELECT * FROM enrollments". + /// "SELECT * FROM enrollments". /// - /// the with the SQL query. It cannot be null. - /// the string that identifies the enrollmentGroup. It cannot be null or empty. + /// the with the SQL query. It cannot be null. + /// the string that identifies the enrollmentGroup. It cannot be null or empty. /// The iterator. public Query CreateEnrollmentGroupRegistrationStateQuery(QuerySpecification querySpecification, string enrollmentGroupId) { /* 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, @@ -1193,17 +1317,18 @@ public Query CreateEnrollmentGroupRegistrationStateQuery(QuerySpecification quer /// Provisioning Service and return it as a iterator. /// /// The Device Provisioning Service expects a SQL query in the , for instance - /// "SELECT * FROM enrollments". + /// "SELECT * FROM enrollments". /// - /// the with the SQL query. It cannot be null. - /// the string that identifies the enrollmentGroup. It cannot be null or empty. + /// the with the SQL query. It cannot be null. + /// the string that identifies the enrollmentGroup. It cannot be null or empty. /// Specifies the HTTP transport settings /// The iterator. public Query CreateEnrollmentGroupRegistrationStateQuery(QuerySpecification querySpecification, string enrollmentGroupId, HttpTransportSettings httpTransportSettings) { /* 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, @@ -1218,10 +1343,10 @@ public Query CreateEnrollmentGroupRegistrationStateQuery(QuerySpecification quer /// Provisioning Service and return it as a iterator. /// /// The Device Provisioning Service expects a SQL query in the , for instance - /// "SELECT * FROM enrollments". + /// "SELECT * FROM enrollments". /// - /// the with the SQL query. It cannot be null. - /// the string that identifies the enrollmentGroup. It cannot be null or empty. + /// the with the SQL query. It cannot be null. + /// the string that identifies the enrollmentGroup. It cannot be null or empty. /// The cancellation token. /// The iterator. public Query CreateEnrollmentGroupRegistrationStateQuery( @@ -1231,7 +1356,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, @@ -1246,22 +1372,23 @@ public Query CreateEnrollmentGroupRegistrationStateQuery( /// Provisioning Service and return it as a iterator. /// /// The Device Provisioning Service expects a SQL query in the , for instance - /// "SELECT * FROM enrollments". + /// "SELECT * FROM enrollments". /// /// For each iteration, the Query will return a List of objects correspondent to the query result. The maximum /// number of items per iteration can be specified by the pageSize. It is optional, you can provide 0 for /// default pageSize or use the API . /// - /// the with the SQL query. It cannot be null. - /// the string that identifies the enrollmentGroup. It cannot be null or empty. - /// the int with the maximum number of items per iteration. It can be 0 for default, but not negative. + /// the with the SQL query. It cannot be null. + /// the string that identifies the enrollmentGroup. It cannot be null or empty. + /// the int with the maximum number of items per iteration. It can be 0 for default, but not negative. /// The iterator. /// if the provided parameters are not correct. public Query CreateEnrollmentGroupRegistrationStateQuery(QuerySpecification querySpecification, string enrollmentGroupId, int pageSize) { /* 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, @@ -1277,15 +1404,15 @@ public Query CreateEnrollmentGroupRegistrationStateQuery(QuerySpecification quer /// Provisioning Service and return it as a iterator. /// /// The Device Provisioning Service expects a SQL query in the , for instance - /// "SELECT * FROM enrollments". + /// "SELECT * FROM enrollments". /// /// For each iteration, the Query will return a List of objects correspondent to the query result. The maximum /// number of items per iteration can be specified by the pageSize. It is optional, you can provide 0 for /// default pageSize or use the API . /// - /// the with the SQL query. It cannot be null. - /// the string that identifies the enrollmentGroup. It cannot be null or empty. - /// the int with the maximum number of items per iteration. It can be 0 for default, but not negative. + /// the with the SQL query. It cannot be null. + /// the string that identifies the enrollmentGroup. It cannot be null or empty. + /// the int with the maximum number of items per iteration. It can be 0 for default, but not negative. /// Specifies the HTTP transport settings /// The iterator. /// if the provided parameters are not correct. @@ -1297,7 +1424,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, @@ -1313,16 +1441,16 @@ public Query CreateEnrollmentGroupRegistrationStateQuery( /// Provisioning Service and return it as a iterator. /// /// The Device Provisioning Service expects a SQL query in the , for instance - /// "SELECT * FROM enrollments". + /// "SELECT * FROM enrollments". /// /// For each iteration, the Query will return a List of objects correspondent to the query result. The maximum /// number of items per iteration can be specified by the pageSize. It is optional, you can provide 0 for /// default pageSize or use the API . /// - /// the with the SQL query. It cannot be null. - /// the string that identifies the enrollmentGroup. It cannot be null or empty. + /// the with the SQL query. It cannot be null. + /// the string that identifies the enrollmentGroup. It cannot be null or empty. /// The cancellation token. - /// the int with the maximum number of items per iteration. It can be 0 for default, but not negative. + /// the int with the maximum number of items per iteration. It can be 0 for default, but not negative. /// The iterator. /// if the provided parameters are not correct. public Query CreateEnrollmentGroupRegistrationStateQuery( @@ -1333,7 +1461,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, @@ -1361,12 +1490,19 @@ public Task GetIndividualEnrollmentAttestationAsync(string /// /// If the provided group id does not match up with any enrollment group, this method will throw . /// - /// the string that identifies the enrollmentGroup. It cannot be null or empty. + /// the string that identifies the enrollmentGroup. It cannot be null or empty. /// The cancellation token. /// The associated with the provided group Id. public Task GetEnrollmentGroupAttestationAsync(string enrollmentGroupId, CancellationToken cancellationToken = default) { 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 47e6d03b60..6eb4ca7b38 100644 --- a/provisioning/service/src/Query.cs +++ b/provisioning/service/src/Query.cs @@ -80,50 +80,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"); + throw new ArgumentException("Service name cannot be empty", nameof(serviceName)); } - /* 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."); + throw new ArgumentException("Page size cannot be negative.", nameof(pageSize)); } // 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("Service name cannot be empty", nameof(serviceName)); + } + + if (querySpecification == null) + { + throw new ArgumentNullException(nameof(querySpecification)); + } + + if (pageSize < 0) + { + throw new ArgumentException("Page size cannot be negative.", nameof(pageSize)); + } + + // 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; } @@ -160,17 +196,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); } @@ -181,25 +214,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, @@ -208,12 +237,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);