From a243d2b82d7c547d88898d993badb09b01ab9c6b Mon Sep 17 00:00:00 2001 From: dylanbulfinMS <95251881+dylanbulfinMS@users.noreply.github.com> Date: Tue, 11 Jan 2022 12:43:03 -0500 Subject: [PATCH 1/5] Basic test layout --- .../config/TestConfiguration.Provisioning.cs | 102 +++++++++++++++++- .../ProvisioningServiceClientE2ETests.cs | 40 +++++++ 2 files changed, 141 insertions(+), 1 deletion(-) diff --git a/e2e/test/config/TestConfiguration.Provisioning.cs b/e2e/test/config/TestConfiguration.Provisioning.cs index a65f27e557..e720052664 100644 --- a/e2e/test/config/TestConfiguration.Provisioning.cs +++ b/e2e/test/config/TestConfiguration.Provisioning.cs @@ -1,8 +1,13 @@ // 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 Azure.Identity; namespace Microsoft.Azure.Devices.E2ETests { @@ -14,6 +19,30 @@ 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; + } + + public static ClientSecretCredential GetClientSecretCredential() + { + return new ClientSecretCredential( + GetValue("MSFT_TENANT_ID"), + GetValue("PROVISIONING_CLIENT_ID"), + GetValue("PROVISIONING_CLIENT_SECRET")); + } + + 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 +67,77 @@ 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) + { + 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://myiothub.azure-devices.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); + + if (!string.IsNullOrWhiteSpace(policyName)) + { + token += "&skn=" + policyName; + } + + return token; + } + + public class ConnectionStringParser + { + public ConnectionStringParser(string connectionString) + { + string[] parts = connectionString.Split(';'); + foreach (string part in parts) + { + string[] tv = part.Split('='); + + switch (tv[0].ToUpperInvariant()) + { + case "HOSTNAME": + ProvisioningHostName = part.Substring("HOSTNAME=".Length); + break; + + case "SHAREDACCESSKEY": + SharedAccessKey = part.Substring("SHAREDACCESSKEY=".Length); + break; + + case "DEVICEID": + DeviceID = part.Substring("DEVICEID=".Length); + break; + + case "SHAREDACCESSKEYNAME": + SharedAccessKeyName = part.Substring("SHAREDACCESSKEYNAME=".Length); + break; + + default: + throw new NotSupportedException("Unrecognized tag found in test ConnectionString."); + } + } + } + + public string ProvisioningHostName { get; private set; } + + public string DeviceID { get; private set; } + + public string SharedAccessKey { get; private set; } + + public string SharedAccessKeyName { get; private set; } + } } } } diff --git a/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs b/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs index 1ada9be022..5175471a51 100644 --- a/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs +++ b/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Net; using System.Threading.Tasks; +using Azure; using Microsoft.Azure.Devices.Provisioning.Security.Samples; using Microsoft.Azure.Devices.Provisioning.Service; using Microsoft.Azure.Devices.Shared; @@ -110,6 +111,45 @@ public async Task ProvisioningServiceClient_GetEnrollmentGroupAttestation_Symmet await ProvisioningServiceClient_GetEnrollmentGroupAttestation(AttestationMechanismType.SymmetricKey); } + [LoggedTestMethod] + public async Task ProvisioningServiceClient_TokenCredentialAuth_Success() + { + using var provisioningServiceClient = ProvisioningServiceClient.Create( + TestConfiguration.Provisioning.GetProvisioningHostName(), + TestConfiguration.Provisioning.GetClientSecretCredential()); + + IndividualEnrollment individualEnrollment = await CreateIndividualEnrollment(provisioningServiceClient, AttestationMechanismType.SymmetricKey, null, AllocationPolicy.Static, null, null, null); + + //act + IndividualEnrollment individualEnrollmentResult = await provisioningServiceClient.CreateOrUpdateIndividualEnrollmentAsync(individualEnrollment); + + //assert + Assert.IsNotNull(individualEnrollmentResult); + + //cleanup + await provisioningServiceClient.DeleteIndividualEnrollmentAsync(individualEnrollment.RegistrationId); + } + + [LoggedTestMethod] + public async Task ProvisioningServiceClient_AzureSasCredentialAuth_Success() + { + 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); + + //act + IndividualEnrollment individualEnrollmentResult = await provisioningServiceClient.CreateOrUpdateIndividualEnrollmentAsync(individualEnrollment); + + //assert + Assert.IsNotNull(individualEnrollmentResult); + + //cleanup + await provisioningServiceClient.DeleteIndividualEnrollmentAsync(individualEnrollment.RegistrationId); + } + public async Task ProvisioningServiceClient_GetIndividualEnrollmentAttestation(AttestationMechanismType attestationType) { using var provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); From e4f7ff95d0cdedea5ffe9eafbfd060afb96cd8f3 Mon Sep 17 00:00:00 2001 From: dylanbulfinMS <95251881+dylanbulfinMS@users.noreply.github.com> Date: Fri, 14 Jan 2022 13:03:59 -0500 Subject: [PATCH 2/5] API update --- e2e/test/config/TestConfiguration.Provisioning.cs | 13 +++++++++++-- provisioning/service/src/Contract/SDKUtils.cs | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/e2e/test/config/TestConfiguration.Provisioning.cs b/e2e/test/config/TestConfiguration.Provisioning.cs index e720052664..a21f2b9dea 100644 --- a/e2e/test/config/TestConfiguration.Provisioning.cs +++ b/e2e/test/config/TestConfiguration.Provisioning.cs @@ -7,8 +7,13 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; + +#if !NET451 + using Azure.Identity; +#endif + namespace Microsoft.Azure.Devices.E2ETests { public static partial class TestConfiguration @@ -25,14 +30,18 @@ public static string GetProvisioningHostName() return connectionString.ProvisioningHostName; } +#if !NET451 + public static ClientSecretCredential GetClientSecretCredential() { return new ClientSecretCredential( GetValue("MSFT_TENANT_ID"), - GetValue("PROVISIONING_CLIENT_ID"), - GetValue("PROVISIONING_CLIENT_SECRET")); + GetValue("IOTHUB_CLIENT_ID"), + GetValue("IOTHUB_CLIENT_SECRET")); } +#endif + public static string GetProvisioningSharedAccessSignature(TimeSpan timeToLive) { var connectionString = new ConnectionStringParser(ConnectionString); 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; } } From 5062f248056132e631b75b26e203c9267e2d06e4 Mon Sep 17 00:00:00 2001 From: dylanbulfinMS <95251881+dylanbulfinMS@users.noreply.github.com> Date: Tue, 25 Jan 2022 19:18:58 -0500 Subject: [PATCH 3/5] Style updates --- .../config/TestConfiguration.Provisioning.cs | 42 ----------------- .../E2ETestsSetup/e2eTestsSetup.ps1 | 3 +- .../provisioning/ConnectionStringParser.cs | 46 +++++++++++++++++++ .../ProvisioningServiceClientE2ETests.cs | 25 ++++++++-- 4 files changed, 69 insertions(+), 47 deletions(-) create mode 100644 e2e/test/provisioning/ConnectionStringParser.cs diff --git a/e2e/test/config/TestConfiguration.Provisioning.cs b/e2e/test/config/TestConfiguration.Provisioning.cs index a21f2b9dea..2cd65733cf 100644 --- a/e2e/test/config/TestConfiguration.Provisioning.cs +++ b/e2e/test/config/TestConfiguration.Provisioning.cs @@ -105,48 +105,6 @@ private static string GenerateSasToken(string resourceUri, string sharedAccessKe return token; } - - public class ConnectionStringParser - { - public ConnectionStringParser(string connectionString) - { - string[] parts = connectionString.Split(';'); - foreach (string part in parts) - { - string[] tv = part.Split('='); - - switch (tv[0].ToUpperInvariant()) - { - case "HOSTNAME": - ProvisioningHostName = part.Substring("HOSTNAME=".Length); - break; - - case "SHAREDACCESSKEY": - SharedAccessKey = part.Substring("SHAREDACCESSKEY=".Length); - break; - - case "DEVICEID": - DeviceID = part.Substring("DEVICEID=".Length); - break; - - case "SHAREDACCESSKEYNAME": - SharedAccessKeyName = part.Substring("SHAREDACCESSKEYNAME=".Length); - break; - - default: - throw new NotSupportedException("Unrecognized tag found in test ConnectionString."); - } - } - } - - public string ProvisioningHostName { get; private set; } - - public string DeviceID { get; private set; } - - public string SharedAccessKey { get; private set; } - - public string SharedAccessKeyName { get; private set; } - } } } } diff --git a/e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1 b/e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1 index 0ac2a2e319..6c1c3c0909 100644 --- a/e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1 +++ b/e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1 @@ -610,13 +610,14 @@ if ($certExists) az iot dps certificate delete -g $ResourceGroup --dps-name $dpsName --name $uploadCertificateName --etag $etag } Write-Host "`nUploading new certificate to DPS." -az iot dps certificate create -g $ResourceGroup --path $rootCertPath --dps-name $dpsName --certificate-name $uploadCertificateName --output none +az iot dps certificate create -g $ResourceGroup --path $rootCertPath --dps-name $dpsName --certificate-name $uploadCertificateName $isVerified = az iot dps certificate show -g $ResourceGroup --dps-name $dpsName --certificate-name $uploadCertificateName --query 'properties.isVerified' --output tsv if ($isVerified -eq 'false') { Write-Host "`nVerifying certificate uploaded to DPS." $etag = az iot dps certificate show -g $ResourceGroup --dps-name $dpsName --certificate-name $uploadCertificateName --query 'etag' + Write-Host "`n az iot dps certificate generate-verification-code -g $ResourceGroup --dps-name $dpsName --certificate-name $uploadCertificateName -e $etag --query 'properties.verificationCode'" $requestedCommonName = az iot dps certificate generate-verification-code -g $ResourceGroup --dps-name $dpsName --certificate-name $uploadCertificateName -e $etag --query 'properties.verificationCode' $verificationCertArgs = @{ "-DnsName" = $requestedCommonName; diff --git a/e2e/test/provisioning/ConnectionStringParser.cs b/e2e/test/provisioning/ConnectionStringParser.cs new file mode 100644 index 0000000000..a2898d6175 --- /dev/null +++ b/e2e/test/provisioning/ConnectionStringParser.cs @@ -0,0 +1,46 @@ +using System; + +namespace Microsoft.Azure.Devices.E2ETests.provisioning +{ + public 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) + { + string[] parts = connectionString.Split(';'); + foreach (string part in parts) + { + string[] tv = part.Split('='); + + switch (tv[0].ToUpperInvariant()) + { + case "HOSTNAME": + ProvisioningHostName = part.Substring("HOSTNAME=".Length); + break; + + case "SHAREDACCESSKEY": + SharedAccessKey = part.Substring("SHAREDACCESSKEY=".Length); + break; + + case "DEVICEID": + DeviceId = part.Substring("DEVICEID=".Length); + break; + + case "SHAREDACCESSKEYNAME": + SharedAccessKeyName = part.Substring("SHAREDACCESSKEYNAME=".Length); + break; + + default: + throw new NotSupportedException("Unrecognized tag found in test ConnectionString."); + } + } + } + } +} diff --git a/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs b/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs index 5175471a51..f8002109ae 100644 --- a/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs +++ b/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs @@ -6,6 +6,7 @@ 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; @@ -118,13 +119,21 @@ public async Task ProvisioningServiceClient_TokenCredentialAuth_Success() TestConfiguration.Provisioning.GetProvisioningHostName(), TestConfiguration.Provisioning.GetClientSecretCredential()); - IndividualEnrollment individualEnrollment = await CreateIndividualEnrollment(provisioningServiceClient, AttestationMechanismType.SymmetricKey, null, AllocationPolicy.Static, null, null, null); + IndividualEnrollment individualEnrollment = await CreateIndividualEnrollment( + provisioningServiceClient, + AttestationMechanismType.SymmetricKey, + null, + AllocationPolicy.Static, + null, + null, + null) + .ConfigureAwait(false); //act IndividualEnrollment individualEnrollmentResult = await provisioningServiceClient.CreateOrUpdateIndividualEnrollmentAsync(individualEnrollment); //assert - Assert.IsNotNull(individualEnrollmentResult); + individualEnrollmentResult.Should().NotBeNull(); //cleanup await provisioningServiceClient.DeleteIndividualEnrollmentAsync(individualEnrollment.RegistrationId); @@ -138,13 +147,21 @@ public async Task ProvisioningServiceClient_AzureSasCredentialAuth_Success() TestConfiguration.Provisioning.GetProvisioningHostName(), new AzureSasCredential(signature)); - IndividualEnrollment individualEnrollment = await CreateIndividualEnrollment(provisioningServiceClient, AttestationMechanismType.SymmetricKey, null, AllocationPolicy.Static, null, null, null); + IndividualEnrollment individualEnrollment = await CreateIndividualEnrollment( + provisioningServiceClient, + AttestationMechanismType.SymmetricKey, + null, + AllocationPolicy.Static, + null, + null, + null) + .ConfigureAwait(false); //act IndividualEnrollment individualEnrollmentResult = await provisioningServiceClient.CreateOrUpdateIndividualEnrollmentAsync(individualEnrollment); //assert - Assert.IsNotNull(individualEnrollmentResult); + individualEnrollmentResult.Should().NotBeNull(); //cleanup await provisioningServiceClient.DeleteIndividualEnrollmentAsync(individualEnrollment.RegistrationId); From 93d3d09a6aace69b085d9303e9a7a6427a994b97 Mon Sep 17 00:00:00 2001 From: dylanbulfinMS <95251881+dylanbulfinMS@users.noreply.github.com> Date: Tue, 25 Jan 2022 19:37:12 -0500 Subject: [PATCH 4/5] Fix namespace issue --- e2e/test/config/TestConfiguration.Provisioning.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/e2e/test/config/TestConfiguration.Provisioning.cs b/e2e/test/config/TestConfiguration.Provisioning.cs index 2cd65733cf..54a46394da 100644 --- a/e2e/test/config/TestConfiguration.Provisioning.cs +++ b/e2e/test/config/TestConfiguration.Provisioning.cs @@ -7,6 +7,7 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; +using Microsoft.Azure.Devices.E2ETests.provisioning; #if !NET451 From 998604551d63dadcb99cad5ea923a43107223538 Mon Sep 17 00:00:00 2001 From: "David R. Williamson" Date: Mon, 31 Jan 2022 16:51:09 -0800 Subject: [PATCH 5/5] Misc cleanup --- .../config/TestConfiguration.Provisioning.cs | 2 +- .../provisioning/ConnectionStringParser.cs | 31 ++++++++++----- .../ProvisioningServiceClientE2ETests.cs | 38 ++++++++++--------- 3 files changed, 43 insertions(+), 28 deletions(-) diff --git a/e2e/test/config/TestConfiguration.Provisioning.cs b/e2e/test/config/TestConfiguration.Provisioning.cs index 54a46394da..80d8727de7 100644 --- a/e2e/test/config/TestConfiguration.Provisioning.cs +++ b/e2e/test/config/TestConfiguration.Provisioning.cs @@ -7,7 +7,7 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; -using Microsoft.Azure.Devices.E2ETests.provisioning; +using Microsoft.Azure.Devices.E2ETests.Provisioning; #if !NET451 diff --git a/e2e/test/provisioning/ConnectionStringParser.cs b/e2e/test/provisioning/ConnectionStringParser.cs index a2898d6175..958c78ebb2 100644 --- a/e2e/test/provisioning/ConnectionStringParser.cs +++ b/e2e/test/provisioning/ConnectionStringParser.cs @@ -1,8 +1,8 @@ using System; -namespace Microsoft.Azure.Devices.E2ETests.provisioning +namespace Microsoft.Azure.Devices.E2ETests.Provisioning { - public class ConnectionStringParser + internal class ConnectionStringParser { public string ProvisioningHostName { get; private set; } @@ -14,31 +14,44 @@ public class ConnectionStringParser public ConnectionStringParser(string connectionString) { + if (string.IsNullOrWhiteSpace(connectionString)) + { + throw new ArgumentException(nameof(connectionString), "Parameter cannot be null, empty, or whitespace."); + } + string[] parts = connectionString.Split(';'); + foreach (string part in parts) { - string[] tv = part.Split('='); + 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 (tv[0].ToUpperInvariant()) + switch (key.ToUpperInvariant()) { case "HOSTNAME": - ProvisioningHostName = part.Substring("HOSTNAME=".Length); + ProvisioningHostName = value; break; case "SHAREDACCESSKEY": - SharedAccessKey = part.Substring("SHAREDACCESSKEY=".Length); + SharedAccessKey = value; break; case "DEVICEID": - DeviceId = part.Substring("DEVICEID=".Length); + DeviceId = value; break; case "SHAREDACCESSKEYNAME": - SharedAccessKeyName = part.Substring("SHAREDACCESSKEYNAME=".Length); + SharedAccessKeyName = value; break; default: - throw new NotSupportedException("Unrecognized tag found in test ConnectionString."); + 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 f8002109ae..e847566278 100644 --- a/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs +++ b/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs @@ -115,55 +115,57 @@ public async Task ProvisioningServiceClient_GetEnrollmentGroupAttestation_Symmet [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, + provisioningServiceClient, + AttestationMechanismType.SymmetricKey, + null, + AllocationPolicy.Static, + null, + null, null) .ConfigureAwait(false); - //act + // act IndividualEnrollment individualEnrollmentResult = await provisioningServiceClient.CreateOrUpdateIndividualEnrollmentAsync(individualEnrollment); - //assert + // assert individualEnrollmentResult.Should().NotBeNull(); - //cleanup + // 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, + provisioningServiceClient, + AttestationMechanismType.SymmetricKey, + null, + AllocationPolicy.Static, + null, + null, null) .ConfigureAwait(false); - //act + // act IndividualEnrollment individualEnrollmentResult = await provisioningServiceClient.CreateOrUpdateIndividualEnrollmentAsync(individualEnrollment); - //assert + // assert individualEnrollmentResult.Should().NotBeNull(); - //cleanup + // cleanup await provisioningServiceClient.DeleteIndividualEnrollmentAsync(individualEnrollment.RegistrationId); }