From ae869ae28956a17ac8f7df2ccd0c9c6ad91e7668 Mon Sep 17 00:00:00 2001 From: George Date: Tue, 13 Apr 2021 13:57:54 +0300 Subject: [PATCH 01/16] Make Interactive Auth the default auth provider. Inform the user of fallback to device code when Interactive Auth is unavailable due to browser not being available such as CloudShell and WSL. Minor cleanup, remove unused methods. --- .../Authentication.Core/Authenticator.cs | 46 +++++++--- .../Interfaces/IAuthContext.cs | 21 +++++ ...Microsoft.Graph.Authentication.Core.csproj | 2 +- .../Utilities/AuthenticationHelpers.cs | 49 ++++++----- .../Authentication/Cmdlets/ConnectMgGraph.cs | 88 +++++++++++++------ .../Microsoft.Graph.Authentication.csproj | 2 +- .../Microsoft.Graph.Authentication.nuspec | 2 +- .../Microsoft.Graph.Authentication.psd1 | 2 +- .../Authentication/Models/AuthContext.cs | 3 +- .../Properties/Resources.Designer.cs | 18 ++++ .../Authentication/Properties/Resources.resx | 6 ++ .../test/Connect-MgGraph.Tests.ps1 | 2 +- 12 files changed, 173 insertions(+), 68 deletions(-) diff --git a/src/Authentication/Authentication.Core/Authenticator.cs b/src/Authentication/Authentication.Core/Authenticator.cs index fe23f3a1a1c..343ae9ffae5 100644 --- a/src/Authentication/Authentication.Core/Authenticator.cs +++ b/src/Authentication/Authentication.Core/Authenticator.cs @@ -9,6 +9,7 @@ namespace Microsoft.Graph.Authentication.Core using Microsoft.Graph.PowerShell.Authentication.Core; using Microsoft.Graph.PowerShell.Authentication.Helpers; using Microsoft.Identity.Client; + using System; using System.Collections.Generic; using System.Globalization; @@ -29,17 +30,22 @@ public static class Authenticator /// Whether or not to force refresh a token if one exists. /// The cancellation token. /// - public static async Task AuthenticateAsync(IAuthContext authContext, bool forceRefresh, CancellationToken cancellationToken) + public static async Task<(IAuthContext context, AuthError authError)> AuthenticateAsync(IAuthContext authContext, bool forceRefresh, CancellationToken cancellationToken) { try { // Gets a static instance of IAuthenticationProvider when the client app hasn't changed. IAuthenticationProvider authProvider = AuthenticationHelpers.GetAuthProvider(authContext); IClientApplicationBase clientApplication = null; - if (authContext.AuthType == AuthenticationType.Delegated) + + if (authContext.AuthType == AuthenticationType.Delegated && authContext.UseDeviceAuth) { clientApplication = (authProvider as DeviceCodeProvider).ClientApplication; } + if (authContext.AuthType == AuthenticationType.Delegated && !authContext.UseDeviceAuth) + { + clientApplication = (authProvider as InteractiveAuthenticationProvider).ClientApplication; + } if (authContext.AuthType == AuthenticationType.AppOnly) { clientApplication = (authProvider as ClientCredentialProvider).ClientApplication; @@ -77,29 +83,38 @@ public static async Task AuthenticateAsync(IAuthContext authContex } JwtHelpers.DecodeJWT(httpRequestMessage.Headers.Authorization?.Parameter, account, ref authContext); - return authContext; + return (authContext, new AuthError(AuthErrorType.None, null)); } catch (AuthenticationException authEx) { - if ((authEx.InnerException is TaskCanceledException) && cancellationToken.IsCancellationRequested) + //Interactive Authentication Failure: Could Not Open Browser, fallback to DeviceAuth + if (IsUnableToOpenWebPageError(authEx)) + { + authContext.UseDeviceAuth = true; + //ReAuthenticate using DeviceCode as fallback. + var (retryAuthContext, retryAuthError) = await AuthenticateAsync(authContext, forceRefresh, cancellationToken); + //Indicate that this was a Fallback + retryAuthError = new AuthError(AuthErrorType.FallBack, retryAuthError.Exception); + return (retryAuthContext, retryAuthError); + } + // DeviceCode Authentication Failure: Timeout + if (authEx.InnerException is TaskCanceledException && cancellationToken.IsCancellationRequested) { // DeviceCodeTimeout - throw new Exception(string.Format( + var deviceCode = new Exception(string.Format( CultureInfo.CurrentCulture, ErrorConstants.Message.DeviceCodeTimeout, Constants.MaxDeviceCodeTimeOut)); + return (authContext, new AuthError(AuthErrorType.DeviceCodeFailure, deviceCode.InnerException ?? deviceCode)); } - else - { - throw authEx.InnerException ?? authEx; - } + //Something Unknown Went Wrong + return (authContext, new AuthError(AuthErrorType.Unknown, authEx.InnerException ?? authEx)); } catch (Exception ex) { - throw ex.InnerException ?? ex; + return (authContext, new AuthError(AuthErrorType.Unknown, ex.InnerException ?? ex)); } } - /// /// Signs out of the provided . /// @@ -108,5 +123,12 @@ public static void LogOut(IAuthContext authContext) { AuthenticationHelpers.Logout(authContext); } + + private static bool IsUnableToOpenWebPageError(Exception exception) + { + return exception.InnerException is MsalClientException clientException && + clientException?.ErrorCode == MsalError.LinuxXdgOpen || + (exception.Message?.ToLower()?.Contains("unable to open a web page") ?? false); + } } -} +} \ No newline at end of file diff --git a/src/Authentication/Authentication.Core/Interfaces/IAuthContext.cs b/src/Authentication/Authentication.Core/Interfaces/IAuthContext.cs index 6e52994179d..37122bdfdff 100644 --- a/src/Authentication/Authentication.Core/Interfaces/IAuthContext.cs +++ b/src/Authentication/Authentication.Core/Interfaces/IAuthContext.cs @@ -4,6 +4,7 @@ namespace Microsoft.Graph.PowerShell.Authentication { + using System; using System.Security.Cryptography.X509Certificates; public enum AuthenticationType @@ -18,7 +19,26 @@ public enum ContextScope Process, CurrentUser } + public enum AuthErrorType + { + None, + FallBack, + DeviceCodeFailure, + InteractiveAuthenticationFailure, + ClientCredentialsFailure, + Unknown + } + public readonly struct AuthError + { + public AuthError(AuthErrorType errorType, Exception ex) + { + AuthErrorType = errorType; + Exception = ex; + } + public AuthErrorType AuthErrorType { get; } + public Exception Exception { get; } + } public interface IAuthContext { string ClientId { get; set; } @@ -31,5 +51,6 @@ public interface IAuthContext string AppName { get; set; } ContextScope ContextScope { get; set; } X509Certificate2 Certificate { get; set; } + bool UseDeviceAuth { get; set; } } } diff --git a/src/Authentication/Authentication.Core/Microsoft.Graph.Authentication.Core.csproj b/src/Authentication/Authentication.Core/Microsoft.Graph.Authentication.Core.csproj index 9832da8843a..63185b31d1c 100644 --- a/src/Authentication/Authentication.Core/Microsoft.Graph.Authentication.Core.csproj +++ b/src/Authentication/Authentication.Core/Microsoft.Graph.Authentication.Core.csproj @@ -3,7 +3,7 @@ netstandard2.0;netcoreapp2.1;net461 Microsoft.Graph.PowerShell.Authentication.Core - 1.4.2 + 1.4.3 diff --git a/src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs b/src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs index 1a5d80ca604..78bf3f45478 100644 --- a/src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs +++ b/src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs @@ -66,45 +66,52 @@ public static IAuthenticationProvider GetAuthProvider(IAuthContext authContext) switch (authContext.AuthType) { case AuthenticationType.Delegated: - { - IPublicClientApplication publicClientApp = PublicClientApplicationBuilder + { + //Specify Default RedirectUri + //https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/MSAL.NET-uses-web-browser + IPublicClientApplication publicClientApp = PublicClientApplicationBuilder .Create(authContext.ClientId) .WithTenantId(authContext.TenantId) .WithAuthority(authorityUrl) .WithClientCapabilities(new[] { "cp1" }) + .WithDefaultRedirectUri() .Build(); - - ConfigureTokenCache(publicClientApp.UserTokenCache, authContext); + ConfigureTokenCache(publicClientApp.UserTokenCache, authContext); + if (authContext.UseDeviceAuth) + { authProvider = new DeviceCodeProvider(publicClientApp, authContext.Scopes, async (result) => { await Console.Out.WriteLineAsync(result.Message); }); - break; } - case AuthenticationType.AppOnly: + else { - IConfidentialClientApplication confidentialClientApp = ConfidentialClientApplicationBuilder + authProvider = new InteractiveAuthenticationProvider(publicClientApp, authContext.Scopes); + } + break; + } + case AuthenticationType.AppOnly: + { + IConfidentialClientApplication confidentialClientApp = ConfidentialClientApplicationBuilder .Create(authContext.ClientId) .WithTenantId(authContext.TenantId) .WithAuthority(authorityUrl) .WithCertificate(GetCertificate(authContext)) .Build(); - ConfigureTokenCache(confidentialClientApp.AppTokenCache, authContext); - string graphBaseUrl = GraphSession.Instance.Environment?.GraphEndpoint ?? "https://graph.microsoft.com"; - authProvider = new ClientCredentialProvider(confidentialClientApp, $"{graphBaseUrl}/.default"); - break; - } + ConfigureTokenCache(confidentialClientApp.AppTokenCache, authContext); + string graphBaseUrl = GraphSession.Instance.Environment?.GraphEndpoint ?? "https://graph.microsoft.com"; + authProvider = new ClientCredentialProvider(confidentialClientApp, $"{graphBaseUrl}/.default"); + break; + } case AuthenticationType.UserProvidedAccessToken: + authProvider = new DelegateAuthenticationProvider((requestMessage) => { - authProvider = new DelegateAuthenticationProvider((requestMessage) => - { - requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", - new NetworkCredential(string.Empty, GraphSession.Instance.UserProvidedToken).Password); - return Task.CompletedTask; - }); - break; - } + requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", + new NetworkCredential(string.Empty, GraphSession.Instance.UserProvidedToken).Password); + return Task.CompletedTask; + }); + break; } return authProvider; } @@ -255,4 +262,4 @@ private static X509Certificate2 GetCertificateByName(string certificateName) return xCertificate; } } -} +} \ No newline at end of file diff --git a/src/Authentication/Authentication/Cmdlets/ConnectMgGraph.cs b/src/Authentication/Authentication/Cmdlets/ConnectMgGraph.cs index 4de46081237..c274afa5e04 100644 --- a/src/Authentication/Authentication/Cmdlets/ConnectMgGraph.cs +++ b/src/Authentication/Authentication/Cmdlets/ConnectMgGraph.cs @@ -1,28 +1,31 @@ // ------------------------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. // ------------------------------------------------------------------------------ + +using AsyncHelpers = Microsoft.Graph.PowerShell.Authentication.Helpers.AsyncHelpers; + namespace Microsoft.Graph.PowerShell.Authentication.Cmdlets { using System; + using System.Collections; using System.Collections.Generic; using System.Linq; using System.Management.Automation; - using System.Threading; - using System.Threading.Tasks; using System.Net; - using System.Collections; using System.Security.Cryptography.X509Certificates; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Graph.Authentication.Core; + using Microsoft.Graph.PowerShell.Authentication.Common; using Microsoft.Graph.PowerShell.Authentication.Helpers; + using Microsoft.Graph.PowerShell.Authentication.Interfaces; using Microsoft.Graph.PowerShell.Authentication.Models; - - using Interfaces; - using Common; - - using static Helpers.AsyncHelpers; - using Microsoft.Graph.Authentication.Core; + using Microsoft.Graph.PowerShell.Authentication.Properties; using Microsoft.Graph.PowerShell.Authentication.Utilities; + using static AsyncHelpers; + [Cmdlet(VerbsCommunications.Connect, "MgGraph", DefaultParameterSetName = Constants.UserParameterSet)] [Alias("Connect-Graph")] public class ConnectMgGraph : PSCmdlet, IModuleAssemblyInitializer, IModuleAssemblyCleanup @@ -87,12 +90,27 @@ public class ConnectMgGraph : PSCmdlet, IModuleAssemblyInitializer, IModuleAssem [Alias("EnvironmentName", "NationalCloud")] public string Environment { get; set; } + [Parameter(ParameterSetName = Constants.UserParameterSet, + Mandatory = false, HelpMessage = "Use device code authentication instead of a browser control")] + [Alias("DeviceCode", "DeviceAuth", "Device")] + public SwitchParameter UseDeviceAuthentication { get; set; } + /// + /// Wait for .NET debugger to attach + /// + [Parameter(Mandatory = false, + DontShow = true, + HelpMessage = "Wait for .NET debugger to attach")] + public SwitchParameter Break { get; set; } + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private IGraphEnvironment environment; - protected override void BeginProcessing() { + if (Break) + { + this.Break(); + } base.BeginProcessing(); ValidateParameters(); @@ -109,12 +127,6 @@ protected override void BeginProcessing() environment = GraphEnvironment.BuiltInEnvironments[GraphEnvironmentConstants.EnvironmentName.Global]; } } - - protected override void EndProcessing() - { - base.EndProcessing(); - } - protected override void ProcessRecord() { base.ProcessRecord(); @@ -170,9 +182,10 @@ private async Task ProcessRecordAsync() _cancellationTokenSource.CancelAfter(authTimeout); authContext.AuthType = AuthenticationType.Delegated; string[] processedScopes = ProcessScopes(Scopes); - authContext.Scopes = processedScopes.Length == 0 ? new string[] { "User.Read" } : processedScopes; + authContext.Scopes = processedScopes.Length == 0 ? new[] { "User.Read" } : processedScopes; // Default to CurrentUser but allow the customer to change this via `ContextScope` param. authContext.ContextScope = this.IsParameterBound(nameof(ContextScope)) ? ContextScope : ContextScope.CurrentUser; + authContext.UseDeviceAuth = UseDeviceAuthentication; } break; case Constants.AppParameterSet: @@ -196,20 +209,37 @@ private async Task ProcessRecordAsync() break; } - try - { - // Save auth context to session state. - GraphSession.Instance.AuthContext = await Authenticator.AuthenticateAsync(authContext, ForceRefresh, _cancellationTokenSource.Token); - } - catch(Exception ex) + // Save auth context to session state. + var (context, authError) = await Authenticator.AuthenticateAsync(authContext, ForceRefresh, _cancellationTokenSource.Token); + + switch (authError.AuthErrorType) { - throw ex; + case AuthErrorType.None: + case AuthErrorType.FallBack: + { + GraphSession.Instance.AuthContext = context; + if (authError.AuthErrorType == AuthErrorType.FallBack) + { + WriteWarning(Resources.DeviceCodeFallback); + } + WriteObject("Welcome To Microsoft Graph!"); + break; + } + case AuthErrorType.InteractiveAuthenticationFailure: + { + WriteWarning(Resources.InteractiveAuthNotSupported); + WriteDebug(authError.Exception.ToString()); + throw authError.Exception; + } + case AuthErrorType.DeviceCodeFailure: + case AuthErrorType.ClientCredentialsFailure: + case AuthErrorType.Unknown: + throw authError.Exception; + default: + throw new ArgumentOutOfRangeException(); } - - WriteObject("Welcome To Microsoft Graph!"); } } - protected override void StopProcessing() { _cancellationTokenSource.Cancel(); @@ -254,7 +284,7 @@ private void ValidateParameters() } // Certificate Thumbprint, Name or Actual Certificate - if (string.IsNullOrEmpty(CertificateThumbprint) && string.IsNullOrEmpty(CertificateName) && this.Certificate == null) + if (string.IsNullOrEmpty(CertificateThumbprint) && string.IsNullOrEmpty(CertificateName) && Certificate == null) { this.ThrowParameterError($"{nameof(CertificateThumbprint)} or {nameof(CertificateName)} or {nameof(Certificate)}"); } @@ -305,4 +335,4 @@ public void OnRemove(PSModuleInfo psModuleInfo) DependencyAssemblyResolver.Reset(); } } -} +} \ No newline at end of file diff --git a/src/Authentication/Authentication/Microsoft.Graph.Authentication.csproj b/src/Authentication/Authentication/Microsoft.Graph.Authentication.csproj index 17acca82e6a..3cdfba3a0b2 100644 --- a/src/Authentication/Authentication/Microsoft.Graph.Authentication.csproj +++ b/src/Authentication/Authentication/Microsoft.Graph.Authentication.csproj @@ -1,6 +1,6 @@ - 1.4.2 + 1.4.3 7.1 netstandard2.0 Library diff --git a/src/Authentication/Authentication/Microsoft.Graph.Authentication.nuspec b/src/Authentication/Authentication/Microsoft.Graph.Authentication.nuspec index 01fe73c6929..9cb7be61bc0 100644 --- a/src/Authentication/Authentication/Microsoft.Graph.Authentication.nuspec +++ b/src/Authentication/Authentication/Microsoft.Graph.Authentication.nuspec @@ -1,7 +1,7 @@ - 1.4.2 + 1.4.3 Microsoft.Graph.Authentication Microsoft Graph PowerShell authentication module Microsoft diff --git a/src/Authentication/Authentication/Microsoft.Graph.Authentication.psd1 b/src/Authentication/Authentication/Microsoft.Graph.Authentication.psd1 index 060868e9376..55bb75419b8 100644 --- a/src/Authentication/Authentication/Microsoft.Graph.Authentication.psd1 +++ b/src/Authentication/Authentication/Microsoft.Graph.Authentication.psd1 @@ -3,7 +3,7 @@ # # Generated by: Microsoft # -# Generated on: 3/12/2021 +# Generated on: 4/13/2021 # @{ diff --git a/src/Authentication/Authentication/Models/AuthContext.cs b/src/Authentication/Authentication/Models/AuthContext.cs index 5a7205e3a57..bc8e0838e5d 100644 --- a/src/Authentication/Authentication/Models/AuthContext.cs +++ b/src/Authentication/Authentication/Models/AuthContext.cs @@ -18,10 +18,11 @@ public class AuthContext: IAuthContext public string AppName { get; set; } public ContextScope ContextScope { get ; set ; } public X509Certificate2 Certificate { get; set; } + public bool UseDeviceAuth { get; set; } public AuthContext() { ClientId = PowerShellClientId; } } -} +} \ No newline at end of file diff --git a/src/Authentication/Authentication/Properties/Resources.Designer.cs b/src/Authentication/Authentication/Properties/Resources.Designer.cs index 6d7e3e333ae..460051f0d51 100644 --- a/src/Authentication/Authentication/Properties/Resources.Designer.cs +++ b/src/Authentication/Authentication/Properties/Resources.Designer.cs @@ -123,6 +123,15 @@ internal static string ContentTypeExceptionErrorMessage { } } + /// + /// Looks up a localized string similar to Interactive authentication is not supported in this session, Falling Back to DeviceCode. Future versions will not automatically fallback to DeviceCode.. + /// + internal static string DeviceCodeFallback { + get { + return ResourceManager.GetString("DeviceCodeFallback", resourceCulture); + } + } + /// /// Looks up a localized string similar to Path '{0}' resolves to a directory. Specify a path including a file name, and then retry the command.. /// @@ -204,6 +213,15 @@ internal static string InferredFileNameVerboseMessage { } } + /// + /// Looks up a localized string similar to Interactive authentication is not supported in this session, please run cmdlet 'Connect-MgGraph -UseDeviceAuthentication'.. + /// + internal static string InteractiveAuthNotSupported { + get { + return ResourceManager.GetString("InteractiveAuthNotSupported", resourceCulture); + } + } + /// /// Looks up a localized string similar to Invalid Host {0}. /// diff --git a/src/Authentication/Authentication/Properties/Resources.resx b/src/Authentication/Authentication/Properties/Resources.resx index 4e151fe31e8..c62945aad9a 100644 --- a/src/Authentication/Authentication/Properties/Resources.resx +++ b/src/Authentication/Authentication/Properties/Resources.resx @@ -223,4 +223,10 @@ Request returned Non-Json response of {0}, Please specify '-OutputFilePath' + + Interactive authentication is not supported in this session, please run cmdlet 'Connect-MgGraph -UseDeviceAuthentication'. + + + Interactive authentication is not supported in this session, Falling Back to DeviceCode. Future versions will not automatically fallback to DeviceCode. + \ No newline at end of file diff --git a/src/Authentication/Authentication/test/Connect-MgGraph.Tests.ps1 b/src/Authentication/Authentication/test/Connect-MgGraph.Tests.ps1 index 76845efcd07..9360d9e8d5f 100644 --- a/src/Authentication/Authentication/test/Connect-MgGraph.Tests.ps1 +++ b/src/Authentication/Authentication/test/Connect-MgGraph.Tests.ps1 @@ -24,7 +24,7 @@ Describe 'Connect-MgGraph In App Mode' { } } -Describe 'Connect-MgGraph Depencency Resolution' { +Describe 'Connect-MgGraph Dependency Resolution' { BeforeAll { Install-Module Az.Accounts -Repository PSGallery -Force } From e02c7f88b1fa6699302fb2e6f9b44fb31a8af239 Mon Sep 17 00:00:00 2001 From: George Date: Tue, 13 Apr 2021 14:09:10 +0300 Subject: [PATCH 02/16] Remove generated text --- .../Authentication/Microsoft.Graph.Authentication.csproj | 2 +- .../Authentication/Microsoft.Graph.Authentication.nuspec | 2 +- .../Authentication/Microsoft.Graph.Authentication.psd1 | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Authentication/Authentication/Microsoft.Graph.Authentication.csproj b/src/Authentication/Authentication/Microsoft.Graph.Authentication.csproj index 3cdfba3a0b2..17acca82e6a 100644 --- a/src/Authentication/Authentication/Microsoft.Graph.Authentication.csproj +++ b/src/Authentication/Authentication/Microsoft.Graph.Authentication.csproj @@ -1,6 +1,6 @@ - 1.4.3 + 1.4.2 7.1 netstandard2.0 Library diff --git a/src/Authentication/Authentication/Microsoft.Graph.Authentication.nuspec b/src/Authentication/Authentication/Microsoft.Graph.Authentication.nuspec index 9cb7be61bc0..01fe73c6929 100644 --- a/src/Authentication/Authentication/Microsoft.Graph.Authentication.nuspec +++ b/src/Authentication/Authentication/Microsoft.Graph.Authentication.nuspec @@ -1,7 +1,7 @@ - 1.4.3 + 1.4.2 Microsoft.Graph.Authentication Microsoft Graph PowerShell authentication module Microsoft diff --git a/src/Authentication/Authentication/Microsoft.Graph.Authentication.psd1 b/src/Authentication/Authentication/Microsoft.Graph.Authentication.psd1 index 55bb75419b8..060868e9376 100644 --- a/src/Authentication/Authentication/Microsoft.Graph.Authentication.psd1 +++ b/src/Authentication/Authentication/Microsoft.Graph.Authentication.psd1 @@ -3,7 +3,7 @@ # # Generated by: Microsoft # -# Generated on: 4/13/2021 +# Generated on: 3/12/2021 # @{ From e3ed0e5e8833bdbc4ace5d34e192c228d6631424 Mon Sep 17 00:00:00 2001 From: George Date: Tue, 13 Apr 2021 14:19:51 +0300 Subject: [PATCH 03/16] Add test. --- .../Helpers/AuthenticationHelpersTests.cs | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/Authentication/Authentication.Test/Helpers/AuthenticationHelpersTests.cs b/src/Authentication/Authentication.Test/Helpers/AuthenticationHelpersTests.cs index 3bf8906e43d..578da855feb 100644 --- a/src/Authentication/Authentication.Test/Helpers/AuthenticationHelpersTests.cs +++ b/src/Authentication/Authentication.Test/Helpers/AuthenticationHelpersTests.cs @@ -48,14 +48,15 @@ public async Task ShouldUseDelegateAuthProviderWhenUserAccessTokenIsProvidedAsyn } [Fact] - public void ShouldUseDeviceCodeProviderWhenDelegatedContextIsProvided() + public void ShouldUseDeviceCodeWhenSpecifiedByUser() { // Arrange AuthContext delegatedAuthContext = new AuthContext { AuthType = AuthenticationType.Delegated, - Scopes = new string[] { "User.Read" }, - ContextScope = ContextScope.Process + Scopes = new[] { "User.Read" }, + ContextScope = ContextScope.Process, + UseDeviceAuth = true }; // Act @@ -67,6 +68,26 @@ public void ShouldUseDeviceCodeProviderWhenDelegatedContextIsProvided() // reset static instance. GraphSession.Reset(); } + [Fact] + public void ShouldUseInteractiveProviderWhenDelegated() + { + // Arrange + AuthContext delegatedAuthContext = new AuthContext + { + AuthType = AuthenticationType.Delegated, + Scopes = new[] { "User.Read" }, + ContextScope = ContextScope.Process + }; + + // Act + IAuthenticationProvider authProvider = AuthenticationHelpers.GetAuthProvider(delegatedAuthContext); + + // Assert + Assert.IsType(authProvider); + + // reset static instance. + GraphSession.Reset(); + } #if NETCORE [Fact] From fcbf3206f139a36d42d379aed5cba1e2f36246c3 Mon Sep 17 00:00:00 2001 From: George Date: Fri, 16 Apr 2021 17:53:20 +0300 Subject: [PATCH 04/16] Update MSAL to 4.29. Add Microsoft.WIndows.SDK.Contracts as a direct dependency, as it was transient before via MSAL 4.23 Handle case when browser may not open or other unexpected error condition. Minor refatoring. --- .../Authentication.Core/Authenticator.cs | 103 ++++++++++++++---- .../Interfaces/IAuthContext.cs | 8 ++ ...Microsoft.Graph.Authentication.Core.csproj | 7 +- .../Authentication/Cmdlets/ConnectMgGraph.cs | 13 ++- .../Utilities/DependencyAssemblyResolver.cs | 2 +- 5 files changed, 103 insertions(+), 30 deletions(-) diff --git a/src/Authentication/Authentication.Core/Authenticator.cs b/src/Authentication/Authentication.Core/Authenticator.cs index 343ae9ffae5..91f1160cc61 100644 --- a/src/Authentication/Authentication.Core/Authenticator.cs +++ b/src/Authentication/Authentication.Core/Authenticator.cs @@ -30,28 +30,51 @@ public static class Authenticator /// Whether or not to force refresh a token if one exists. /// The cancellation token. /// - public static async Task<(IAuthContext context, AuthError authError)> AuthenticateAsync(IAuthContext authContext, bool forceRefresh, CancellationToken cancellationToken) + public static Task<(IAuthContext context, AuthError authError)> AuthenticateAsync(IAuthContext authContext, bool forceRefresh, CancellationToken cancellationToken) { - try - { - // Gets a static instance of IAuthenticationProvider when the client app hasn't changed. - IAuthenticationProvider authProvider = AuthenticationHelpers.GetAuthProvider(authContext); - IClientApplicationBase clientApplication = null; + // Gets a static instance of IAuthenticationProvider when the client app hasn't changed. + var (authProvider, clientApplication, _) = GetClientApplication(authContext); + return AuthenticateAsync(clientApplication, authProvider, authContext, forceRefresh, cancellationToken); + } - if (authContext.AuthType == AuthenticationType.Delegated && authContext.UseDeviceAuth) - { - clientApplication = (authProvider as DeviceCodeProvider).ClientApplication; - } - if (authContext.AuthType == AuthenticationType.Delegated && !authContext.UseDeviceAuth) - { - clientApplication = (authProvider as InteractiveAuthenticationProvider).ClientApplication; - } - if (authContext.AuthType == AuthenticationType.AppOnly) - { - clientApplication = (authProvider as ClientCredentialProvider).ClientApplication; - } + /// + /// Authenticates the client using the provided . + /// + /// The to authenticate. + /// Whether or not to force refresh a token if one exists. + /// The cancellation token. + /// + /// + public static Task<(IAuthContext context, AuthError authError)> AuthenticateAsync(IAuthContext authContext, bool forceRefresh, CancellationToken cancellationToken, Action fallBackWarning) + { + // Gets a static instance of IAuthenticationProvider when the client app hasn't changed. + var (authProvider, clientApplication, authProviderType) = GetClientApplication(authContext); + fallBackWarning(authProviderType); + return AuthenticateAsync(clientApplication, authProvider, authContext, forceRefresh, cancellationToken); + } - // Incremental scope consent without re-instantiating the auth provider. We will use a static instance. + /// + /// Signs out of the provided . + /// + /// The to sign-out from. + public static void LogOut(IAuthContext authContext) + { + AuthenticationHelpers.Logout(authContext); + } + /// + /// Authenticates the client using the provided . + /// + /// + /// + /// + /// + /// + /// + private static async Task<(IAuthContext context, AuthError authError)> AuthenticateAsync(IClientApplicationBase clientApplication, IAuthenticationProvider authProvider, IAuthContext authContext, bool forceRefresh, CancellationToken cancellationToken) + { + try + { + // Incremental scope consent without re-instantiating the auth provider. We will use provided instance. GraphRequestContext graphRequestContext = new GraphRequestContext(); graphRequestContext.CancellationToken = cancellationToken; graphRequestContext.MiddlewareOptions = new Dictionary @@ -94,7 +117,7 @@ public static class Authenticator //ReAuthenticate using DeviceCode as fallback. var (retryAuthContext, retryAuthError) = await AuthenticateAsync(authContext, forceRefresh, cancellationToken); //Indicate that this was a Fallback - retryAuthError = new AuthError(AuthErrorType.FallBack, retryAuthError.Exception); + retryAuthError = new AuthError(AuthErrorType.FallBack, retryAuthError.Exception ?? authEx); return (retryAuthContext, retryAuthError); } // DeviceCode Authentication Failure: Timeout @@ -115,13 +138,44 @@ public static class Authenticator return (authContext, new AuthError(AuthErrorType.Unknown, ex.InnerException ?? ex)); } } + /// - /// Signs out of the provided . + /// Gets a static instance of IAuthenticationProvider when the client app hasn't changed. /// - /// The to sign-out from. - public static void LogOut(IAuthContext authContext) + /// + /// + private static (IAuthenticationProvider authProvider, IClientApplicationBase clientApplication, AuthProviderType authProviderType) GetClientApplication(IAuthContext authContext) { - AuthenticationHelpers.Logout(authContext); + // Gets a static instance of IAuthenticationProvider when the client app hasn't changed. + var authProvider = AuthenticationHelpers.GetAuthProvider(authContext); + IClientApplicationBase clientApplication = null; + var authProviderType = AuthProviderType.None; + + if (authContext.AuthType == AuthenticationType.Delegated && authContext.UseDeviceAuth) + { + clientApplication = (authProvider as DeviceCodeProvider).ClientApplication; + authProviderType = AuthProviderType.DeviceCodeProvider; + } + + if (authContext.AuthType == AuthenticationType.Delegated && !authContext.UseDeviceAuth) + { + var interactiveProvider = (authProvider as InteractiveAuthenticationProvider).ClientApplication; + authProviderType = AuthProviderType.InteractiveAuthenticationProvider; + if (!interactiveProvider.IsUserInteractive()) + { + authContext.UseDeviceAuth = true; + var (fallBackAuthProvider, fallBackClientApplication, fallBackAuthProviderType) = GetClientApplication(authContext); + fallBackAuthProviderType = AuthProviderType.DeviceCodeProviderFallBack; + return (fallBackAuthProvider, fallBackClientApplication, fallBackAuthProviderType); + } + } + if (authContext.AuthType == AuthenticationType.AppOnly) + { + clientApplication = (authProvider as ClientCredentialProvider).ClientApplication; + authProviderType = AuthProviderType.ClientCredentialProvider; + } + + return (authProvider, clientApplication, authProviderType); } private static bool IsUnableToOpenWebPageError(Exception exception) @@ -130,5 +184,6 @@ private static bool IsUnableToOpenWebPageError(Exception exception) clientException?.ErrorCode == MsalError.LinuxXdgOpen || (exception.Message?.ToLower()?.Contains("unable to open a web page") ?? false); } + } } \ No newline at end of file diff --git a/src/Authentication/Authentication.Core/Interfaces/IAuthContext.cs b/src/Authentication/Authentication.Core/Interfaces/IAuthContext.cs index 37122bdfdff..d3caff827f0 100644 --- a/src/Authentication/Authentication.Core/Interfaces/IAuthContext.cs +++ b/src/Authentication/Authentication.Core/Interfaces/IAuthContext.cs @@ -28,6 +28,14 @@ public enum AuthErrorType ClientCredentialsFailure, Unknown } + public enum AuthProviderType + { + None, + InteractiveAuthenticationProvider, + DeviceCodeProvider, + DeviceCodeProviderFallBack, + ClientCredentialProvider + } public readonly struct AuthError { diff --git a/src/Authentication/Authentication.Core/Microsoft.Graph.Authentication.Core.csproj b/src/Authentication/Authentication.Core/Microsoft.Graph.Authentication.Core.csproj index 63185b31d1c..7eeb4c233bf 100644 --- a/src/Authentication/Authentication.Core/Microsoft.Graph.Authentication.Core.csproj +++ b/src/Authentication/Authentication.Core/Microsoft.Graph.Authentication.Core.csproj @@ -6,12 +6,13 @@ 1.4.3 - - - + + + + diff --git a/src/Authentication/Authentication/Cmdlets/ConnectMgGraph.cs b/src/Authentication/Authentication/Cmdlets/ConnectMgGraph.cs index c274afa5e04..80b7f7de0b3 100644 --- a/src/Authentication/Authentication/Cmdlets/ConnectMgGraph.cs +++ b/src/Authentication/Authentication/Cmdlets/ConnectMgGraph.cs @@ -209,8 +209,16 @@ private async Task ProcessRecordAsync() break; } - // Save auth context to session state. - var (context, authError) = await Authenticator.AuthenticateAsync(authContext, ForceRefresh, _cancellationTokenSource.Token); + + var (context, authError) = await Authenticator.AuthenticateAsync(authContext, ForceRefresh, _cancellationTokenSource.Token, + type => + { + // Early Detection of FallBack to DeviceCode + if (type == AuthProviderType.DeviceCodeProviderFallBack) + { + WriteWarning(Resources.DeviceCodeFallback); + } + }); switch (authError.AuthErrorType) { @@ -218,6 +226,7 @@ private async Task ProcessRecordAsync() case AuthErrorType.FallBack: { GraphSession.Instance.AuthContext = context; + //Fallback was due to an Exception, ie Browser Could not be Opened. if (authError.AuthErrorType == AuthErrorType.FallBack) { WriteWarning(Resources.DeviceCodeFallback); diff --git a/src/Authentication/Authentication/Utilities/DependencyAssemblyResolver.cs b/src/Authentication/Authentication/Utilities/DependencyAssemblyResolver.cs index 82fe833d2dc..4cb7f5fe4b1 100644 --- a/src/Authentication/Authentication/Utilities/DependencyAssemblyResolver.cs +++ b/src/Authentication/Authentication/Utilities/DependencyAssemblyResolver.cs @@ -13,7 +13,7 @@ public static class DependencyAssemblyResolver // Catalog our dependencies here to ensure we don't load anything else. private static IReadOnlyDictionary Dependencies = new Dictionary { - { "Microsoft.Identity.Client", new Version("4.23.0.0") }, + { "Microsoft.Identity.Client", new Version("4.29.0.0") }, { "Microsoft.Graph.Auth", new Version("1.0.0.0") }, { "Microsoft.IdentityModel.Tokens", new Version("5.6.0.61018") }, { "Microsoft.IdentityModel.Logging", new Version("5.6.0.61018") }, From 14627f0628539bece4bbd7a8c6d4e44089c260e4 Mon Sep 17 00:00:00 2001 From: George <1641829+finsharp@users.noreply.github.com> Date: Fri, 16 Apr 2021 18:40:19 +0300 Subject: [PATCH 05/16] Update src/Authentication/Authentication/Properties/Resources.resx Co-authored-by: Peter Ombwa --- src/Authentication/Authentication/Properties/Resources.resx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Authentication/Authentication/Properties/Resources.resx b/src/Authentication/Authentication/Properties/Resources.resx index c62945aad9a..f9618810abb 100644 --- a/src/Authentication/Authentication/Properties/Resources.resx +++ b/src/Authentication/Authentication/Properties/Resources.resx @@ -227,6 +227,6 @@ Interactive authentication is not supported in this session, please run cmdlet 'Connect-MgGraph -UseDeviceAuthentication'. - Interactive authentication is not supported in this session, Falling Back to DeviceCode. Future versions will not automatically fallback to DeviceCode. + Interactive authentication is not supported in this session, falling back to DeviceCode. Future versions will not automatically fallback to DeviceCode. - \ No newline at end of file + From 4b4f65cf81d1db1460e8a2b83d6a9c37c3a4c89a Mon Sep 17 00:00:00 2001 From: George Date: Fri, 23 Apr 2021 01:24:50 +0300 Subject: [PATCH 06/16] Simplify fallback Logic. Store AuthProviderType in AuthContext. Add test for fallback scenario. --- .../Authentication.Core/Authenticator.cs | 131 ++++++------------ .../Interfaces/IAuthContext.cs | 26 +--- .../Utilities/AuthenticationHelpers.cs | 68 ++++----- .../Helpers/AuthenticationHelpersTests.cs | 23 ++- .../Authentication/Cmdlets/ConnectMgGraph.cs | 50 ++----- .../Authentication/Models/AuthContext.cs | 2 +- 6 files changed, 117 insertions(+), 183 deletions(-) diff --git a/src/Authentication/Authentication.Core/Authenticator.cs b/src/Authentication/Authentication.Core/Authenticator.cs index 91f1160cc61..f007ba4011d 100644 --- a/src/Authentication/Authentication.Core/Authenticator.cs +++ b/src/Authentication/Authentication.Core/Authenticator.cs @@ -29,49 +29,38 @@ public static class Authenticator /// The to authenticate. /// Whether or not to force refresh a token if one exists. /// The cancellation token. + /// Callback to report FallBack to DeviceCode Authentication /// - public static Task<(IAuthContext context, AuthError authError)> AuthenticateAsync(IAuthContext authContext, bool forceRefresh, CancellationToken cancellationToken) + public static async Task AuthenticateAsync(IAuthContext authContext, bool forceRefresh, CancellationToken cancellationToken, Action fallBackWarning = null) { // Gets a static instance of IAuthenticationProvider when the client app hasn't changed. - var (authProvider, clientApplication, _) = GetClientApplication(authContext); - return AuthenticateAsync(clientApplication, authProvider, authContext, forceRefresh, cancellationToken); - } - - /// - /// Authenticates the client using the provided . - /// - /// The to authenticate. - /// Whether or not to force refresh a token if one exists. - /// The cancellation token. - /// - /// - public static Task<(IAuthContext context, AuthError authError)> AuthenticateAsync(IAuthContext authContext, bool forceRefresh, CancellationToken cancellationToken, Action fallBackWarning) - { - // Gets a static instance of IAuthenticationProvider when the client app hasn't changed. - var (authProvider, clientApplication, authProviderType) = GetClientApplication(authContext); - fallBackWarning(authProviderType); - return AuthenticateAsync(clientApplication, authProvider, authContext, forceRefresh, cancellationToken); - } - - /// - /// Signs out of the provided . - /// - /// The to sign-out from. - public static void LogOut(IAuthContext authContext) - { - AuthenticationHelpers.Logout(authContext); - } - /// - /// Authenticates the client using the provided . - /// - /// - /// - /// - /// - /// - /// - private static async Task<(IAuthContext context, AuthError authError)> AuthenticateAsync(IClientApplicationBase clientApplication, IAuthenticationProvider authProvider, IAuthContext authContext, bool forceRefresh, CancellationToken cancellationToken) - { + var authProvider = AuthenticationHelpers.GetAuthProvider(authContext); + IClientApplicationBase clientApplication = null; + switch (authContext.AuthProviderType) + { + case AuthProviderType.DeviceCodeProvider: + case AuthProviderType.DeviceCodeProviderFallBack: + clientApplication = (authProvider as DeviceCodeProvider).ClientApplication; + break; + case AuthProviderType.InteractiveAuthenticationProvider: + { + var interactiveProvider = (authProvider as InteractiveAuthenticationProvider).ClientApplication; + //When User is not Interactive, Pre-Emptively Fallback and warn, to DeviceCode + if (!interactiveProvider.IsUserInteractive()) + { + authContext.AuthProviderType = AuthProviderType.DeviceCodeProviderFallBack; + fallBackWarning?.Invoke(); + var fallBackAuthContext= await AuthenticateAsync(authContext, forceRefresh, cancellationToken, fallBackWarning); + return fallBackAuthContext; + } + break; + } + case AuthProviderType.ClientCredentialProvider: + { + clientApplication = (authProvider as ClientCredentialProvider).ClientApplication; + break; + } + } try { // Incremental scope consent without re-instantiating the auth provider. We will use provided instance. @@ -106,76 +95,45 @@ public static void LogOut(IAuthContext authContext) } JwtHelpers.DecodeJWT(httpRequestMessage.Headers.Authorization?.Parameter, account, ref authContext); - return (authContext, new AuthError(AuthErrorType.None, null)); + return authContext; } catch (AuthenticationException authEx) { //Interactive Authentication Failure: Could Not Open Browser, fallback to DeviceAuth if (IsUnableToOpenWebPageError(authEx)) { - authContext.UseDeviceAuth = true; + authContext.AuthProviderType = AuthProviderType.DeviceCodeProviderFallBack; //ReAuthenticate using DeviceCode as fallback. - var (retryAuthContext, retryAuthError) = await AuthenticateAsync(authContext, forceRefresh, cancellationToken); + var fallBackAuthContext = await AuthenticateAsync(authContext, forceRefresh, cancellationToken); //Indicate that this was a Fallback - retryAuthError = new AuthError(AuthErrorType.FallBack, retryAuthError.Exception ?? authEx); - return (retryAuthContext, retryAuthError); + if (fallBackWarning != null && fallBackAuthContext.AuthProviderType == AuthProviderType.DeviceCodeProviderFallBack) + { + fallBackWarning(); + } + return fallBackAuthContext; } // DeviceCode Authentication Failure: Timeout if (authEx.InnerException is TaskCanceledException && cancellationToken.IsCancellationRequested) { // DeviceCodeTimeout - var deviceCode = new Exception(string.Format( - CultureInfo.CurrentCulture, - ErrorConstants.Message.DeviceCodeTimeout, - Constants.MaxDeviceCodeTimeOut)); - return (authContext, new AuthError(AuthErrorType.DeviceCodeFailure, deviceCode.InnerException ?? deviceCode)); + throw new Exception(string.Format(CultureInfo.CurrentCulture, ErrorConstants.Message.DeviceCodeTimeout, Constants.MaxDeviceCodeTimeOut)); } //Something Unknown Went Wrong - return (authContext, new AuthError(AuthErrorType.Unknown, authEx.InnerException ?? authEx)); + throw authEx.InnerException ?? authEx; } catch (Exception ex) { - return (authContext, new AuthError(AuthErrorType.Unknown, ex.InnerException ?? ex)); + throw ex.InnerException ?? ex; } } /// - /// Gets a static instance of IAuthenticationProvider when the client app hasn't changed. + /// Signs out of the provided . /// - /// - /// - private static (IAuthenticationProvider authProvider, IClientApplicationBase clientApplication, AuthProviderType authProviderType) GetClientApplication(IAuthContext authContext) + /// The to sign-out from. + public static void LogOut(IAuthContext authContext) { - // Gets a static instance of IAuthenticationProvider when the client app hasn't changed. - var authProvider = AuthenticationHelpers.GetAuthProvider(authContext); - IClientApplicationBase clientApplication = null; - var authProviderType = AuthProviderType.None; - - if (authContext.AuthType == AuthenticationType.Delegated && authContext.UseDeviceAuth) - { - clientApplication = (authProvider as DeviceCodeProvider).ClientApplication; - authProviderType = AuthProviderType.DeviceCodeProvider; - } - - if (authContext.AuthType == AuthenticationType.Delegated && !authContext.UseDeviceAuth) - { - var interactiveProvider = (authProvider as InteractiveAuthenticationProvider).ClientApplication; - authProviderType = AuthProviderType.InteractiveAuthenticationProvider; - if (!interactiveProvider.IsUserInteractive()) - { - authContext.UseDeviceAuth = true; - var (fallBackAuthProvider, fallBackClientApplication, fallBackAuthProviderType) = GetClientApplication(authContext); - fallBackAuthProviderType = AuthProviderType.DeviceCodeProviderFallBack; - return (fallBackAuthProvider, fallBackClientApplication, fallBackAuthProviderType); - } - } - if (authContext.AuthType == AuthenticationType.AppOnly) - { - clientApplication = (authProvider as ClientCredentialProvider).ClientApplication; - authProviderType = AuthProviderType.ClientCredentialProvider; - } - - return (authProvider, clientApplication, authProviderType); + AuthenticationHelpers.Logout(authContext); } private static bool IsUnableToOpenWebPageError(Exception exception) @@ -184,6 +142,5 @@ private static bool IsUnableToOpenWebPageError(Exception exception) clientException?.ErrorCode == MsalError.LinuxXdgOpen || (exception.Message?.ToLower()?.Contains("unable to open a web page") ?? false); } - } } \ No newline at end of file diff --git a/src/Authentication/Authentication.Core/Interfaces/IAuthContext.cs b/src/Authentication/Authentication.Core/Interfaces/IAuthContext.cs index d3caff827f0..0b0dc48595a 100644 --- a/src/Authentication/Authentication.Core/Interfaces/IAuthContext.cs +++ b/src/Authentication/Authentication.Core/Interfaces/IAuthContext.cs @@ -4,7 +4,6 @@ namespace Microsoft.Graph.PowerShell.Authentication { - using System; using System.Security.Cryptography.X509Certificates; public enum AuthenticationType @@ -19,33 +18,14 @@ public enum ContextScope Process, CurrentUser } - public enum AuthErrorType - { - None, - FallBack, - DeviceCodeFailure, - InteractiveAuthenticationFailure, - ClientCredentialsFailure, - Unknown - } public enum AuthProviderType { None, InteractiveAuthenticationProvider, DeviceCodeProvider, DeviceCodeProviderFallBack, - ClientCredentialProvider - } - - public readonly struct AuthError - { - public AuthError(AuthErrorType errorType, Exception ex) - { - AuthErrorType = errorType; - Exception = ex; - } - public AuthErrorType AuthErrorType { get; } - public Exception Exception { get; } + ClientCredentialProvider, + UserProvidedToken } public interface IAuthContext { @@ -54,11 +34,11 @@ public interface IAuthContext string CertificateThumbprint { get; set; } string[] Scopes { get; set; } AuthenticationType AuthType { get; set; } + AuthProviderType AuthProviderType { get; set; } string CertificateName { get; set; } string Account { get; set; } string AppName { get; set; } ContextScope ContextScope { get; set; } X509Certificate2 Certificate { get; set; } - bool UseDeviceAuth { get; set; } } } diff --git a/src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs b/src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs index 78bf3f45478..644d37302c5 100644 --- a/src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs +++ b/src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs @@ -66,46 +66,46 @@ public static IAuthenticationProvider GetAuthProvider(IAuthContext authContext) switch (authContext.AuthType) { case AuthenticationType.Delegated: - { - //Specify Default RedirectUri - //https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/MSAL.NET-uses-web-browser - IPublicClientApplication publicClientApp = PublicClientApplicationBuilder - .Create(authContext.ClientId) - .WithTenantId(authContext.TenantId) - .WithAuthority(authorityUrl) - .WithClientCapabilities(new[] { "cp1" }) - .WithDefaultRedirectUri() - .Build(); - ConfigureTokenCache(publicClientApp.UserTokenCache, authContext); - if (authContext.UseDeviceAuth) { - authProvider = new DeviceCodeProvider(publicClientApp, authContext.Scopes, async (result) => + //Specify Default RedirectUri + //https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/MSAL.NET-uses-web-browser + IPublicClientApplication publicClientApp = PublicClientApplicationBuilder + .Create(authContext.ClientId) + .WithTenantId(authContext.TenantId) + .WithAuthority(authorityUrl) + .WithClientCapabilities(new[] { "cp1" }) + .WithDefaultRedirectUri() + .Build(); + ConfigureTokenCache(publicClientApp.UserTokenCache, authContext); + switch (authContext.AuthProviderType) { - await Console.Out.WriteLineAsync(result.Message); - }); - } - else - { - authProvider = new InteractiveAuthenticationProvider(publicClientApp, authContext.Scopes); + case AuthProviderType.DeviceCodeProvider: + case AuthProviderType.DeviceCodeProviderFallBack: + authProvider = new DeviceCodeProvider(publicClientApp, authContext.Scopes, + async result => { await Console.Out.WriteLineAsync(result.Message); }); + break; + case AuthProviderType.InteractiveAuthenticationProvider: + authProvider = new InteractiveAuthenticationProvider(publicClientApp, authContext.Scopes); + break; + } + break; } - break; - } case AuthenticationType.AppOnly: - { - IConfidentialClientApplication confidentialClientApp = ConfidentialClientApplicationBuilder - .Create(authContext.ClientId) - .WithTenantId(authContext.TenantId) - .WithAuthority(authorityUrl) - .WithCertificate(GetCertificate(authContext)) - .Build(); + { + IConfidentialClientApplication confidentialClientApp = ConfidentialClientApplicationBuilder + .Create(authContext.ClientId) + .WithTenantId(authContext.TenantId) + .WithAuthority(authorityUrl) + .WithCertificate(GetCertificate(authContext)) + .Build(); - ConfigureTokenCache(confidentialClientApp.AppTokenCache, authContext); - string graphBaseUrl = GraphSession.Instance.Environment?.GraphEndpoint ?? "https://graph.microsoft.com"; - authProvider = new ClientCredentialProvider(confidentialClientApp, $"{graphBaseUrl}/.default"); - break; - } + ConfigureTokenCache(confidentialClientApp.AppTokenCache, authContext); + string graphBaseUrl = GraphSession.Instance.Environment?.GraphEndpoint ?? "https://graph.microsoft.com"; + authProvider = new ClientCredentialProvider(confidentialClientApp, $"{graphBaseUrl}/.default"); + break; + } case AuthenticationType.UserProvidedAccessToken: - authProvider = new DelegateAuthenticationProvider((requestMessage) => + authProvider = new DelegateAuthenticationProvider(requestMessage => { requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", new NetworkCredential(string.Empty, GraphSession.Instance.UserProvidedToken).Password); diff --git a/src/Authentication/Authentication.Test/Helpers/AuthenticationHelpersTests.cs b/src/Authentication/Authentication.Test/Helpers/AuthenticationHelpersTests.cs index 578da855feb..359bd51f163 100644 --- a/src/Authentication/Authentication.Test/Helpers/AuthenticationHelpersTests.cs +++ b/src/Authentication/Authentication.Test/Helpers/AuthenticationHelpersTests.cs @@ -56,7 +56,28 @@ public void ShouldUseDeviceCodeWhenSpecifiedByUser() AuthType = AuthenticationType.Delegated, Scopes = new[] { "User.Read" }, ContextScope = ContextScope.Process, - UseDeviceAuth = true + AuthProviderType = AuthProviderType.DeviceCodeProvider + }; + + // Act + IAuthenticationProvider authProvider = AuthenticationHelpers.GetAuthProvider(delegatedAuthContext); + + // Assert + Assert.IsType(authProvider); + + // reset static instance. + GraphSession.Reset(); + } + [Fact] + public void ShouldUseDeviceCodeWhenFallback() + { + // Arrange + AuthContext delegatedAuthContext = new AuthContext + { + AuthType = AuthenticationType.Delegated, + Scopes = new[] { "User.Read" }, + ContextScope = ContextScope.Process, + AuthProviderType = AuthProviderType.DeviceCodeProviderFallBack }; // Act diff --git a/src/Authentication/Authentication/Cmdlets/ConnectMgGraph.cs b/src/Authentication/Authentication/Cmdlets/ConnectMgGraph.cs index 80b7f7de0b3..fef860afea4 100644 --- a/src/Authentication/Authentication/Cmdlets/ConnectMgGraph.cs +++ b/src/Authentication/Authentication/Cmdlets/ConnectMgGraph.cs @@ -185,7 +185,7 @@ private async Task ProcessRecordAsync() authContext.Scopes = processedScopes.Length == 0 ? new[] { "User.Read" } : processedScopes; // Default to CurrentUser but allow the customer to change this via `ContextScope` param. authContext.ContextScope = this.IsParameterBound(nameof(ContextScope)) ? ContextScope : ContextScope.CurrentUser; - authContext.UseDeviceAuth = UseDeviceAuthentication; + authContext.AuthProviderType = UseDeviceAuthentication ? AuthProviderType.DeviceCodeProvider : AuthProviderType.InteractiveAuthenticationProvider; } break; case Constants.AppParameterSet: @@ -197,6 +197,7 @@ private async Task ProcessRecordAsync() authContext.Certificate = Certificate; // Default to Process but allow the customer to change this via `ContextScope` param. authContext.ContextScope = this.IsParameterBound(nameof(ContextScope)) ? ContextScope : ContextScope.Process; + authContext.AuthProviderType = AuthProviderType.ClientCredentialProvider; } break; case Constants.AccessTokenParameterSet: @@ -205,48 +206,23 @@ private async Task ProcessRecordAsync() authContext.ContextScope = ContextScope.Process; // Store user provided access token to a session object. GraphSession.Instance.UserProvidedToken = new NetworkCredential(string.Empty, AccessToken).SecurePassword; + authContext.AuthProviderType = AuthProviderType.UserProvidedToken; } break; } - - var (context, authError) = await Authenticator.AuthenticateAsync(authContext, ForceRefresh, _cancellationTokenSource.Token, - type => - { - // Early Detection of FallBack to DeviceCode - if (type == AuthProviderType.DeviceCodeProviderFallBack) - { - WriteWarning(Resources.DeviceCodeFallback); - } - }); - - switch (authError.AuthErrorType) + try { - case AuthErrorType.None: - case AuthErrorType.FallBack: - { - GraphSession.Instance.AuthContext = context; - //Fallback was due to an Exception, ie Browser Could not be Opened. - if (authError.AuthErrorType == AuthErrorType.FallBack) - { - WriteWarning(Resources.DeviceCodeFallback); - } - WriteObject("Welcome To Microsoft Graph!"); - break; - } - case AuthErrorType.InteractiveAuthenticationFailure: - { - WriteWarning(Resources.InteractiveAuthNotSupported); - WriteDebug(authError.Exception.ToString()); - throw authError.Exception; - } - case AuthErrorType.DeviceCodeFailure: - case AuthErrorType.ClientCredentialsFailure: - case AuthErrorType.Unknown: - throw authError.Exception; - default: - throw new ArgumentOutOfRangeException(); + + GraphSession.Instance.AuthContext = await Authenticator.AuthenticateAsync(authContext, ForceRefresh, + _cancellationTokenSource.Token, + () => { WriteWarning(Resources.DeviceCodeFallback); }); + } + catch (Exception ex) + { + throw ex; } + WriteObject("Welcome To Microsoft Graph!"); } } protected override void StopProcessing() diff --git a/src/Authentication/Authentication/Models/AuthContext.cs b/src/Authentication/Authentication/Models/AuthContext.cs index bc8e0838e5d..d52fbb312c2 100644 --- a/src/Authentication/Authentication/Models/AuthContext.cs +++ b/src/Authentication/Authentication/Models/AuthContext.cs @@ -13,12 +13,12 @@ public class AuthContext: IAuthContext public string CertificateThumbprint { get; set; } public string[] Scopes { get; set; } public AuthenticationType AuthType { get; set; } + public AuthProviderType AuthProviderType { get; set; } public string CertificateName { get; set; } public string Account { get; set; } public string AppName { get; set; } public ContextScope ContextScope { get ; set ; } public X509Certificate2 Certificate { get; set; } - public bool UseDeviceAuth { get; set; } public AuthContext() { From 2d7af65259dbc80ef8965dbe6ffd0715f7e7e6b4 Mon Sep 17 00:00:00 2001 From: George Date: Mon, 26 Apr 2021 22:13:34 +0300 Subject: [PATCH 07/16] Install certificate in auth step since stages may run on different machines. --- .../generate-auth-module-template.yml | 31 +++++++++++++++++++ .azure-pipelines/integrated-pipeline.yml | 2 ++ tools/Tests/loadEnv.ps1 | 1 + 3 files changed, 34 insertions(+) diff --git a/.azure-pipelines/generate-auth-module-template.yml b/.azure-pipelines/generate-auth-module-template.yml index 0600456da0a..66b9e1a464b 100644 --- a/.azure-pipelines/generate-auth-module-template.yml +++ b/.azure-pipelines/generate-auth-module-template.yml @@ -21,6 +21,17 @@ parameters: displayName: 'Build Number' type: string default: $[format('{0:yyMMddHH}', pipeline.startTime)] + - name: AZURESUBSCRIPTION + default: "Microsoft Graph Build Agents (Win+Lin)" + displayName: Azure Subscription + + - name: KEYVAULT + default: "msgraph-build-keyvault" + displayName: Build Key vault + +variables: + AZURESUBSCRIPTION: ${{ parameters.AZURESUBSCRIPTION }} + KEYVAULT: ${{ parameters.KEYVAULT }} jobs: - job: MsGraphPSSDKAuthModuleGeneration @@ -29,6 +40,26 @@ jobs: steps: - template: ./install-tools-template.yml + - task: AzureKeyVault@1 + inputs: + azureSubscription: $(AZURESUBSCRIPTION) + KeyVaultName: $(KEYVAULT) + SecretsFilter: '*' + RunAsPreJob: true + + - task: PowerShell@2 + displayName: 'Install Test Certificate' + inputs: + targetType: 'inline' + script: | + $kvSecretBytes = [System.Convert]::FromBase64String('$(MsGraphPSSDKCertificate)') + $certCollection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection + $certCollection.Import($kvSecretBytes,$null,[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable) + $store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My", "CurrentUser") + $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) + $store.AddRange($certCollection) + $store.Close() + - task: PowerShell@2 displayName: 'Generate and Build Auth Module' inputs: diff --git a/.azure-pipelines/integrated-pipeline.yml b/.azure-pipelines/integrated-pipeline.yml index dc2af0bc751..31a22510e42 100644 --- a/.azure-pipelines/integrated-pipeline.yml +++ b/.azure-pipelines/integrated-pipeline.yml @@ -101,6 +101,8 @@ stages: AUTH_MODULE_PATH: $(AUTH_MODULE_PATH) EnableSigning: true BUILDNUMBER: $(BUILDNUMBER) + KEYVAULT: $(KEYVAULT) + AZURESUBSCRIPTION: $(AZURESUBSCRIPTION) - stage: GenerateBetaModules displayName: 'Generate Beta Modules (Microsoft.Graph.*)' diff --git a/tools/Tests/loadEnv.ps1 b/tools/Tests/loadEnv.ps1 index 3e5126424e8..3ac8d6b7307 100644 --- a/tools/Tests/loadEnv.ps1 +++ b/tools/Tests/loadEnv.ps1 @@ -29,4 +29,5 @@ if (Test-Path -Path $envFilePath) { ClientId = $env.ClientId TenantId = $env.TenantId } + $PSDefaultParameterValues=@{"Connect-MgGraph:TenantId"=$env.TenantId; "Connect-MgGraph:ClientId"=$env.ClientId; "Connect-MgGraph:CertificateThumbprint"=${env:CERTIFICATETHUMBPRINT}} } \ No newline at end of file From ea58f1465738d719950b4315ac75d9275de93daf Mon Sep 17 00:00:00 2001 From: George Date: Mon, 26 Apr 2021 22:20:44 +0300 Subject: [PATCH 08/16] formatting. --- .azure-pipelines/generate-auth-module-template.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.azure-pipelines/generate-auth-module-template.yml b/.azure-pipelines/generate-auth-module-template.yml index 66b9e1a464b..b245dc97023 100644 --- a/.azure-pipelines/generate-auth-module-template.yml +++ b/.azure-pipelines/generate-auth-module-template.yml @@ -50,7 +50,7 @@ jobs: - task: PowerShell@2 displayName: 'Install Test Certificate' inputs: - targetType: 'inline' + targetType: 'inline' script: | $kvSecretBytes = [System.Convert]::FromBase64String('$(MsGraphPSSDKCertificate)') $certCollection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection From bd9550dfc062f5d1f5c843d26b13af9a426261a1 Mon Sep 17 00:00:00 2001 From: George Date: Mon, 26 Apr 2021 22:21:33 +0300 Subject: [PATCH 09/16] Remove variables. --- .azure-pipelines/generate-auth-module-template.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.azure-pipelines/generate-auth-module-template.yml b/.azure-pipelines/generate-auth-module-template.yml index b245dc97023..64a9ff49617 100644 --- a/.azure-pipelines/generate-auth-module-template.yml +++ b/.azure-pipelines/generate-auth-module-template.yml @@ -29,9 +29,6 @@ parameters: default: "msgraph-build-keyvault" displayName: Build Key vault -variables: - AZURESUBSCRIPTION: ${{ parameters.AZURESUBSCRIPTION }} - KEYVAULT: ${{ parameters.KEYVAULT }} jobs: - job: MsGraphPSSDKAuthModuleGeneration From 3b2b7ff090a5bc86179842b54af3609c30bf8ddd Mon Sep 17 00:00:00 2001 From: George Date: Mon, 26 Apr 2021 23:27:15 +0300 Subject: [PATCH 10/16] make tests verbose. --- .../Authentication/test/Invoke-MgGraphRequest.Tests.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Authentication/Authentication/test/Invoke-MgGraphRequest.Tests.ps1 b/src/Authentication/Authentication/test/Invoke-MgGraphRequest.Tests.ps1 index b3a90dcd091..ba6d4ddd27c 100644 --- a/src/Authentication/Authentication/test/Invoke-MgGraphRequest.Tests.ps1 +++ b/src/Authentication/Authentication/test/Invoke-MgGraphRequest.Tests.ps1 @@ -11,18 +11,18 @@ } Describe 'Invoke-MgGraphRequest Collection Results' { BeforeAll { - Connect-MgGraph + Connect-MgGraph -Debug -Verbose } It 'ShouldReturnPsObject' { - Invoke-MgGraphRequest -OutputType PSObject -Uri "https://graph.microsoft.com/v1.0/users" | Should -BeOfType [System.Management.Automation.PSObject] + Invoke-MgGraphRequest -OutputType PSObject -Uri "https://graph.microsoft.com/v1.0/users" -Debug -Verbose | Should -BeOfType [System.Management.Automation.PSObject] } It 'ShouldReturnHashTable' { - $hashTable = Invoke-MgGraphRequest -OutputType Hashtable -Uri "https://graph.microsoft.com/v1.0/users" | Should -BeOfType [System.Collections.Hashtable] + $hashTable = Invoke-MgGraphRequest -OutputType Hashtable -Uri "https://graph.microsoft.com/v1.0/users" -Debug -Verbose | Should -BeOfType [System.Collections.Hashtable] } It 'ShouldReturnJsonString' { - $jsonString = Invoke-MgGraphRequest -OutputType Json -Uri "https://graph.microsoft.com/v1.0/users" | Should -BeOfType [System.String] + $jsonString = Invoke-MgGraphRequest -OutputType Json -Uri "https://graph.microsoft.com/v1.0/users" -Debug -Verbose | Should -BeOfType [System.String] } It 'ShouldReturnHttpResponseMessage' { From 5822e36fc9825193640dad78b5e75a80472b968c Mon Sep 17 00:00:00 2001 From: George Date: Mon, 26 Apr 2021 23:44:17 +0300 Subject: [PATCH 11/16] use device auth --- .../Authentication/test/Invoke-MgGraphRequest.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Authentication/Authentication/test/Invoke-MgGraphRequest.Tests.ps1 b/src/Authentication/Authentication/test/Invoke-MgGraphRequest.Tests.ps1 index ba6d4ddd27c..8f1c91f942c 100644 --- a/src/Authentication/Authentication/test/Invoke-MgGraphRequest.Tests.ps1 +++ b/src/Authentication/Authentication/test/Invoke-MgGraphRequest.Tests.ps1 @@ -11,7 +11,7 @@ } Describe 'Invoke-MgGraphRequest Collection Results' { BeforeAll { - Connect-MgGraph -Debug -Verbose + Connect-MgGraph -Debug -Verbose -UseDeviceAuthentication } It 'ShouldReturnPsObject' { Invoke-MgGraphRequest -OutputType PSObject -Uri "https://graph.microsoft.com/v1.0/users" -Debug -Verbose | Should -BeOfType [System.Management.Automation.PSObject] From eefb43a69328e42a05cc8c0a9e2d266fb6a121b3 Mon Sep 17 00:00:00 2001 From: George Date: Tue, 27 Apr 2021 00:12:40 +0300 Subject: [PATCH 12/16] unblock tests due to default auth change. --- .../Authentication/test/Connect-MgGraph.Tests.ps1 | 8 ++++---- .../Authentication/test/Invoke-MgGraphRequest.Tests.ps1 | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Authentication/Authentication/test/Connect-MgGraph.Tests.ps1 b/src/Authentication/Authentication/test/Connect-MgGraph.Tests.ps1 index 9360d9e8d5f..dde084b74d3 100644 --- a/src/Authentication/Authentication/test/Connect-MgGraph.Tests.ps1 +++ b/src/Authentication/Authentication/test/Connect-MgGraph.Tests.ps1 @@ -6,11 +6,11 @@ BeforeAll { } Describe 'Connect-MgGraph In Delegated Mode' { It 'ShouldThrowExceptionWhenInvalidTenantIdIsSpecified' { - { Connect-MgGraph -TenantId "thisdomaindoesnotexist.com" -Scopes 'User.Read.All' -ErrorAction Stop } | Should -Throw -ExpectedMessage "*Tenant 'thisdomaindoesnotexist.com' not found*" + { Connect-MgGraph -TenantId "thisdomaindoesnotexist.com" -Scopes 'User.Read.All' -ErrorAction Stop -UseDeviceAuthentication } | Should -Throw -ExpectedMessage "*Tenant 'thisdomaindoesnotexist.com' not found*" } It 'ShouldThrowExceptionWhenInvalidScopeIsSpecified' { - { Connect-MgGraph -Scopes 'User.Read.XYZ' -ErrorAction Stop } | Should -Throw -ExpectedMessage "*The scope 'User.Read.XYZ offline_access profile openid' does not exist*" + { Connect-MgGraph -Scopes 'User.Read.XYZ' -ErrorAction Stop -UseDeviceAuthentication } | Should -Throw -ExpectedMessage "*The scope 'User.Read.XYZ offline_access profile openid' does not exist*" } } @@ -26,11 +26,11 @@ Describe 'Connect-MgGraph In App Mode' { } Describe 'Connect-MgGraph Dependency Resolution' { BeforeAll { - Install-Module Az.Accounts -Repository PSGallery -Force + Install-Module Az.Accounts -Repository PSGallery -Force -AllowClobber } It 'ShouldLoadMgModuleSideBySideWithAzModule.' { { Connect-AzAccount -ApplicationId $RandomClientId -CertificateThumbprint "Invalid" -Tenant "Invalid" -ErrorAction Stop } | Should -Throw -ExpectedMessage "*Could not find tenant id*" - { Connect-MgGraph -Scopes "inavid.scope" -ErrorAction Stop } | Should -Throw -ExpectedMessage "*AADSTS70011:*" + { Connect-MgGraph -Scopes "inavid.scope" -ErrorAction Stop -UseDeviceAuthentication } | Should -Throw -ExpectedMessage "*AADSTS70011:*" } } \ No newline at end of file diff --git a/src/Authentication/Authentication/test/Invoke-MgGraphRequest.Tests.ps1 b/src/Authentication/Authentication/test/Invoke-MgGraphRequest.Tests.ps1 index 8f1c91f942c..b3a90dcd091 100644 --- a/src/Authentication/Authentication/test/Invoke-MgGraphRequest.Tests.ps1 +++ b/src/Authentication/Authentication/test/Invoke-MgGraphRequest.Tests.ps1 @@ -11,18 +11,18 @@ } Describe 'Invoke-MgGraphRequest Collection Results' { BeforeAll { - Connect-MgGraph -Debug -Verbose -UseDeviceAuthentication + Connect-MgGraph } It 'ShouldReturnPsObject' { - Invoke-MgGraphRequest -OutputType PSObject -Uri "https://graph.microsoft.com/v1.0/users" -Debug -Verbose | Should -BeOfType [System.Management.Automation.PSObject] + Invoke-MgGraphRequest -OutputType PSObject -Uri "https://graph.microsoft.com/v1.0/users" | Should -BeOfType [System.Management.Automation.PSObject] } It 'ShouldReturnHashTable' { - $hashTable = Invoke-MgGraphRequest -OutputType Hashtable -Uri "https://graph.microsoft.com/v1.0/users" -Debug -Verbose | Should -BeOfType [System.Collections.Hashtable] + $hashTable = Invoke-MgGraphRequest -OutputType Hashtable -Uri "https://graph.microsoft.com/v1.0/users" | Should -BeOfType [System.Collections.Hashtable] } It 'ShouldReturnJsonString' { - $jsonString = Invoke-MgGraphRequest -OutputType Json -Uri "https://graph.microsoft.com/v1.0/users" -Debug -Verbose | Should -BeOfType [System.String] + $jsonString = Invoke-MgGraphRequest -OutputType Json -Uri "https://graph.microsoft.com/v1.0/users" | Should -BeOfType [System.String] } It 'ShouldReturnHttpResponseMessage' { From 1d77e8b46abae2eaff8390dfad53d6b75ecc3fa8 Mon Sep 17 00:00:00 2001 From: George Date: Tue, 27 Apr 2021 00:14:58 +0300 Subject: [PATCH 13/16] revert environment loader. --- tools/Tests/loadEnv.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/Tests/loadEnv.ps1 b/tools/Tests/loadEnv.ps1 index 3ac8d6b7307..3e5126424e8 100644 --- a/tools/Tests/loadEnv.ps1 +++ b/tools/Tests/loadEnv.ps1 @@ -29,5 +29,4 @@ if (Test-Path -Path $envFilePath) { ClientId = $env.ClientId TenantId = $env.TenantId } - $PSDefaultParameterValues=@{"Connect-MgGraph:TenantId"=$env.TenantId; "Connect-MgGraph:ClientId"=$env.ClientId; "Connect-MgGraph:CertificateThumbprint"=${env:CERTIFICATETHUMBPRINT}} } \ No newline at end of file From 759f7099e05c31f7787da8aaa6090de22e6da3f2 Mon Sep 17 00:00:00 2001 From: George Date: Tue, 27 Apr 2021 00:25:28 +0300 Subject: [PATCH 14/16] default auth provider should not be none. --- .../Authentication.Core/Interfaces/IAuthContext.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Authentication/Authentication.Core/Interfaces/IAuthContext.cs b/src/Authentication/Authentication.Core/Interfaces/IAuthContext.cs index 0b0dc48595a..11994fd0b35 100644 --- a/src/Authentication/Authentication.Core/Interfaces/IAuthContext.cs +++ b/src/Authentication/Authentication.Core/Interfaces/IAuthContext.cs @@ -20,7 +20,6 @@ public enum ContextScope } public enum AuthProviderType { - None, InteractiveAuthenticationProvider, DeviceCodeProvider, DeviceCodeProviderFallBack, From 024afe6b1274cfa4f213eac554b2b9ae436c2e29 Mon Sep 17 00:00:00 2001 From: George Date: Tue, 27 Apr 2021 00:29:20 +0300 Subject: [PATCH 15/16] change cert store. --- .azure-pipelines/generate-auth-module-template.yml | 2 +- .azure-pipelines/integrated-pipeline.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.azure-pipelines/generate-auth-module-template.yml b/.azure-pipelines/generate-auth-module-template.yml index 64a9ff49617..ca1704e8354 100644 --- a/.azure-pipelines/generate-auth-module-template.yml +++ b/.azure-pipelines/generate-auth-module-template.yml @@ -52,7 +52,7 @@ jobs: $kvSecretBytes = [System.Convert]::FromBase64String('$(MsGraphPSSDKCertificate)') $certCollection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection $certCollection.Import($kvSecretBytes,$null,[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable) - $store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My", "CurrentUser") + $store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My", "LocalMachine") $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) $store.AddRange($certCollection) $store.Close() diff --git a/.azure-pipelines/integrated-pipeline.yml b/.azure-pipelines/integrated-pipeline.yml index 31a22510e42..00e952b4e50 100644 --- a/.azure-pipelines/integrated-pipeline.yml +++ b/.azure-pipelines/integrated-pipeline.yml @@ -81,7 +81,7 @@ stages: $kvSecretBytes = [System.Convert]::FromBase64String('$(MsGraphPSSDKCertificate)') $certCollection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection $certCollection.Import($kvSecretBytes,$null,[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable) - $store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My", "CurrentUser") + $store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My", "LocalMachine") $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) $store.AddRange($certCollection) $store.Close() From b70efc4fd03f1980fc4b5fa583216971a2c1709c Mon Sep 17 00:00:00 2001 From: George Date: Tue, 27 Apr 2021 00:31:55 +0300 Subject: [PATCH 16/16] store certs in current user. --- .azure-pipelines/generate-auth-module-template.yml | 2 +- .azure-pipelines/integrated-pipeline.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.azure-pipelines/generate-auth-module-template.yml b/.azure-pipelines/generate-auth-module-template.yml index ca1704e8354..64a9ff49617 100644 --- a/.azure-pipelines/generate-auth-module-template.yml +++ b/.azure-pipelines/generate-auth-module-template.yml @@ -52,7 +52,7 @@ jobs: $kvSecretBytes = [System.Convert]::FromBase64String('$(MsGraphPSSDKCertificate)') $certCollection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection $certCollection.Import($kvSecretBytes,$null,[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable) - $store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My", "LocalMachine") + $store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My", "CurrentUser") $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) $store.AddRange($certCollection) $store.Close() diff --git a/.azure-pipelines/integrated-pipeline.yml b/.azure-pipelines/integrated-pipeline.yml index 00e952b4e50..31a22510e42 100644 --- a/.azure-pipelines/integrated-pipeline.yml +++ b/.azure-pipelines/integrated-pipeline.yml @@ -81,7 +81,7 @@ stages: $kvSecretBytes = [System.Convert]::FromBase64String('$(MsGraphPSSDKCertificate)') $certCollection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection $certCollection.Import($kvSecretBytes,$null,[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable) - $store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My", "LocalMachine") + $store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My", "CurrentUser") $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) $store.AddRange($certCollection) $store.Close()