From 1c4d13d0037d746aa4343fbb8d3798aa82772d65 Mon Sep 17 00:00:00 2001 From: Andres Paz Date: Thu, 10 Jun 2021 10:28:15 -0700 Subject: [PATCH 1/7] Move to CredentialFactory. --- .../CredentialFactoryTests.cs | 39 ++++++++ .../WorkspaceTest.cs | 8 +- .../Authentication/CredentialFactory.cs | 94 +++++++++++++++++++ .../Exceptions/MissingCredentialException.cs | 26 +++++ .../Exceptions/WorkspaceClientException.cs | 8 +- .../JobManagement/Workspace.cs | 10 +- src/Simulation/EntryPointDriver/Azure.cs | 61 +++++++----- 7 files changed, 207 insertions(+), 39 deletions(-) create mode 100644 src/Azure/Azure.Quantum.Client.Test/CredentialFactoryTests.cs create mode 100644 src/Azure/Azure.Quantum.Client/Authentication/CredentialFactory.cs create mode 100644 src/Azure/Azure.Quantum.Client/Exceptions/MissingCredentialException.cs diff --git a/src/Azure/Azure.Quantum.Client.Test/CredentialFactoryTests.cs b/src/Azure/Azure.Quantum.Client.Test/CredentialFactoryTests.cs new file mode 100644 index 00000000000..af899ae71b8 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client.Test/CredentialFactoryTests.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +using Azure.Identity; + +using Microsoft.Azure.Quantum.Authentication; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Azure.Quantum.Test +{ + [TestClass] + public class CredentialFactoryTests + { + [DataTestMethod] + [DataRow(CredentialTypes.Default, typeof(DefaultAzureCredential))] + [DataRow(CredentialTypes.Environment, typeof(EnvironmentCredential))] + [DataRow(CredentialTypes.ManagedIdentity, typeof(ManagedIdentityCredential))] + [DataRow(CredentialTypes.CLI, typeof(AzureCliCredential))] + [DataRow(CredentialTypes.SharedToken, typeof(SharedTokenCacheCredential))] + [DataRow(CredentialTypes.VisualStudio, typeof(VisualStudioCredential))] + [DataRow(CredentialTypes.VisualStudioCode, typeof(VisualStudioCodeCredential))] + [DataRow(CredentialTypes.Interactive, typeof(InteractiveBrowserCredential))] + public void TestCreateCredential(CredentialTypes credentialType, Type expectedType) + { + var actual = CredentialFactory.CreateCredential(credentialType); + + Assert.IsNotNull(actual); + Assert.AreEqual(expectedType, actual.GetType()); + } + + [TestMethod] + public void TestInvalidCredentialType() + { + Assert.ThrowsException(() => CredentialFactory.CreateCredential((CredentialTypes)9999)); + } + } +} diff --git a/src/Azure/Azure.Quantum.Client.Test/WorkspaceTest.cs b/src/Azure/Azure.Quantum.Client.Test/WorkspaceTest.cs index bb5fb81f7ba..ad6b3669c47 100644 --- a/src/Azure/Azure.Quantum.Client.Test/WorkspaceTest.cs +++ b/src/Azure/Azure.Quantum.Client.Test/WorkspaceTest.cs @@ -29,8 +29,9 @@ public class WorkspaceTest * E2E_WORKSPACE_RG: the Azure Quantum workspace's resource group. * E2E_WORKSPACE_LOCATION: the Azure Quantum workspace's location (region). -You'll also need to authenticate with Azure using any of the methods listed in: +We'll also try to authenticate with Azure using an instance of DefaultCredential. See https://docs.microsoft.com/en-us/dotnet/api/overview/azure/identity-readme#authenticate-the-client +for details. Tests will be marked as Inconclusive if the pre-reqs are not correctly setup."; @@ -197,12 +198,15 @@ private IWorkspace GetLiveWorkspace() var options = new QuantumJobClientOptions(); options.Diagnostics.ApplicationId = "ClientTests"; + var credential = Authentication.CredentialFactory.CreateCredential(Authentication.CredentialTypes.Default); + return new Workspace( subscriptionId: System.Environment.GetEnvironmentVariable("E2E_SUBSCRIPTION_ID"), resourceGroupName: System.Environment.GetEnvironmentVariable("E2E_WORKSPACE_RG"), workspaceName: System.Environment.GetEnvironmentVariable("E2E_WORKSPACE_NAME"), location: System.Environment.GetEnvironmentVariable("E2E_WORKSPACE_LOCATION"), - options: options); + options: options, + credential: credential); } private static JobDetails CreateJobDetails(string jobId, string containerUri = null, string inputUri = null) diff --git a/src/Azure/Azure.Quantum.Client/Authentication/CredentialFactory.cs b/src/Azure/Azure.Quantum.Client/Authentication/CredentialFactory.cs new file mode 100644 index 00000000000..0a054eb0400 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/Authentication/CredentialFactory.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#nullable enable + +namespace Microsoft.Azure.Quantum.Authentication +{ + using System; + + using global::Azure.Core; + using global::Azure.Identity; + + /// + /// The enumeration of supported Credential Classes supported out of the box for + /// authentication in Azure Quantum. + /// NOTE: For more information + /// about authentication with Azure services and the different Credential types see + /// https://docs.microsoft.com/en-us/dotnet/api/overview/azure/identity-readme. + /// + public enum CredentialTypes + { + /// + /// Provides a simplified authentication experience to quickly start developing applications run in the Azure cloud. + /// See: https://docs.microsoft.com/en-us/dotnet/api/azure.identity.defaultazurecredential + /// + Default, + + /// + /// Authenticates a service principal or user via credential information specified in environment variables. + /// See: https://docs.microsoft.com/en-us/dotnet/api/azure.identity.environmentcredential + /// + Environment, + + /// + /// Authenticates the managed identity of an azure resource. + /// See: https://docs.microsoft.com/en-us/dotnet/api/azure.identity.managedidentitycredential + /// + ManagedIdentity, + + /// + /// Authenticates in a development environment with the Azure CLI. + /// See https://docs.microsoft.com/en-us/dotnet/api/azure.identity.azureclicredential + /// + CLI, + + /// + /// Authenticates using tokens in the local cache shared between Microsoft applications. + /// See: https://docs.microsoft.com/en-us/dotnet/api/azure.identity.sharedtokencachecredential + /// + SharedToken, + + /// + /// Authenticates using data from Visual Studio. + /// See: https://docs.microsoft.com/en-us/dotnet/api/azure.identity.visualstudiocredential + /// + VisualStudio, + + /// + /// Authenticate in a development environment with Visual Studio Code. + /// See: https://docs.microsoft.com/en-us/dotnet/api/azure.identity.visualstudiocodecredential + /// + VisualStudioCode, + + /// + /// A TokenCredential implementation which launches the system default browser to interactively authenticate a user, + /// and obtain an access token. The browser will only be launched to authenticate the user once, + /// then will silently acquire access tokens through the users refresh token as long as it's valid. + /// See: https://docs.microsoft.com/en-us/dotnet/api/azure.identity.interactivebrowsercredential + /// + Interactive, + } + + public static class CredentialFactory + { + /// + /// Creates an instance of TokenCredential that corresponds to the given . + /// It creates an instance of the Credential Class with default parameters. + /// + /// The type of Credential Class to create. + /// An instance of TokenCredential for the corresponding value. + public static TokenCredential CreateCredential(CredentialTypes credentialType) => credentialType switch + { + CredentialTypes.Environment => new EnvironmentCredential(), + CredentialTypes.ManagedIdentity => new ManagedIdentityCredential(), + CredentialTypes.SharedToken => new SharedTokenCacheCredential(), + CredentialTypes.VisualStudio => new VisualStudioCredential(), + CredentialTypes.VisualStudioCode => new VisualStudioCodeCredential(), + CredentialTypes.CLI => new AzureCliCredential(), + CredentialTypes.Interactive => new InteractiveBrowserCredential(), + CredentialTypes.Default => new DefaultAzureCredential(includeInteractiveCredentials: true), + _ => throw new ArgumentException() + }; + } +} \ No newline at end of file diff --git a/src/Azure/Azure.Quantum.Client/Exceptions/MissingCredentialException.cs b/src/Azure/Azure.Quantum.Client/Exceptions/MissingCredentialException.cs new file mode 100644 index 00000000000..1a8cb2067d1 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/Exceptions/MissingCredentialException.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Azure.Quantum.Exceptions +{ + using System; + + /// + /// The exception that is thrown when the user tries to create a Workspace instance but provides no credential. + /// + public class MissingCredentialException : AzureQuantumException + { + private const string MESSAGE = + "You must provide a TokenCredential instance to connect to a Workspace. \n" + + "For information on how to authenticate with Azure services, see: \n" + + "https://docs.microsoft.com/en-us/dotnet/api/overview/azure/identity-readme"; + + /// + /// Initializes a new instance of the class with a default error message. + /// + public MissingCredentialException() + : base(MESSAGE) + { + } + } +} diff --git a/src/Azure/Azure.Quantum.Client/Exceptions/WorkspaceClientException.cs b/src/Azure/Azure.Quantum.Client/Exceptions/WorkspaceClientException.cs index 9c70b86fdcd..8f5c2327b89 100644 --- a/src/Azure/Azure.Quantum.Client/Exceptions/WorkspaceClientException.cs +++ b/src/Azure/Azure.Quantum.Client/Exceptions/WorkspaceClientException.cs @@ -12,10 +12,6 @@ public class WorkspaceClientException : AzureQuantumException { private const string BaseMessage = "An exception related to the Azure workspace client occurred"; - public string ErrorCode { get; } - - public int Status { get; } - /// /// Initializes a new instance of the class with a default error message. /// @@ -82,6 +78,10 @@ public WorkspaceClientException( } } + public string ErrorCode { get; } + + public int Status { get; } + /// /// Formats the contents of the inner exception in so it can be included in the /// exception message and presented in an informative way. diff --git a/src/Azure/Azure.Quantum.Client/JobManagement/Workspace.cs b/src/Azure/Azure.Quantum.Client/JobManagement/Workspace.cs index b8eb8349377..f2f97427044 100644 --- a/src/Azure/Azure.Quantum.Client/JobManagement/Workspace.cs +++ b/src/Azure/Azure.Quantum.Client/JobManagement/Workspace.cs @@ -38,7 +38,7 @@ public Workspace( string resourceGroupName, string workspaceName, string location, - TokenCredential credential = null, + TokenCredential credential, QuantumJobClientOptions options = default) { // Required parameters: @@ -50,13 +50,7 @@ public Workspace( // Optional parameters: if (credential == null) { - // We have to disable VisualStudio until 16.11 goes out, see: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1332071 - var credOptions = new DefaultAzureCredentialOptions() - { - ExcludeVisualStudioCredential = true, - }; - - credential = new DefaultAzureCredential(credOptions); + throw new MissingCredentialException(); } options ??= new QuantumJobClientOptions(); diff --git a/src/Simulation/EntryPointDriver/Azure.cs b/src/Simulation/EntryPointDriver/Azure.cs index ab0813e0cc8..999e95f0283 100644 --- a/src/Simulation/EntryPointDriver/Azure.cs +++ b/src/Simulation/EntryPointDriver/Azure.cs @@ -3,11 +3,14 @@ using System; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Azure.Core; +using Azure.Identity; using Microsoft.Azure.Quantum; +using Microsoft.Azure.Quantum.Authentication; using Microsoft.Azure.Quantum.Exceptions; using Microsoft.Quantum.Runtime; using Microsoft.Quantum.Simulation.Common.Exceptions; @@ -212,6 +215,22 @@ public enum OutputFormat /// public sealed class AzureSettings { + private class AADTokenCredential : TokenCredential + { + AccessToken Token { get; } + + public AADTokenCredential(string token) + { + Token = new AccessToken(token, DateTime.Now.AddMinutes(5)); + } + + public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) => + Token; + + public override ValueTask GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken) => + new ValueTask(Token); + } + /// /// The subscription ID. /// @@ -242,6 +261,14 @@ public sealed class AzureSettings /// public string? AadToken { get; set; } + /// + /// The type of Credentials to use to authenticate with Azure. For more information + /// about authentication with Azure services see: https://docs.microsoft.com/en-us/dotnet/api/overview/azure/identity-readme + /// NOTE: If both and properties are specified, takes precedence. + /// If none are provided, then it uses . + /// + public CredentialTypes? Credential { get; set; } + /// /// The base URI of the Azure Quantum endpoint. /// NOTE: This parameter is deprected, please always use . @@ -280,31 +307,15 @@ public sealed class AzureSettings /// public bool Verbose { get; set; } - /// - /// Print a warning about passing an AAD token. Using this is not supported anymore but we keep - /// the parameter to break any existing clients, like the az cli. - /// Once the known clients are updated we should remove the parameter too. - /// - internal void PrintAadWarning() + internal TokenCredential CreateCredentials() { - Console.ForegroundColor = ConsoleColor.Yellow; - if (!(AadToken is null)) { - try - { - Console.Error.WriteLine("----------------------------------------------------------------------------"); - Console.Error.WriteLine(" [Warning]"); - Console.Error.WriteLine(" The AadToken parameter is not supported anymore."); - Console.Error.WriteLine(" Take a look at the Azure Identity client library at"); - Console.Error.WriteLine(" https://docs.microsoft.com/en-us/dotnet/api/overview/azure/identity-readme"); - Console.Error.WriteLine(" for new authentication options."); - Console.Error.WriteLine("----------------------------------------------------------------------------"); - } - finally - { - Console.ResetColor(); - } + return new AADTokenCredential(AadToken); + } + else + { + return CredentialFactory.CreateCredential(Credential ?? CredentialTypes.Default); } } @@ -314,15 +325,15 @@ internal void PrintAadWarning() /// The based on the settings. internal Workspace CreateWorkspace() { - PrintAadWarning(); - + var credentials = CreateCredentials(); var location = NormalizeLocation(Location ?? ExtractLocation(BaseUri)); return new Workspace( subscriptionId: Subscription, resourceGroupName: ResourceGroup, workspaceName: Workspace, - location: location); + location: location, + credential: credentials); } public override string ToString() => From 95e4ef26320e2e358597da013665c4cfb7c8d2b2 Mon Sep 17 00:00:00 2001 From: Andres Paz Date: Thu, 10 Jun 2021 11:37:51 -0700 Subject: [PATCH 2/7] rename enum --- .../CredentialFactoryTests.cs | 20 ++++++------- .../WorkspaceTest.cs | 2 +- .../Authentication/CredentialFactory.cs | 30 +++++++++---------- src/Simulation/EntryPointDriver/Azure.cs | 6 ++-- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/Azure/Azure.Quantum.Client.Test/CredentialFactoryTests.cs b/src/Azure/Azure.Quantum.Client.Test/CredentialFactoryTests.cs index af899ae71b8..f8f0d50774e 100644 --- a/src/Azure/Azure.Quantum.Client.Test/CredentialFactoryTests.cs +++ b/src/Azure/Azure.Quantum.Client.Test/CredentialFactoryTests.cs @@ -14,15 +14,15 @@ namespace Microsoft.Azure.Quantum.Test public class CredentialFactoryTests { [DataTestMethod] - [DataRow(CredentialTypes.Default, typeof(DefaultAzureCredential))] - [DataRow(CredentialTypes.Environment, typeof(EnvironmentCredential))] - [DataRow(CredentialTypes.ManagedIdentity, typeof(ManagedIdentityCredential))] - [DataRow(CredentialTypes.CLI, typeof(AzureCliCredential))] - [DataRow(CredentialTypes.SharedToken, typeof(SharedTokenCacheCredential))] - [DataRow(CredentialTypes.VisualStudio, typeof(VisualStudioCredential))] - [DataRow(CredentialTypes.VisualStudioCode, typeof(VisualStudioCodeCredential))] - [DataRow(CredentialTypes.Interactive, typeof(InteractiveBrowserCredential))] - public void TestCreateCredential(CredentialTypes credentialType, Type expectedType) + [DataRow(CredentialType.Default, typeof(DefaultAzureCredential))] + [DataRow(CredentialType.Environment, typeof(EnvironmentCredential))] + [DataRow(CredentialType.ManagedIdentity, typeof(ManagedIdentityCredential))] + [DataRow(CredentialType.CLI, typeof(AzureCliCredential))] + [DataRow(CredentialType.SharedToken, typeof(SharedTokenCacheCredential))] + [DataRow(CredentialType.VisualStudio, typeof(VisualStudioCredential))] + [DataRow(CredentialType.VisualStudioCode, typeof(VisualStudioCodeCredential))] + [DataRow(CredentialType.Interactive, typeof(InteractiveBrowserCredential))] + public void TestCreateCredential(CredentialType credentialType, Type expectedType) { var actual = CredentialFactory.CreateCredential(credentialType); @@ -33,7 +33,7 @@ public void TestCreateCredential(CredentialTypes credentialType, Type expectedTy [TestMethod] public void TestInvalidCredentialType() { - Assert.ThrowsException(() => CredentialFactory.CreateCredential((CredentialTypes)9999)); + Assert.ThrowsException(() => CredentialFactory.CreateCredential((CredentialType)9999)); } } } diff --git a/src/Azure/Azure.Quantum.Client.Test/WorkspaceTest.cs b/src/Azure/Azure.Quantum.Client.Test/WorkspaceTest.cs index ad6b3669c47..e9c87e7ac03 100644 --- a/src/Azure/Azure.Quantum.Client.Test/WorkspaceTest.cs +++ b/src/Azure/Azure.Quantum.Client.Test/WorkspaceTest.cs @@ -198,7 +198,7 @@ private IWorkspace GetLiveWorkspace() var options = new QuantumJobClientOptions(); options.Diagnostics.ApplicationId = "ClientTests"; - var credential = Authentication.CredentialFactory.CreateCredential(Authentication.CredentialTypes.Default); + var credential = Authentication.CredentialFactory.CreateCredential(Authentication.CredentialType.Default); return new Workspace( subscriptionId: System.Environment.GetEnvironmentVariable("E2E_SUBSCRIPTION_ID"), diff --git a/src/Azure/Azure.Quantum.Client/Authentication/CredentialFactory.cs b/src/Azure/Azure.Quantum.Client/Authentication/CredentialFactory.cs index 0a054eb0400..c0ebe7e64a7 100644 --- a/src/Azure/Azure.Quantum.Client/Authentication/CredentialFactory.cs +++ b/src/Azure/Azure.Quantum.Client/Authentication/CredentialFactory.cs @@ -17,7 +17,7 @@ namespace Microsoft.Azure.Quantum.Authentication /// about authentication with Azure services and the different Credential types see /// https://docs.microsoft.com/en-us/dotnet/api/overview/azure/identity-readme. /// - public enum CredentialTypes + public enum CredentialType { /// /// Provides a simplified authentication experience to quickly start developing applications run in the Azure cloud. @@ -38,19 +38,19 @@ public enum CredentialTypes ManagedIdentity, /// - /// Authenticates in a development environment with the Azure CLI. + /// Authenticate in a development environment with the Azure CLI. /// See https://docs.microsoft.com/en-us/dotnet/api/azure.identity.azureclicredential /// CLI, /// - /// Authenticates using tokens in the local cache shared between Microsoft applications. + /// Authenticate using tokens in the local cache shared between Microsoft applications. /// See: https://docs.microsoft.com/en-us/dotnet/api/azure.identity.sharedtokencachecredential /// SharedToken, /// - /// Authenticates using data from Visual Studio. + /// Authenticate using data from Visual Studio. /// See: https://docs.microsoft.com/en-us/dotnet/api/azure.identity.visualstudiocredential /// VisualStudio, @@ -73,22 +73,22 @@ public enum CredentialTypes public static class CredentialFactory { /// - /// Creates an instance of TokenCredential that corresponds to the given . + /// Creates an instance of TokenCredential that corresponds to the given . /// It creates an instance of the Credential Class with default parameters. /// /// The type of Credential Class to create. /// An instance of TokenCredential for the corresponding value. - public static TokenCredential CreateCredential(CredentialTypes credentialType) => credentialType switch + public static TokenCredential CreateCredential(CredentialType credentialType) => credentialType switch { - CredentialTypes.Environment => new EnvironmentCredential(), - CredentialTypes.ManagedIdentity => new ManagedIdentityCredential(), - CredentialTypes.SharedToken => new SharedTokenCacheCredential(), - CredentialTypes.VisualStudio => new VisualStudioCredential(), - CredentialTypes.VisualStudioCode => new VisualStudioCodeCredential(), - CredentialTypes.CLI => new AzureCliCredential(), - CredentialTypes.Interactive => new InteractiveBrowserCredential(), - CredentialTypes.Default => new DefaultAzureCredential(includeInteractiveCredentials: true), - _ => throw new ArgumentException() + CredentialType.Environment => new EnvironmentCredential(), + CredentialType.ManagedIdentity => new ManagedIdentityCredential(), + CredentialType.SharedToken => new SharedTokenCacheCredential(), + CredentialType.VisualStudio => new VisualStudioCredential(), + CredentialType.VisualStudioCode => new VisualStudioCodeCredential(), + CredentialType.CLI => new AzureCliCredential(), + CredentialType.Interactive => new InteractiveBrowserCredential(), + CredentialType.Default => new DefaultAzureCredential(includeInteractiveCredentials: true), + _ => throw new ArgumentException($"Credentials of type {credentialType} are not supported.") }; } } \ No newline at end of file diff --git a/src/Simulation/EntryPointDriver/Azure.cs b/src/Simulation/EntryPointDriver/Azure.cs index 999e95f0283..dcb6ce9362c 100644 --- a/src/Simulation/EntryPointDriver/Azure.cs +++ b/src/Simulation/EntryPointDriver/Azure.cs @@ -265,9 +265,9 @@ public override ValueTask GetTokenAsync(TokenRequestContext request /// The type of Credentials to use to authenticate with Azure. For more information /// about authentication with Azure services see: https://docs.microsoft.com/en-us/dotnet/api/overview/azure/identity-readme /// NOTE: If both and properties are specified, takes precedence. - /// If none are provided, then it uses . + /// If none are provided, then it uses . /// - public CredentialTypes? Credential { get; set; } + public CredentialType? Credential { get; set; } /// /// The base URI of the Azure Quantum endpoint. @@ -315,7 +315,7 @@ internal TokenCredential CreateCredentials() } else { - return CredentialFactory.CreateCredential(Credential ?? CredentialTypes.Default); + return CredentialFactory.CreateCredential(Credential ?? CredentialType.Default); } } From 400965283a4be7d454ef79da2087f71ddca44e36 Mon Sep 17 00:00:00 2001 From: Andres Paz Date: Sat, 12 Jun 2021 14:21:53 -0700 Subject: [PATCH 3/7] Automatically calculate tenantid for subscription --- .../CredentialFactoryTests.cs | 34 ++++ .../Authentication/CredentialFactory.cs | 179 +++++++++++++++++- .../JobManagement/Workspace.cs | 9 +- src/Simulation/EntryPointDriver/Azure.cs | 2 +- 4 files changed, 210 insertions(+), 14 deletions(-) diff --git a/src/Azure/Azure.Quantum.Client.Test/CredentialFactoryTests.cs b/src/Azure/Azure.Quantum.Client.Test/CredentialFactoryTests.cs index f8f0d50774e..5bc8209455f 100644 --- a/src/Azure/Azure.Quantum.Client.Test/CredentialFactoryTests.cs +++ b/src/Azure/Azure.Quantum.Client.Test/CredentialFactoryTests.cs @@ -13,6 +13,8 @@ namespace Microsoft.Azure.Quantum.Test [TestClass] public class CredentialFactoryTests { + private const string SUBSCRIPTION = "916dfd6d-030c-4bd9-b579-7bb6d1926e97"; + [DataTestMethod] [DataRow(CredentialType.Default, typeof(DefaultAzureCredential))] [DataRow(CredentialType.Environment, typeof(EnvironmentCredential))] @@ -25,7 +27,11 @@ public class CredentialFactoryTests public void TestCreateCredential(CredentialType credentialType, Type expectedType) { var actual = CredentialFactory.CreateCredential(credentialType); + Assert.IsNotNull(actual); + Assert.AreEqual(expectedType, actual.GetType()); + // Now test with a specific subscription id: + actual = CredentialFactory.CreateCredential(credentialType, SUBSCRIPTION); Assert.IsNotNull(actual); Assert.AreEqual(expectedType, actual.GetType()); } @@ -35,5 +41,33 @@ public void TestInvalidCredentialType() { Assert.ThrowsException(() => CredentialFactory.CreateCredential((CredentialType)9999)); } + + [TestMethod] + public void TestGetTenantId() + { + var actual = CredentialFactory.GetTenantId(SUBSCRIPTION); + Assert.IsNotNull(actual); + Assert.IsFalse(string.IsNullOrWhiteSpace(actual)); + + var actual2 = CredentialFactory.GetTenantId(SUBSCRIPTION); + Assert.AreEqual(actual, actual2); + } + + [DataTestMethod] + [DataRow(null, null)] + [DataRow("", null)] + [DataRow("some random string", null)] + [DataRow("string,with,random,values", null)] + [DataRow("string=with,random=,key=values", null)] + [DataRow("string=with,random=,authorization_uri=", null)] + [DataRow("string=with,invalid=authorization_uri,authorization_uri=some-random-value", null)] + [DataRow("string=with,invalid=authorization_uri,authorization_uri=http://foo.bar.com/some-random-value", null)] + [DataRow("string=missing,tenant_id=authorization_uri,authorization_uri=\"http://foo.bar.com/", null)] + [DataRow("authorization_uri=\"https://login.microsoftonline.com/tenantId\",key1=value1s,etc...", "tenantId")] + public void TestExtractTenantIdFromBearer(string bearer, string expected) + { + var actual = CredentialFactory.ExtractTenantIdFromBearer(bearer); + Assert.AreEqual(expected, actual); + } } } diff --git a/src/Azure/Azure.Quantum.Client/Authentication/CredentialFactory.cs b/src/Azure/Azure.Quantum.Client/Authentication/CredentialFactory.cs index c0ebe7e64a7..2ce4c555a22 100644 --- a/src/Azure/Azure.Quantum.Client/Authentication/CredentialFactory.cs +++ b/src/Azure/Azure.Quantum.Client/Authentication/CredentialFactory.cs @@ -6,6 +6,9 @@ namespace Microsoft.Azure.Quantum.Authentication { using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; using global::Azure.Core; using global::Azure.Identity; @@ -72,23 +75,185 @@ public enum CredentialType public static class CredentialFactory { + // Used to fetch the tenantId automatically from ARM + private static readonly HttpClient Client = new HttpClient(); + + // Used to catch all the TenantIds: + private static readonly Dictionary TenantIds = new Dictionary(); + + public static TokenCredential CreateCredential(CredentialType credentialType, string? subscriptionId = null) => credentialType switch + { + CredentialType.SharedToken => CreateCredential(credentialType, () => SharedTokenOptions(subscriptionId)), + CredentialType.VisualStudio => CreateCredential(credentialType, () => VisualStudioOptions(subscriptionId)), + CredentialType.VisualStudioCode => CreateCredential(credentialType, () => VisualStudioCodeOptions(subscriptionId)), + CredentialType.Interactive => CreateCredential(credentialType, () => InteractiveOptions(subscriptionId)), + CredentialType.Default => CreateCredential(credentialType, () => DefaultOptions(subscriptionId)), + _ => CreateCredential(credentialType, () => DefaultOptions(subscriptionId)), + }; + /// /// Creates an instance of TokenCredential that corresponds to the given . /// It creates an instance of the Credential Class with default parameters. /// /// The type of Credential Class to create. + /// A configuration method for the corresponding credential options (not used for Environment, ManagedIdentity or CLI credentials). /// An instance of TokenCredential for the corresponding value. - public static TokenCredential CreateCredential(CredentialType credentialType) => credentialType switch + public static TokenCredential CreateCredential(CredentialType credentialType, Func options) => credentialType switch { CredentialType.Environment => new EnvironmentCredential(), CredentialType.ManagedIdentity => new ManagedIdentityCredential(), - CredentialType.SharedToken => new SharedTokenCacheCredential(), - CredentialType.VisualStudio => new VisualStudioCredential(), - CredentialType.VisualStudioCode => new VisualStudioCodeCredential(), CredentialType.CLI => new AzureCliCredential(), - CredentialType.Interactive => new InteractiveBrowserCredential(), - CredentialType.Default => new DefaultAzureCredential(includeInteractiveCredentials: true), + CredentialType.SharedToken => new SharedTokenCacheCredential(options: options?.Invoke() as SharedTokenCacheCredentialOptions), + CredentialType.VisualStudio => new VisualStudioCredential(options: options?.Invoke() as VisualStudioCredentialOptions), + CredentialType.VisualStudioCode => new VisualStudioCodeCredential(options: options?.Invoke() as VisualStudioCodeCredentialOptions), + CredentialType.Interactive => new InteractiveBrowserCredential(options: options?.Invoke() as InteractiveBrowserCredentialOptions), + CredentialType.Default => new DefaultAzureCredential(options: options?.Invoke() as DefaultAzureCredentialOptions), _ => throw new ArgumentException($"Credentials of type {credentialType} are not supported.") }; + + /// + /// Returns an DefaultAzureCredentialOptions, populated with the TenantId for the given subscription. + /// We als disabilitate VisualStudio credentials, since they don't currently work with Azure Quantum. + /// + /// An subscription Id. + /// A new instance of InteractiveBrowserCredentialOptions with the TenantId populated + public static DefaultAzureCredentialOptions DefaultOptions(string? subscriptionid) + { + string? tenantId = GetTenantId(subscriptionid); + + return new DefaultAzureCredentialOptions + { + // Disable VS credentials until https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1332071 is fixed: + ExcludeVisualStudioCredential = true, + ExcludeInteractiveBrowserCredential = false, + + InteractiveBrowserTenantId = tenantId, + SharedTokenCacheTenantId = tenantId, + VisualStudioCodeTenantId = tenantId, + VisualStudioTenantId = tenantId, + }; + } + + /// + /// Returns an InteractiveBrowserCredentialOptions, populated with the TenantId for the given subscription. + /// + /// An subscription Id. + /// A new instance of InteractiveBrowserCredentialOptions with the TenantId populated + public static InteractiveBrowserCredentialOptions InteractiveOptions(string? subscriptionid) => + new InteractiveBrowserCredentialOptions + { + TenantId = GetTenantId(subscriptionid), + }; + + /// + /// Returns an VisualStudioCodeCredentialOptions, populated with the TenantId for the given subscription. + /// + /// An subscription Id. + /// A new instance of InteractiveBrowserCredentialOptions with the TenantId populated + public static VisualStudioCodeCredentialOptions VisualStudioCodeOptions(string? subscriptionid) => + new VisualStudioCodeCredentialOptions + { + TenantId = GetTenantId(subscriptionid), + }; + + /// + /// Returns an VisualStudioCredentialOptions, populated with the TenantId for the given subscription. + /// + /// An subscription Id. + /// A new instance of InteractiveBrowserCredentialOptions with the TenantId populated + public static VisualStudioCredentialOptions VisualStudioOptions(string? subscriptionid) => + new VisualStudioCredentialOptions + { + TenantId = GetTenantId(subscriptionid), + }; + + /// + /// Returns an SharedTokenCacheCredentialOptions, populated with the TenantId for the given subscription. + /// + /// An subscription Id. + /// A new instance of InteractiveBrowserCredentialOptions with the TenantId populated + public static SharedTokenCacheCredentialOptions SharedTokenOptions(string? subscriptionid) => + new SharedTokenCacheCredentialOptions + { + TenantId = GetTenantId(subscriptionid), + }; + + /// + /// This gnarly piece of code is how we get the guest tenant + /// authority associated with the subscription. + /// We make a unauthenticated request to ARM and extract the tenant + /// authority from the WWW-Authenticate header in the response. + /// + /// The subscriptionId. + /// The tenantId for the given subscription; null if it can be found or for a null subscription. + public static string? GetTenantId(string? subscriptionId) + { + if (subscriptionId == null) + { + return null; + } + + if (TenantIds.TryGetValue(subscriptionId, out string? tenantId)) + { + return tenantId; + } + + try + { + string url = $"https://management.azure.com/subscriptions/{subscriptionId}?api-version=2020-01-01"; + HttpResponseMessage response = Client.GetAsync(url).Result; + var header = response + .Headers + .WwwAuthenticate + .FirstOrDefault(v => v.Scheme == "Bearer") + ?.Parameter; + + tenantId = ExtractTenantIdFromBearer(header); + TenantIds[subscriptionId] = tenantId; + + return tenantId; + } + catch + { + return null; + } + } + + /// + /// Here we parse WWW-Authenticate header in the response to match the tenant id. + /// The header is of the form: + /// Bearer authorization_uri="https://login.microsoftonline.com/tenantId",key1=value1s,etc... + /// + /// The value of the Bearer in the WWWAuthenticate header + /// The tenant-id, or null if it can't find it. + public static string? ExtractTenantIdFromBearer(string? bearer) + { + if (bearer == null) + { + return null; + } + + // Split the key=value comma seperated list and look for the "authorization_uri" key: + var auth_uri = bearer + .Split(',') + .Select(kv => kv.Split('=', 2)) + .FirstOrDefault(pair => pair[0] == "authorization_uri")?[1]; + + // If found an authorization_uri, find the tenant id from a URL surrounded by quotes, i.e.: + // "https://login.microsoftonline.com/tenantId" + if (auth_uri != null && auth_uri.StartsWith('"') && auth_uri.EndsWith('"')) + { + var id = auth_uri + [1 .. ^1] + [auth_uri.LastIndexOf('/') .. ]; + + return id; + } + else + { + return null; + } + + } } -} \ No newline at end of file +} diff --git a/src/Azure/Azure.Quantum.Client/JobManagement/Workspace.cs b/src/Azure/Azure.Quantum.Client/JobManagement/Workspace.cs index f2f97427044..e6c9a7e1f97 100644 --- a/src/Azure/Azure.Quantum.Client/JobManagement/Workspace.cs +++ b/src/Azure/Azure.Quantum.Client/JobManagement/Workspace.cs @@ -15,6 +15,7 @@ namespace Microsoft.Azure.Quantum using global::Azure.Quantum.Jobs; using global::Azure.Quantum.Jobs.Models; + using Microsoft.Azure.Quantum.Authentication; using Microsoft.Azure.Quantum.Exceptions; using Microsoft.Azure.Quantum.Utility; @@ -38,7 +39,7 @@ public Workspace( string resourceGroupName, string workspaceName, string location, - TokenCredential credential, + TokenCredential credential = null, QuantumJobClientOptions options = default) { // Required parameters: @@ -48,11 +49,7 @@ public Workspace( Ensure.NotNullOrWhiteSpace(location, nameof(location)); // Optional parameters: - if (credential == null) - { - throw new MissingCredentialException(); - } - + credential ??= CredentialFactory.CreateCredential(CredentialType.Default, subscriptionId); options ??= new QuantumJobClientOptions(); this.ResourceGroupName = resourceGroupName; diff --git a/src/Simulation/EntryPointDriver/Azure.cs b/src/Simulation/EntryPointDriver/Azure.cs index dcb6ce9362c..8ff1f321108 100644 --- a/src/Simulation/EntryPointDriver/Azure.cs +++ b/src/Simulation/EntryPointDriver/Azure.cs @@ -315,7 +315,7 @@ internal TokenCredential CreateCredentials() } else { - return CredentialFactory.CreateCredential(Credential ?? CredentialType.Default); + return CredentialFactory.CreateCredential(Credential ?? CredentialType.Default, Subscription); } } From 190f753487caf7e2da2f81291abe9c14d9a68410 Mon Sep 17 00:00:00 2001 From: Andres Paz Date: Sat, 12 Jun 2021 23:16:14 -0700 Subject: [PATCH 4/7] fix cloudjob uri. --- src/Azure/Azure.Quantum.Client/JobManagement/CloudJob.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Azure/Azure.Quantum.Client/JobManagement/CloudJob.cs b/src/Azure/Azure.Quantum.Client/JobManagement/CloudJob.cs index 05c92de3e0b..aa7c5eb30fd 100644 --- a/src/Azure/Azure.Quantum.Client/JobManagement/CloudJob.cs +++ b/src/Azure/Azure.Quantum.Client/JobManagement/CloudJob.cs @@ -142,12 +142,7 @@ public async Task WaitForCompletion(int pollIntervalMilliseconds = 500, Cancella private Uri GenerateUri() { - if (!(this.Workspace is Workspace cloudWorkspace)) - { - throw new NotSupportedException($"{typeof(CloudJob)}'s Workspace is not of type {typeof(Workspace)} and does not have enough data to generate URI"); - } - - var uriStr = $"https://ms.portal.azure.com/#@microsoft.onmicrosoft.com/resource/subscriptions/{cloudWorkspace.SubscriptionId}/resourceGroups/{cloudWorkspace.ResourceGroupName}/providers/Microsoft.Quantum/Workspaces/{cloudWorkspace.WorkspaceName}/job_management?microsoft_azure_quantum_jobid={Id}"; + var uriStr = $"https://portal.azure.com/#@microsoft.onmicrosoft.com/resource/subscriptions/{Workspace.SubscriptionId}/resourceGroups/{Workspace.ResourceGroupName}/providers/Microsoft.Quantum/Workspaces/{Workspace.WorkspaceName}/job_management?microsoft_azure_quantum_jobid={Id}"; return new Uri(uriStr); } } From d2410b43d9ec949808e1742f0546a81837fdc9e8 Mon Sep 17 00:00:00 2001 From: Andres Paz Date: Sun, 13 Jun 2021 00:04:09 -0700 Subject: [PATCH 5/7] adding credential option to entrypoint --- src/Simulation/EntryPointDriver.Tests/Tests.fs | 16 ++++++++++++++++ src/Simulation/EntryPointDriver/Azure.cs | 1 + src/Simulation/EntryPointDriver/Driver.cs | 16 ++++++++++++++-- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/Simulation/EntryPointDriver.Tests/Tests.fs b/src/Simulation/EntryPointDriver.Tests/Tests.fs index b5b9837b9d6..9d75e67c648 100644 --- a/src/Simulation/EntryPointDriver.Tests/Tests.fs +++ b/src/Simulation/EntryPointDriver.Tests/Tests.fs @@ -568,6 +568,7 @@ let ``Submit uses default values`` () = Storage: Base URI: Location: myLocation + Credential: Default Job Name: Shots: 500 Output: FriendlyUri @@ -587,6 +588,7 @@ let ``Submit uses default values with default target`` () = Storage: Base URI: Location: myLocation + Credential: Default Job Name: Shots: 500 Output: FriendlyUri @@ -608,6 +610,8 @@ let ``Submit allows overriding default values`` () = "myJobName" "--shots" "750" + "--credential" + "cli" ]) |> yields "Subscription: mySubscription Resource Group: myResourceGroup @@ -616,6 +620,7 @@ let ``Submit allows overriding default values`` () = Storage: myStorage Base URI: Location: myLocation + Credential: CLI Job Name: myJobName Shots: 750 Output: FriendlyUri @@ -637,6 +642,8 @@ let ``Submit extracts the location from a quantum endpoint`` () = "https://westus.quantum.microsoft.com/" "--job-name" "myJobName" + "--credential" + "VisualStudio" "--shots" "750" "--target" @@ -649,6 +656,7 @@ let ``Submit extracts the location from a quantum endpoint`` () = Storage: myStorage Base URI: https://westus.quantum.microsoft.com/ Location: westus + Credential: VisualStudio Job Name: myJobName Shots: 750 Output: FriendlyUri @@ -664,6 +672,8 @@ let ``Submit allows overriding default values with default target`` () = "--verbose" "--storage" "myStorage" + "--credential" + "Interactive" "--aad-token" "myToken" "--job-name" @@ -678,6 +688,7 @@ let ``Submit allows overriding default values with default target`` () = Storage: myStorage Base URI: Location: myLocation + Credential: Interactive Job Name: myJobName Shots: 750 Output: FriendlyUri @@ -712,6 +723,7 @@ let ``Submit allows to include --base-uri option when --location is not present` Storage: Base URI: http://mybaseuri.foo.com/ Location: mybaseuri + Credential: Default Job Name: Shots: 500 Output: FriendlyUri @@ -733,6 +745,7 @@ let ``Submit allows to include --location option when --base-uri is not present` Storage: Base URI: Location: myLocation + Credential: Default Job Name: Shots: 500 Output: FriendlyUri @@ -758,6 +771,7 @@ let ``Submit allows spaces for the --location option`` () = Storage: Base URI: Location: My Location + Credential: Default Job Name: Shots: 500 Output: FriendlyUri @@ -870,6 +884,7 @@ let ``Shows help text for submit command`` () = --resource-group (REQUIRED) The resource group name. --workspace (REQUIRED) The workspace name. --target (REQUIRED) The target device ID. + --credential The type of credential to use to authenticate with Azure. --storage The storage account connection string. --aad-token The Azure Active Directory authentication token. --base-uri The base URI of the Azure Quantum endpoint. @@ -899,6 +914,7 @@ let ``Shows help text for submit command with default target`` () = --resource-group (REQUIRED) The resource group name. --workspace (REQUIRED) The workspace name. --target The target device ID. + --credential The type of credential to use to authenticate with Azure. --storage The storage account connection string. --aad-token The Azure Active Directory authentication token. --base-uri The base URI of the Azure Quantum endpoint. diff --git a/src/Simulation/EntryPointDriver/Azure.cs b/src/Simulation/EntryPointDriver/Azure.cs index 8ff1f321108..4cbd1b28c1d 100644 --- a/src/Simulation/EntryPointDriver/Azure.cs +++ b/src/Simulation/EntryPointDriver/Azure.cs @@ -345,6 +345,7 @@ public override string ToString() => $"Storage: {Storage}", $"Base URI: {BaseUri}", $"Location: {Location ?? ExtractLocation(BaseUri)}", + $"Credential: {Credential}", $"Job Name: {JobName}", $"Shots: {Shots}", $"Output: {Output}", diff --git a/src/Simulation/EntryPointDriver/Driver.cs b/src/Simulation/EntryPointDriver/Driver.cs index 796cc4556a0..1a3943c0f00 100644 --- a/src/Simulation/EntryPointDriver/Driver.cs +++ b/src/Simulation/EntryPointDriver/Driver.cs @@ -14,6 +14,8 @@ using System.Text; using System.Threading.Tasks; +using Microsoft.Azure.Quantum.Authentication; + namespace Microsoft.Quantum.EntryPointDriver { using Validators = ImmutableList>; @@ -47,6 +49,14 @@ public sealed class Driver private static readonly OptionInfo StorageOption = new OptionInfo( ImmutableList.Create("--storage"), default, "The storage account connection string."); + /// + /// The credential option. + /// + private static readonly OptionInfo CredentialOption = new OptionInfo( + ImmutableList.Create("--credential"), + CredentialType.Default, + "The type of credential to use to authenticate with Azure."); + /// /// The AAD token option. /// @@ -417,7 +427,8 @@ private CommandWithValidators CreateSubmitEntryPointCommand(IEntryPoint entryPoi var validators = AddOptionIfAvailable(command, SubscriptionOption) .Concat(AddOptionIfAvailable(command, ResourceGroupOption)) .Concat(AddOptionIfAvailable(command, WorkspaceOption)) - .Concat(AddOptionIfAvailable(command, this.TargetOption)) + .Concat(AddOptionIfAvailable(command, TargetOption)) + .Concat(AddOptionIfAvailable(command, CredentialOption)) .Concat(AddOptionIfAvailable(command, StorageOption)) .Concat(AddOptionIfAvailable(command, AadTokenOption)) .Concat(AddOptionIfAvailable(command, BaseUriOption)) @@ -457,10 +468,11 @@ private Task Submit(ParseResult parseResult, AzureSettings azureSettings, I Subscription = azureSettings.Subscription, ResourceGroup = azureSettings.ResourceGroup, Workspace = azureSettings.Workspace, - Target = DefaultIfShadowed(entryPoint, this.TargetOption, azureSettings.Target), + Target = DefaultIfShadowed(entryPoint, TargetOption, azureSettings.Target), Storage = DefaultIfShadowed(entryPoint, StorageOption, azureSettings.Storage), BaseUri = DefaultIfShadowed(entryPoint, BaseUriOption, azureSettings.BaseUri), Location = DefaultIfShadowed(entryPoint, LocationOption, azureSettings.Location), + Credential = DefaultIfShadowed(entryPoint, CredentialOption, azureSettings.Credential), JobName = DefaultIfShadowed(entryPoint, JobNameOption, azureSettings.JobName), Shots = DefaultIfShadowed(entryPoint, ShotsOption, azureSettings.Shots), Output = DefaultIfShadowed(entryPoint, OutputOption, azureSettings.Output), From 5e42ad7e3d96e3c51d7155bcafc7aa2553397fa5 Mon Sep 17 00:00:00 2001 From: Andres Paz Date: Tue, 15 Jun 2021 11:30:07 -0700 Subject: [PATCH 6/7] Remove unused exception. --- .../Exceptions/MissingCredentialException.cs | 26 ------------------ ...sts.Microsoft.Quantum.EntryPointDriver.sln | Bin 0 -> 14910 bytes 2 files changed, 26 deletions(-) delete mode 100644 src/Azure/Azure.Quantum.Client/Exceptions/MissingCredentialException.cs create mode 100644 src/Simulation/EntryPointDriver.Tests/Tests.Microsoft.Quantum.EntryPointDriver.sln diff --git a/src/Azure/Azure.Quantum.Client/Exceptions/MissingCredentialException.cs b/src/Azure/Azure.Quantum.Client/Exceptions/MissingCredentialException.cs deleted file mode 100644 index 1a8cb2067d1..00000000000 --- a/src/Azure/Azure.Quantum.Client/Exceptions/MissingCredentialException.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -namespace Microsoft.Azure.Quantum.Exceptions -{ - using System; - - /// - /// The exception that is thrown when the user tries to create a Workspace instance but provides no credential. - /// - public class MissingCredentialException : AzureQuantumException - { - private const string MESSAGE = - "You must provide a TokenCredential instance to connect to a Workspace. \n" + - "For information on how to authenticate with Azure services, see: \n" + - "https://docs.microsoft.com/en-us/dotnet/api/overview/azure/identity-readme"; - - /// - /// Initializes a new instance of the class with a default error message. - /// - public MissingCredentialException() - : base(MESSAGE) - { - } - } -} diff --git a/src/Simulation/EntryPointDriver.Tests/Tests.Microsoft.Quantum.EntryPointDriver.sln b/src/Simulation/EntryPointDriver.Tests/Tests.Microsoft.Quantum.EntryPointDriver.sln new file mode 100644 index 0000000000000000000000000000000000000000..00e2b89cd813d2248845a802044c95f1a143346b GIT binary patch literal 14910 zcmdU$T~Fgi6o%(^EA>CDxY|lh6vzijrC#KdR%$C2w(Si!3jxXs$QCcFQrf@X_Ibw% zcEWh##$%(%N-`dgXU?4WJ!j5*}_vV_rWAg6J zl)Ra}n~{4@p6+kSnUK0R=jLmho>A8gZ^reUa~D08+%%)nlH?uZS@0ho>BUZ z*)fl%XZmKB?_J6?skug~!|y(+7O9Tuk{Xz?X>(tjmml-I&%MgExjN==YWoU#zU8xH zp7*g%`9JB!9j({RTie%L+s_;BhxF%)C(7Be{n{b#8*^fg$o*(_bz**}XTQC*rsAv?5~3E2S{Hpm;0*R@Y4Y!9+W zOBNSnDtj&uPt~3e%PiTl5fp9%EUX7g1JLU7+XopTC{H*h)#qu0wDP+A?t$4pp9z=l z`y1J&>giLDI3Vkw576x` zpSzTumW8Ch)qDuAlfBMZ>hi+EP=0u5^(s``-1oT`sy(zWZFkYH#=v-l8-a|*wIQ1B zkSl#};)TmW`W>i6jFb^0r)#8LMP1{@CvbjX?3m%5bvNSv%6zu?mxKDcR_i9|$4M$3 zrcYUHXn^WIz3;#Qjm9-FmQ9Z=8~UVWh4KYs>j4^+8I!wfxiGYB8Ij(E4b{b3+rfZ* zcE6rS?&zxv(u$z=z)lfZjc*~TRLfp%Qi{D4%f`N5DDU!2cDoA;+TguiNV{Sq#lAx< zOrHJ>i(f9I`NQ()S&lzgCqq6qEX%%#F4xt)+`p`tK-R61q6t&PgPI+ml`F<5I*`B6 z=quJ}rlj(k{q!iQabgH>wv91wQ!zo=F^eNf-T)0Y$B<%#N!&``uI z`vHu9wZ8o$YY>X(%kkgY?d8Ka`~9p~ZD=vn94%f+QLHAU;#f^{Ypqgg9jc4v%EtAp zOLHrwC%iG<2-la3)s{y%`N*gwL|rZA!_xI;mURyBs0~oiOloYg)4W%amV9xSXR=QD zX00V^%++|R@m6*{CZ*ZewvDRq@Va;A5_^p2Y2Vvfm#p(*9)*>KYP#hS?O)g#-xYGi z(Ne^Uic6Y&$fs-9{L@ua78AOlsd06WD_;B58jR*zVuIG66<=wsS)8eXyH?fWb!qWL z)~p%YIzN4YSDcws^n7jCO<$RG&M`XC`gg32H$*?n>+6b3ZmrfX89&pjBZu=f%?nCs z%{_Wr@z&a};_Bs^w6gb#ODLRD*QNOj`ET*quj$F5`FXxxp8b4L!x1%T#s58Ro@dnJ zFB8|KxzyC7jkuIgTg!V0o9Rm9)~8HugeYL+aAcv7)>TwU<#QFRPJpb6$p%ZynZI&QYR+m2W${B9wr956%C8JA^ zdHiKa`TV#Q_m@@5>e8pbe~JBs+XE_(msQE=(qkT8hLq2bTX9}iDXUAL{{AJr47X!d z9xtns(WS>cybLLyAGhMXtWs8&KK=bmco}YAtUO*;C8JA^d3YI8K0j{7d0C~bE`9p@ zm+&&&E?;@PtV%|g9`o=rqF-~{%W(UtC@l8gqPuVh|A+;RWiEtn1`1k z<@4iKoR?L~>e8pbe+e(c?N^t_%c^8_=`jy4L(1pJtvD~Ml+~qAfB#}$7T>{NJ*5!u z=*K6=bSgssEm-`M&wsArJ4#O}zgpu@oP$t){BJ;=*!qDod&RK>C z2~O?)xX!mcES+pf_ribTU{(GDyGJ~o%m2u^6P*dw|3q|lMW+h&-;kC)t)%mYI(4WM fOgdezl+LZ_UtT)rG$P%#_xi6;ehi;$N$>s#n6Iw1 literal 0 HcmV?d00001 From 9594b43c2dbb1b0e87b43fc772c908078316303b Mon Sep 17 00:00:00 2001 From: Andres Paz Date: Tue, 15 Jun 2021 17:41:26 -0700 Subject: [PATCH 7/7] Adding support for DeviceCode --- .../CredentialFactoryTests.cs | 1 + .../Authentication/CredentialFactory.cs | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/Azure/Azure.Quantum.Client.Test/CredentialFactoryTests.cs b/src/Azure/Azure.Quantum.Client.Test/CredentialFactoryTests.cs index 5bc8209455f..908224c54ef 100644 --- a/src/Azure/Azure.Quantum.Client.Test/CredentialFactoryTests.cs +++ b/src/Azure/Azure.Quantum.Client.Test/CredentialFactoryTests.cs @@ -24,6 +24,7 @@ public class CredentialFactoryTests [DataRow(CredentialType.VisualStudio, typeof(VisualStudioCredential))] [DataRow(CredentialType.VisualStudioCode, typeof(VisualStudioCodeCredential))] [DataRow(CredentialType.Interactive, typeof(InteractiveBrowserCredential))] + [DataRow(CredentialType.DeviceCode, typeof(DeviceCodeCredential))] public void TestCreateCredential(CredentialType credentialType, Type expectedType) { var actual = CredentialFactory.CreateCredential(credentialType); diff --git a/src/Azure/Azure.Quantum.Client/Authentication/CredentialFactory.cs b/src/Azure/Azure.Quantum.Client/Authentication/CredentialFactory.cs index 2ce4c555a22..ce0f3347e04 100644 --- a/src/Azure/Azure.Quantum.Client/Authentication/CredentialFactory.cs +++ b/src/Azure/Azure.Quantum.Client/Authentication/CredentialFactory.cs @@ -71,6 +71,13 @@ public enum CredentialType /// See: https://docs.microsoft.com/en-us/dotnet/api/azure.identity.interactivebrowsercredential /// Interactive, + + /// + /// A TokenCredential implementation which authenticates a user using the device code flow, + /// and provides access tokens for that user account. + /// See: https://docs.microsoft.com/en-us/dotnet/api/azure.identity.devicecodecredential + /// + DeviceCode, } public static class CredentialFactory @@ -87,6 +94,7 @@ public static class CredentialFactory CredentialType.VisualStudio => CreateCredential(credentialType, () => VisualStudioOptions(subscriptionId)), CredentialType.VisualStudioCode => CreateCredential(credentialType, () => VisualStudioCodeOptions(subscriptionId)), CredentialType.Interactive => CreateCredential(credentialType, () => InteractiveOptions(subscriptionId)), + CredentialType.DeviceCode => CreateCredential(credentialType, () => DeviceCodeOptions(subscriptionId)), CredentialType.Default => CreateCredential(credentialType, () => DefaultOptions(subscriptionId)), _ => CreateCredential(credentialType, () => DefaultOptions(subscriptionId)), }; @@ -103,6 +111,7 @@ public static class CredentialFactory CredentialType.Environment => new EnvironmentCredential(), CredentialType.ManagedIdentity => new ManagedIdentityCredential(), CredentialType.CLI => new AzureCliCredential(), + CredentialType.DeviceCode => new DeviceCodeCredential(options: options?.Invoke() as DeviceCodeCredentialOptions), CredentialType.SharedToken => new SharedTokenCacheCredential(options: options?.Invoke() as SharedTokenCacheCredentialOptions), CredentialType.VisualStudio => new VisualStudioCredential(options: options?.Invoke() as VisualStudioCredentialOptions), CredentialType.VisualStudioCode => new VisualStudioCodeCredential(options: options?.Invoke() as VisualStudioCodeCredentialOptions), @@ -178,6 +187,17 @@ public static SharedTokenCacheCredentialOptions SharedTokenOptions(string? subsc TenantId = GetTenantId(subscriptionid), }; + /// + /// Returns an VisualStudioCodeCredentialOptions, populated with the TenantId for the given subscription. + /// + /// An subscription Id. + /// A new instance of InteractiveBrowserCredentialOptions with the TenantId populated + public static DeviceCodeCredentialOptions DeviceCodeOptions(string? subscriptionid) => + new DeviceCodeCredentialOptions + { + TenantId = GetTenantId(subscriptionid), + }; + /// /// This gnarly piece of code is how we get the guest tenant /// authority associated with the subscription.