diff --git a/.gitignore b/.gitignore index 1d4d1e43..c1b514c5 100644 --- a/.gitignore +++ b/.gitignore @@ -453,4 +453,6 @@ FodyWeavers.xsd *.sln.iml ### VisualStudio Patch ### -# Additional files built by Visual Studio \ No newline at end of file +# Additional files built by Visual Studio + +.env.local diff --git a/.vscode/launch.json b/.vscode/launch.json index a07f03d1..69664299 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,7 @@ "request": "launch", "preLaunchTask": "build sample", // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/src/sample/bin/Debug/net7.0/sample.dll", + "program": "${workspaceFolder}/src/sample/bin/Debug/net8.0/sample.dll", "args": [ "login", "--strategy", @@ -30,7 +30,7 @@ "request": "launch", "preLaunchTask": "build sample", // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/src/sample/bin/Debug/net7.0/sample.dll", + "program": "${workspaceFolder}/src/sample/bin/Debug/net8.0/sample.dll", "args": [ "login", "--strategy", @@ -46,7 +46,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": "build sample", - "program": "${workspaceFolder}/src/sample/bin/Debug/net7.0/sample.dll", + "program": "${workspaceFolder}/src/sample/bin/Debug/net8.0/sample.dll", "args": [ "login", "--strategy", @@ -67,7 +67,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": "build sample", - "program": "${workspaceFolder}/src/sample/bin/Debug/net7.0/sample.dll", + "program": "${workspaceFolder}/src/sample/bin/Debug/net8.0/sample.dll", "args": [ "login", "--strategy", @@ -77,6 +77,23 @@ "--client-id", "e49807f2-94cc-4f59-9e14-be2a37eab7c2" ], + "envFile": "${workspaceFolder}/.env.local", + "cwd": "${workspaceFolder}/src/sample", + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": "login national cloud (sample)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build sample", + "program": "${workspaceFolder}/src/sample/bin/Debug/net8.0/sample.dll", + "args": [ + "login", + "--environment", + "US_GOV" + ], + "envFile": "${workspaceFolder}/.env.local", "cwd": "${workspaceFolder}/src/sample", "console": "internalConsole", "stopAtEntry": false @@ -86,13 +103,32 @@ "type": "coreclr", "request": "launch", "preLaunchTask": "build sample", - "program": "${workspaceFolder}/src/sample/bin/Debug/net7.0/sample.dll", + "program": "${workspaceFolder}/src/sample/bin/Debug/net8.0/sample.dll", "args": [ "users", "list", "--debug", "--top", - "2" + "2", + "--headers", + "sample=header" + ], + "envFile": "${workspaceFolder}/.env.local", + "cwd": "${workspaceFolder}/src/sample", + "console": "integratedTerminal", + "stopAtEntry": false, + "justMyCode": false + }, + { + "name": "me get (sample)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build sample", + "program": "${workspaceFolder}/src/sample/bin/Debug/net8.0/sample.dll", + "args": [ + "me", + "get", + "--debug" ], "cwd": "${workspaceFolder}/src/sample", "console": "integratedTerminal", @@ -103,7 +139,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": "build sample", - "program": "${workspaceFolder}/src/sample/bin/Debug/net7.0/sample.dll", + "program": "${workspaceFolder}/src/sample/bin/Debug/net8.0/sample.dll", "args": [ "logout", "--debug" @@ -117,9 +153,8 @@ "type": "coreclr", "request": "launch", "preLaunchTask": "build sample", - "program": "${workspaceFolder}/src/sample/bin/Debug/net7.0/sample.dll", + "program": "${workspaceFolder}/src/sample/bin/Debug/net8.0/sample.dll", "args": [ - "login", "--help" ], "cwd": "${workspaceFolder}/src/sample", @@ -132,4 +167,4 @@ "request": "attach" } ] -} \ No newline at end of file +} diff --git a/src/Microsoft.Graph.Cli.Core/Authentication/AuthenticationServiceFactory.cs b/src/Microsoft.Graph.Cli.Core/Authentication/AuthenticationServiceFactory.cs index e6f14091..ac38353c 100644 --- a/src/Microsoft.Graph.Cli.Core/Authentication/AuthenticationServiceFactory.cs +++ b/src/Microsoft.Graph.Cli.Core/Authentication/AuthenticationServiceFactory.cs @@ -41,12 +41,13 @@ public AuthenticationServiceFactory(IPathUtility pathUtility, IAuthenticationCac /// Client Id /// Certificate name /// Certificate thumb-print + /// The national cloud environment. Either 'Global', 'US_GOV', 'US_GOV_DOD' or 'China' /// Cancellation token /// Returns a login service instance. /// When an unsupported authentication strategy is provided. - public virtual async Task GetAuthenticationServiceAsync(AuthenticationStrategy strategy, string? tenantId, string? clientId, string? certificateName, string? certificateThumbPrint, CancellationToken cancellationToken = default) + public virtual async Task GetAuthenticationServiceAsync(AuthenticationStrategy strategy, string? tenantId, string? clientId, string? certificateName, string? certificateThumbPrint, CloudEnvironment environment, CancellationToken cancellationToken = default) { - var credential = await GetTokenCredentialAsync(strategy, tenantId, clientId, certificateName, certificateThumbPrint, cancellationToken); + var credential = await GetTokenCredentialAsync(strategy, tenantId, clientId, certificateName, certificateThumbPrint, environment, cancellationToken); if (strategy == AuthenticationStrategy.DeviceCode && credential is DeviceCodeCredential deviceCred) { return new InteractiveLoginService(deviceCred, pathUtility); @@ -81,35 +82,33 @@ public virtual async Task GetAuthenticationServiceAsync(Authen /// Client Id /// Certificate name /// Certificate thumb-print + /// The cloud environment. /// Cancellation token. /// A token credential instance. /// When an unsupported authentication strategy is provided. - public virtual async Task GetTokenCredentialAsync(AuthenticationStrategy strategy, string? tenantId, string? clientId, string? certificateName, string? certificateThumbPrint, CancellationToken cancellationToken = default) + /// When a null url is provided for the authority host. + public virtual async Task GetTokenCredentialAsync(AuthenticationStrategy strategy, string? tenantId, string? clientId, string? certificateName, string? certificateThumbPrint, CloudEnvironment environment, CancellationToken cancellationToken = default) { - switch (strategy) + var authorityHost = environment.Authority(); + return strategy switch { - case AuthenticationStrategy.DeviceCode: - return await GetDeviceCodeCredentialAsync(tenantId, clientId, cancellationToken); - case AuthenticationStrategy.InteractiveBrowser: - return await GetInteractiveBrowserCredentialAsync(tenantId, clientId, cancellationToken); - case AuthenticationStrategy.ClientCertificate: - return GetClientCertificateCredential(tenantId, clientId, certificateName, certificateThumbPrint); - case AuthenticationStrategy.Environment: - return new EnvironmentCredential(tenantId, clientId); - case AuthenticationStrategy.ManagedIdentity: - return new ManagedIdentityCredential(clientId); - default: - throw new InvalidOperationException($"The authentication strategy {strategy} is not supported"); - } + AuthenticationStrategy.DeviceCode => await GetDeviceCodeCredentialAsync(tenantId, clientId, authorityHost, cancellationToken), + AuthenticationStrategy.InteractiveBrowser => await GetInteractiveBrowserCredentialAsync(tenantId, clientId, authorityHost, cancellationToken), + AuthenticationStrategy.ClientCertificate => GetClientCertificateCredential(tenantId, clientId, certificateName, certificateThumbPrint, authorityHost), + AuthenticationStrategy.Environment => new EnvironmentCredential(tenantId, clientId, new TokenCredentialOptions { AuthorityHost = authorityHost }), + AuthenticationStrategy.ManagedIdentity => new ManagedIdentityCredential(clientId, new TokenCredentialOptions { AuthorityHost = authorityHost }), + _ => throw new InvalidOperationException($"The authentication strategy {strategy} is not supported"), + }; } - private async Task GetDeviceCodeCredentialAsync(string? tenantId, string? clientId, CancellationToken cancellationToken = default) + private async Task GetDeviceCodeCredentialAsync(string? tenantId, string? clientId, Uri authorityHost, CancellationToken cancellationToken = default) { DeviceCodeCredentialOptions credOptions = new() { ClientId = clientId ?? Constants.DefaultAppId, TenantId = tenantId ?? Constants.DefaultTenant, DisableAutomaticAuthentication = true, + AuthorityHost = authorityHost }; TokenCachePersistenceOptions tokenCacheOptions = new() { Name = Constants.TokenCacheName }; @@ -119,13 +118,14 @@ private async Task GetDeviceCodeCredentialAsync(string? te return new DeviceCodeCredential(credOptions); } - private async Task GetInteractiveBrowserCredentialAsync(string? tenantId, string? clientId, CancellationToken cancellationToken = default) + private async Task GetInteractiveBrowserCredentialAsync(string? tenantId, string? clientId, Uri authorityHost, CancellationToken cancellationToken = default) { InteractiveBrowserCredentialOptions credOptions = new() { ClientId = clientId ?? Constants.DefaultAppId, TenantId = tenantId ?? Constants.DefaultTenant, DisableAutomaticAuthentication = true, + AuthorityHost = authorityHost }; TokenCachePersistenceOptions tokenCacheOptions = new() { Name = Constants.TokenCacheName }; @@ -135,8 +135,8 @@ private async Task GetInteractiveBrowserCredential return new InteractiveBrowserCredential(credOptions); } - private ClientCertificateCredential GetClientCertificateCredential(string? tenantId, string? clientId, string? certificateName, string? certificateThumbPrint) + private ClientCertificateCredential GetClientCertificateCredential(string? tenantId, string? clientId, string? certificateName, string? certificateThumbPrint, Uri authorityHost) { - return ClientCertificateCredentialFactory.GetClientCertificateCredential(tenantId ?? Constants.DefaultTenant, clientId ?? Constants.DefaultAppId, certificateName, certificateThumbPrint); + return ClientCertificateCredentialFactory.GetClientCertificateCredential(tenantId ?? Constants.DefaultTenant, clientId ?? Constants.DefaultAppId, certificateName, certificateThumbPrint, authorityHost); } } diff --git a/src/Microsoft.Graph.Cli.Core/Authentication/ClientCertificateCredentialFactory.cs b/src/Microsoft.Graph.Cli.Core/Authentication/ClientCertificateCredentialFactory.cs index 590ce27f..c2741904 100644 --- a/src/Microsoft.Graph.Cli.Core/Authentication/ClientCertificateCredentialFactory.cs +++ b/src/Microsoft.Graph.Cli.Core/Authentication/ClientCertificateCredentialFactory.cs @@ -4,6 +4,7 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using Azure.Identity; +using Microsoft.Graph.Cli.Core.Utils; namespace Microsoft.Graph.Cli.Core.Authentication; @@ -19,15 +20,17 @@ public static class ClientCertificateCredentialFactory /// ClientId /// Subject name of the certificate. /// Thumb print of the certificate. + /// The entra authentication endpoint (to use with national clouds) /// A ClientCertificateCredential - public static ClientCertificateCredential GetClientCertificateCredential(string? tenantId, string? clientId, string? certificateName, string? certificateThumbPrint) + /// When a null url is provided for the authority host. + public static ClientCertificateCredential GetClientCertificateCredential(string? tenantId, string? clientId, string? certificateName, string? certificateThumbPrint, Uri authorityHost) { if (string.IsNullOrWhiteSpace(certificateName) && string.IsNullOrWhiteSpace(certificateThumbPrint)) { throw new ArgumentException("Either a certificate name or a certificate thumb print must be provided."); } - ClientCertificateCredentialOptions credOptions = new(); + ClientCertificateCredentialOptions credOptions = new() { AuthorityHost = authorityHost }; // // TODO: Enable token caching // // Fix error: diff --git a/src/Microsoft.Graph.Cli.Core/Authentication/CloudEnvironment.cs b/src/Microsoft.Graph.Cli.Core/Authentication/CloudEnvironment.cs new file mode 100644 index 00000000..be4eff2d --- /dev/null +++ b/src/Microsoft.Graph.Cli.Core/Authentication/CloudEnvironment.cs @@ -0,0 +1,73 @@ +using System; +using Azure.Identity; +using Microsoft.Graph; +using Microsoft.Graph.Cli.Core.Utils; + + +/// +/// The cloud environment to use +/// +public enum CloudEnvironment +{ + /// + /// Global environment. + /// + Global, + /// + /// US Government cloud environment. + /// + USGov, + /// + /// US Government Department of Defense (DoD) cloud environment. + /// + USGovDoD, + /// + /// China cloud environment. + /// + China, +} + +/// +/// Provides methods for the class. +/// +public static class CloudEnvironmentExtensions +{ + /// + /// Gets the authority URL for the specified cloud environment. + /// + /// The cloud environment. + /// The authority URL. + /// + /// If the cloud environment is not one of the members. + /// + public static Uri Authority(this CloudEnvironment environment) + { + return environment switch + { + CloudEnvironment.Global => AzureAuthorityHosts.AzurePublicCloud, + CloudEnvironment.USGov or CloudEnvironment.USGovDoD => AzureAuthorityHosts.AzureGovernment, + CloudEnvironment.China => AzureAuthorityHosts.AzureChina, + _ => throw new ArgumentException("Unknown cloud environment", nameof(environment)) + }; + } + + /// + /// Gets the GraphClient Cloud identifier. + /// + /// The cloud environment. + /// The cloud identifier to be used by the graph client. + /// + /// If the cloud environment is not one of the members. + /// + public static string GraphClientCloud(this CloudEnvironment environment) + { + return environment switch + { + CloudEnvironment.Global => GraphClientFactory.Global_Cloud, + CloudEnvironment.USGov => GraphClientFactory.USGOV_Cloud, + CloudEnvironment.USGovDoD => GraphClientFactory.USGOV_DOD_Cloud, + CloudEnvironment.China => GraphClientFactory.China_Cloud, + _ => throw new ArgumentException("Unknown cloud environment", nameof(environment)) + }; + } +} diff --git a/src/Microsoft.Graph.Cli.Core/Authentication/EnvironmentCredential.cs b/src/Microsoft.Graph.Cli.Core/Authentication/EnvironmentCredential.cs index 70db173d..e03260d0 100644 --- a/src/Microsoft.Graph.Cli.Core/Authentication/EnvironmentCredential.cs +++ b/src/Microsoft.Graph.Cli.Core/Authentication/EnvironmentCredential.cs @@ -72,11 +72,11 @@ public EnvironmentCredential(string? tenantId, string? clientId, TokenCredential bool sendCertificateChain = !string.IsNullOrEmpty(clientSendCertificateChain) && (clientSendCertificateChain == "1" || clientSendCertificateChain == "true"); - ClientCertificateCredentialOptions clientCertificateCredentialOptions = new ClientCertificateCredentialOptions + ClientCertificateCredentialOptions clientCertificateCredentialOptions = new() { AuthorityHost = _options.AuthorityHost, Transport = _options.Transport, - SendCertificateChain = sendCertificateChain + SendCertificateChain = sendCertificateChain, }; // Use reflection to set internal properties. X509Certificate2? cert; diff --git a/src/Microsoft.Graph.Cli.Core/Commands/Authentication/LoginCommand.cs b/src/Microsoft.Graph.Cli.Core/Commands/Authentication/LoginCommand.cs index b6045e66..65f542d0 100644 --- a/src/Microsoft.Graph.Cli.Core/Commands/Authentication/LoginCommand.cs +++ b/src/Microsoft.Graph.Cli.Core/Commands/Authentication/LoginCommand.cs @@ -27,6 +27,8 @@ public sealed class LoginCommand : Command private Option certificateThumbPrintOption = new("--certificate-thumb-print", "The thumbprint of your certificate. The certificate will be retrieved from the current user's certificate store."); + private Option environmentOption = new("--environment", () => CloudEnvironment.Global, "Select the cloud environment to log in to. If login is run without providing an environment, Global is used."); + private Option strategyOption = new("--strategy", () => Constants.defaultAuthStrategy); internal LoginCommand() : base("login", "Login and store the session for use in subsequent commands") @@ -36,6 +38,7 @@ public sealed class LoginCommand : Command AddOption(tenantIdOption); AddOption(certificateNameOption); AddOption(certificateThumbPrintOption); + AddOption(environmentOption); AddOption(strategyOption); this.SetHandler(async (context) => { @@ -44,15 +47,16 @@ public sealed class LoginCommand : Command var tenantId = context.ParseResult.GetValueForOption(tenantIdOption); var certificateName = context.ParseResult.GetValueForOption(certificateNameOption); var certificateThumbPrint = context.ParseResult.GetValueForOption(certificateThumbPrintOption); + var environment = context.ParseResult.GetValueForOption(environmentOption); var strategy = context.ParseResult.GetValueForOption(strategyOption); var cancellationToken = context.GetCancellationToken(); var authUtil = context.BindingContext.GetRequiredService(); var authSvcFactory = context.BindingContext.GetRequiredService(); - var authService = await authSvcFactory.GetAuthenticationServiceAsync(strategy, tenantId, clientId, certificateName, certificateThumbPrint, cancellationToken); + var authService = await authSvcFactory.GetAuthenticationServiceAsync(strategy, tenantId, clientId, certificateName, certificateThumbPrint, environment, cancellationToken); await authService.LoginAsync(scopes, cancellationToken); - await authUtil.SaveAuthenticationIdentifiersAsync(clientId, tenantId, certificateName, certificateThumbPrint, strategy, cancellationToken); + await authUtil.SaveAuthenticationIdentifiersAsync(clientId, tenantId, certificateName, certificateThumbPrint, strategy, environment, cancellationToken); }); } diff --git a/src/Microsoft.Graph.Cli.Core/Configuration/AuthenticationOptions.cs b/src/Microsoft.Graph.Cli.Core/Configuration/AuthenticationOptions.cs index ad3cabac..d02868c2 100644 --- a/src/Microsoft.Graph.Cli.Core/Configuration/AuthenticationOptions.cs +++ b/src/Microsoft.Graph.Cli.Core/Configuration/AuthenticationOptions.cs @@ -8,7 +8,8 @@ namespace Microsoft.Graph.Cli.Core.Configuration; public class AuthenticationOptions { /// - /// Authority + /// Entra authority. Corresponds to the via the + /// convenience extension. /// public string? Authority { get; set; } @@ -36,4 +37,10 @@ public class AuthenticationOptions /// Authentication strategy /// public AuthenticationStrategy Strategy { get; set; } = AuthenticationStrategy.DeviceCode; + + /// + /// Cloud environment for authentication. This is stored separately from + /// Authority in case some environments use the same authority. + /// + public CloudEnvironment Environment { get; set; } = CloudEnvironment.Global; } diff --git a/src/Microsoft.Graph.Cli.Core/IO/AuthenticationCacheManager.cs b/src/Microsoft.Graph.Cli.Core/IO/AuthenticationCacheManager.cs index 1c24eefd..62ce7977 100644 --- a/src/Microsoft.Graph.Cli.Core/IO/AuthenticationCacheManager.cs +++ b/src/Microsoft.Graph.Cli.Core/IO/AuthenticationCacheManager.cs @@ -54,13 +54,13 @@ public async Task ReadAuthenticationIdentifiersAsync(Canc } /// - public async Task SaveAuthenticationIdentifiersAsync(string? clientId, string? tenantId, string? certificateName, string? certificateThumbPrint, AuthenticationStrategy strategy, CancellationToken cancellationToken = default) + public async Task SaveAuthenticationIdentifiersAsync(string? clientId, string? tenantId, string? certificateName, string? certificateThumbPrint, AuthenticationStrategy strategy, CloudEnvironment environment = CloudEnvironment.Global, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); var path = this.GetAuthenticationCacheFilePath(); ConfigurationRoot configuration = await ReadConfigurationAsync(cancellationToken) ?? new Configuration.ConfigurationRoot(); var authOptions = configuration.AuthenticationOptions; - var adAuthority = Constants.DefaultAuthority; + var adAuthority = environment.Authority(); clientId = clientId ?? Constants.DefaultAppId; tenantId = tenantId ?? Constants.DefaultTenant; @@ -68,17 +68,18 @@ public async Task SaveAuthenticationIdentifiersAsync(string? clientId, string? t if ( clientId != authOptions.ClientId || tenantId != authOptions.TenantId || certificateName != authOptions.ClientCertificateName || certificateThumbPrint != authOptions.ClientCertificateThumbPrint || strategy != authOptions.Strategy || - adAuthority != authOptions.Authority + (authOptions.Authority is not null && adAuthority != new Uri(authOptions.Authority)) ) { configuration.AuthenticationOptions = new AuthenticationOptions { - Authority = adAuthority, + Authority = adAuthority.ToString(), ClientId = clientId, TenantId = tenantId, ClientCertificateName = certificateName, ClientCertificateThumbPrint = certificateThumbPrint, Strategy = strategy, + Environment = environment, }; await WriteConfigurationAsync(path, configuration, cancellationToken); @@ -109,8 +110,16 @@ public async Task ClearTokenCache(CancellationToken cancellationToken = default) { // https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-net-clear-token-cache // Work-around until https://github.com/Azure/azure-sdk-for-net/issues/32048 is resolved - var options = await ReadAuthenticationIdentifiersAsync(cancellationToken); - var record = await ReadAuthenticationRecordAsync(cancellationToken); + var optionsTask = ReadAuthenticationIdentifiersAsync(cancellationToken); + var recordTask = ReadAuthenticationRecordAsync(cancellationToken); + + // Run both tasks concurrently + await Task.WhenAll(optionsTask, recordTask); + + // The tasks should be complete at this point. Result won't block. + var options = optionsTask.Result; + var record = recordTask.Result; + var clientId = record?.ClientId ?? options?.ClientId; var tenantId = record?.TenantId ?? options?.TenantId; if (options == null || string.IsNullOrWhiteSpace(clientId)) @@ -183,18 +192,18 @@ private void DeleteAuthenticationRecord() } } - private async Task WriteConfigurationAsync(string path, ConfigurationRoot configuration, CancellationToken cancellationToken = default, int retryCount = 0) + private async Task WriteConfigurationAsync(string path, ConfigurationRoot configuration, CancellationToken cancellationToken = default, int retryCount = 1) { try { - using FileStream fileStream = File.Open(path, FileMode.Create, FileAccess.Write); + using FileStream fileStream = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.Read); await JsonSerializer.SerializeAsync(fileStream, configuration, SourceGenerationContext.Default.ConfigurationRoot, cancellationToken: cancellationToken).ConfigureAwait(false); } catch (DirectoryNotFoundException) { Directory.CreateDirectory(path); - if (retryCount < 1) - await WriteConfigurationAsync(path, configuration, cancellationToken, retryCount + 1); + if (retryCount > 0) + await WriteConfigurationAsync(path, configuration, cancellationToken, retryCount - 1); } } diff --git a/src/Microsoft.Graph.Cli.Core/IO/GraphCliClientFactory.cs b/src/Microsoft.Graph.Cli.Core/IO/GraphCliClientFactory.cs index 2a1d3cff..f080f3ae 100644 --- a/src/Microsoft.Graph.Cli.Core/IO/GraphCliClientFactory.cs +++ b/src/Microsoft.Graph.Cli.Core/IO/GraphCliClientFactory.cs @@ -23,12 +23,12 @@ public class GraphCliClientFactory /// /// Graph client options /// API version - /// National cloud in use. + /// The cloud environment in use. /// Final HTTP handler /// Logging handler /// Other middlewares. /// Returns a new HTTP client. - public static HttpClient GetDefaultClient(GraphClientOptions? options = null, string version = "v1.0", string nationalCloud = GraphClientFactory.Global_Cloud, HttpMessageHandler? finalHandler = null, LoggingHandler? loggingHandler = null, params DelegatingHandler[] middlewares) + public static HttpClient GetDefaultClient(GraphClientOptions? options = null, string version = "v1.0", CloudEnvironment environment = CloudEnvironment.Global, HttpMessageHandler? finalHandler = null, LoggingHandler? loggingHandler = null, params DelegatingHandler[] middlewares) { var m = new List(); @@ -67,6 +67,6 @@ public static HttpClient GetDefaultClient(GraphClientOptions? options = null, st return 0; }); - return GraphClientFactory.Create(version: version, nationalCloud: nationalCloud, finalHandler: finalHandler, handlers: m); + return GraphClientFactory.Create(version: version, nationalCloud: environment.GraphClientCloud(), finalHandler: finalHandler, handlers: m); } } diff --git a/src/Microsoft.Graph.Cli.Core/IO/IAuthenticationCacheManager.cs b/src/Microsoft.Graph.Cli.Core/IO/IAuthenticationCacheManager.cs index 71eacd3e..95a5dce0 100644 --- a/src/Microsoft.Graph.Cli.Core/IO/IAuthenticationCacheManager.cs +++ b/src/Microsoft.Graph.Cli.Core/IO/IAuthenticationCacheManager.cs @@ -25,9 +25,10 @@ public interface IAuthenticationCacheManager /// Certificate name /// Certificate thumb-print /// Authentication strategy. + /// The cloud environment. /// Cancellation token. /// A void awaitable task. - Task SaveAuthenticationIdentifiersAsync(string? clientId, string? tenantId, string? certificateName, string? certificateThumbPrint, AuthenticationStrategy strategy, CancellationToken cancellationToken = default); + Task SaveAuthenticationIdentifiersAsync(string? clientId, string? tenantId, string? certificateName, string? certificateThumbPrint, AuthenticationStrategy strategy, CloudEnvironment environment = CloudEnvironment.Global, CancellationToken cancellationToken = default); /// /// Reads authentication options from the authentication cache path. diff --git a/src/Microsoft.Graph.Cli.Core/utils/Constants.cs b/src/Microsoft.Graph.Cli.Core/utils/Constants.cs index 7fda69d5..c38fbc64 100644 --- a/src/Microsoft.Graph.Cli.Core/utils/Constants.cs +++ b/src/Microsoft.Graph.Cli.Core/utils/Constants.cs @@ -5,7 +5,7 @@ namespace Microsoft.Graph.Cli.Core.Utils /// /// Graph CLI Core Constants /// - public class Constants + internal class Constants { /// /// Name of the CLI core directory in the APPDATA root. @@ -37,15 +37,10 @@ public class Constants /// public const string DefaultTenant = "common"; - /// - /// Default authority - /// - public const string DefaultAuthority = "https://login.microsoftonline.com"; - /// /// Environmrnt constants. /// - public static class Environment + internal static class Environment { /// /// Tenant Id. diff --git a/src/sample/Program.cs b/src/sample/Program.cs index 412b4f56..a6373991 100644 --- a/src/sample/Program.cs +++ b/src/sample/Program.cs @@ -176,16 +176,17 @@ static IHostBuilder CreateHostBuilder(string[] args) => GraphServiceTargetVersion = "1.0" }; + var authSettings = p.GetRequiredService>().Value; var headersHandler = new NativeHttpHeadersHandler(() => InMemoryHeadersStore.Instance, p.GetService>()); - return GraphCliClientFactory.GetDefaultClient(options, loggingHandler: p.GetRequiredService(), middlewares: new[] { headersHandler }); + return GraphCliClientFactory.GetDefaultClient(options, environment: authSettings.Environment, loggingHandler: p.GetRequiredService(), middlewares: new[] { headersHandler }); }); services.AddSingleton(p => { var authSettings = p.GetRequiredService>()?.Value; var serviceFactory = p.GetRequiredService(); AuthenticationStrategy authStrategy = authSettings?.Strategy ?? AuthenticationStrategy.DeviceCode; - var credential = serviceFactory.GetTokenCredentialAsync(authStrategy, authSettings?.TenantId, authSettings?.ClientId, authSettings?.ClientCertificateName, authSettings?.ClientCertificateThumbPrint); + var credential = serviceFactory.GetTokenCredentialAsync(authStrategy, authSettings?.TenantId, authSettings?.ClientId, authSettings?.ClientCertificateName, authSettings?.ClientCertificateThumbPrint, authSettings?.Environment ?? CloudEnvironment.Global); credential.Wait(); return new AzureIdentityAuthenticationProvider(credential.Result); });