Skip to content

Commit ae5f6f7

Browse files
authored
Handle AzurePowerShellCredential not logged in scenarios on non-windows (Azure#26868)
* handle login issues on non-windows
1 parent c20c05a commit ae5f6f7

File tree

2 files changed

+44
-55
lines changed

2 files changed

+44
-55
lines changed

sdk/identity/Azure.Identity/src/AzurePowerShellCredential.cs

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
using System.IO;
99
using System.Runtime.InteropServices;
1010
using System.Text;
11-
using System.Text.Json;
1211
using System.Threading;
1312
using System.Threading.Tasks;
1413
using System.Xml.Linq;
@@ -30,21 +29,19 @@ public class AzurePowerShellCredential : TokenCredential
3029
private const string Troubleshooting = "See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/powershellcredential/troubleshoot";
3130
private const string AzurePowerShellFailedError = "Azure PowerShell authentication failed due to an unknown error. " + Troubleshooting;
3231
private const string AzurePowerShellTimeoutError = "Azure PowerShell authentication timed out.";
33-
internal const string AzurePowerShellNotLogInError = "Please run 'Connect-AzAccount' to set up account.";
34-
internal const string AzurePowerShellModuleNotInstalledError = "Az.Account module >= 2.2.0 is not installed.";
35-
internal const string PowerShellNotInstalledError = "PowerShell is not installed.";
36-
32+
private const string RunConnectAzAccountToLogin = "Run Connect-AzAccount to login";
33+
private const string NoAccountsWereFoundInTheCache = "No accounts were found in the cache";
34+
private const string CannotRetrieveAccessToken = "cannot retrieve access token";
3735
private const string AzurePowerShellNoAzAccountModule = "NoAzAccountModule";
3836
private static readonly string DefaultWorkingDirWindows = Environment.GetFolderPath(Environment.SpecialFolder.System);
3937
private const string DefaultWorkingDirNonWindows = "/bin/";
40-
41-
private static readonly string DefaultWorkingDir =
42-
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? DefaultWorkingDirWindows : DefaultWorkingDirNonWindows;
43-
38+
private static readonly string DefaultWorkingDir = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? DefaultWorkingDirWindows : DefaultWorkingDirNonWindows;
4439
private readonly string _tenantId;
45-
4640
private const int ERROR_FILE_NOT_FOUND = 2;
4741
private readonly bool _logPII;
42+
internal const string AzurePowerShellNotLogInError = "Please run 'Connect-AzAccount' to set up account.";
43+
internal const string AzurePowerShellModuleNotInstalledError = "Az.Account module >= 2.2.0 is not installed.";
44+
internal const string PowerShellNotInstalledError = "PowerShell is not installed.";
4845

4946
/// <summary>
5047
/// Creates a new instance of the <see cref="AzurePowerShellCredential"/>.
@@ -139,35 +136,28 @@ private async ValueTask<AccessToken> RequestAzurePowerShellAccessTokenAsync(bool
139136
{
140137
output = async ? await processRunner.RunAsync().ConfigureAwait(false) : processRunner.Run();
141138
CheckForErrors(output);
139+
ValidateResult(output);
142140
}
143141
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
144142
{
145143
throw new AuthenticationFailedException(AzurePowerShellTimeoutError);
146144
}
147145
catch (InvalidOperationException exception)
148146
{
149-
bool noPowerShell = exception.Message.IndexOf("not found", StringComparison.OrdinalIgnoreCase) != -1 ||
150-
exception.Message.IndexOf("is not recognized", StringComparison.OrdinalIgnoreCase) != -1;
151-
152-
if (noPowerShell)
153-
{
154-
throw new CredentialUnavailableException(PowerShellNotInstalledError);
155-
}
156-
157-
bool noLogin = exception.Message.IndexOf("Run Connect-AzAccount to login", StringComparison.OrdinalIgnoreCase) != -1;
158-
159-
if (noLogin)
160-
{
161-
throw new CredentialUnavailableException(AzurePowerShellNotLogInError);
162-
}
163-
147+
CheckForErrors(exception.Message);
164148
throw new AuthenticationFailedException($"{AzurePowerShellFailedError} {exception.Message}");
165149
}
166150
return DeserializeOutput(output);
167151
}
168152

169153
private static void CheckForErrors(string output)
170154
{
155+
bool noPowerShell = output.IndexOf("not found", StringComparison.OrdinalIgnoreCase) != -1 ||
156+
output.IndexOf("is not recognized", StringComparison.OrdinalIgnoreCase) != -1;
157+
if (noPowerShell)
158+
{
159+
throw new CredentialUnavailableException(PowerShellNotInstalledError);
160+
}
171161
if (output.IndexOf(AzurePowerShellNoAzAccountModule, StringComparison.OrdinalIgnoreCase) != -1)
172162
{
173163
throw new CredentialUnavailableException(AzurePowerShellModuleNotInstalledError);
@@ -176,6 +166,18 @@ private static void CheckForErrors(string output)
176166
{
177167
throw new Win32Exception(ERROR_FILE_NOT_FOUND);
178168
}
169+
170+
var needsLogin = output.IndexOf(RunConnectAzAccountToLogin, StringComparison.OrdinalIgnoreCase) != -1 ||
171+
output.IndexOf(NoAccountsWereFoundInTheCache, StringComparison.OrdinalIgnoreCase) != -1 ||
172+
output.IndexOf(CannotRetrieveAccessToken, StringComparison.OrdinalIgnoreCase) != -1;
173+
if (needsLogin)
174+
{
175+
throw new CredentialUnavailableException(AzurePowerShellNotLogInError);
176+
}
177+
}
178+
179+
private static void ValidateResult(string output)
180+
{
179181
if (output.IndexOf("Microsoft.Azure.Commands.Profile.Models.PSAccessToken", StringComparison.OrdinalIgnoreCase) < 0)
180182
{
181183
throw new CredentialUnavailableException("PowerShell did not return a valid response.");

sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs

Lines changed: 17 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
using System;
5+
using System.Collections.Generic;
56
using System.ComponentModel;
67
using System.Diagnostics;
78
using System.Globalization;
@@ -61,16 +62,27 @@ public async Task AuthenticateWithAzurePowerShellCredential(
6162
}
6263
}
6364

65+
private static IEnumerable<object[]> ErrorScenarios()
66+
{
67+
yield return new object[] { "'pwsh' is not recognized", AzurePowerShellCredential.PowerShellNotInstalledError };
68+
yield return new object[] { "pwsh: command not found", AzurePowerShellCredential.PowerShellNotInstalledError };
69+
yield return new object[] { "pwsh: not found", AzurePowerShellCredential.PowerShellNotInstalledError };
70+
yield return new object[] { "Run Connect-AzAccount to login", AzurePowerShellCredential.AzurePowerShellNotLogInError };
71+
yield return new object[] { "NoAzAccountModule", AzurePowerShellCredential.AzurePowerShellModuleNotInstalledError };
72+
yield return new object[] { "Get-AzAccessToken: Run Connect-AzAccount to login.", AzurePowerShellCredential.AzurePowerShellNotLogInError };
73+
yield return new object[] { "No accounts were found in the cache", AzurePowerShellCredential.AzurePowerShellNotLogInError };
74+
yield return new object[] { "cannot retrieve access token", AzurePowerShellCredential.AzurePowerShellNotLogInError };
75+
}
76+
6477
[Test]
65-
public void AuthenticateWithAzurePowerShellCredential_PwshNotInstalled(
66-
[Values("'pwsh' is not recognized", "pwsh: command not found", "pwsh: not found")]
67-
string errorMessage)
78+
[TestCaseSource(nameof(ErrorScenarios))]
79+
public void AuthenticateWithAzurePowerShellCredential_ErrorScenarios(string errorMessage, string expectedError)
6880
{
6981
var testProcess = new TestProcess { Error = errorMessage };
7082
AzurePowerShellCredential credential = InstrumentClient(
7183
new AzurePowerShellCredential(new AzurePowerShellCredentialOptions(), CredentialPipeline.GetInstance(null), new TestProcessService(testProcess)));
7284
var ex = Assert.ThrowsAsync<CredentialUnavailableException>(async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default)));
73-
Assert.AreEqual(AzurePowerShellCredential.PowerShellNotInstalledError, ex.Message);
85+
Assert.AreEqual(expectedError, ex.Message);
7486
}
7587

7688
[Test]
@@ -82,10 +94,7 @@ public async Task HandlesAlternateDateTimeFormats([Values("en-US", "nl-NL")] str
8294
{
8395
var (expectedToken, expectedExpiresOn, processOutput) = CredentialTestHelpers.CreateTokenForAzurePowerShell(TimeSpan.FromSeconds(30));
8496
TestContext.WriteLine(processOutput);
85-
var testProcess = new TestProcess
86-
{
87-
Output = processOutput,
88-
};
97+
var testProcess = new TestProcess { Output = processOutput, };
8998
AzurePowerShellCredential credential = InstrumentClient(
9099
new AzurePowerShellCredential(
91100
new AzurePowerShellCredentialOptions(),
@@ -138,28 +147,6 @@ public void AuthenticateWithAzurePowerShellCredential_PowerShellNotInstalled(
138147
Assert.AreEqual(AzurePowerShellCredential.PowerShellNotInstalledError, ex.Message);
139148
}
140149

141-
[Test]
142-
public void AuthenticateWithAzurePowerShellCredential_RunConnectAzAccount(
143-
[Values("Run Connect-AzAccount to login")]
144-
string errorMessage)
145-
{
146-
var testProcess = new TestProcess { Error = errorMessage };
147-
AzurePowerShellCredential credential = InstrumentClient(
148-
new AzurePowerShellCredential(new AzurePowerShellCredentialOptions(), CredentialPipeline.GetInstance(null), new TestProcessService(testProcess)));
149-
var ex = Assert.ThrowsAsync<CredentialUnavailableException>(async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default)));
150-
Assert.AreEqual(AzurePowerShellCredential.AzurePowerShellNotLogInError, ex.Message);
151-
}
152-
153-
[Test]
154-
public void AuthenticateWithAzurePowerShellCredential_AzurePowerShellModuleNotInstalled([Values("NoAzAccountModule")] string message)
155-
{
156-
var testProcess = new TestProcess { Output = message };
157-
AzurePowerShellCredential credential = InstrumentClient(
158-
new AzurePowerShellCredential(new AzurePowerShellCredentialOptions(), CredentialPipeline.GetInstance(null), new TestProcessService(testProcess)));
159-
var ex = Assert.ThrowsAsync<CredentialUnavailableException>(async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default)));
160-
Assert.AreEqual(AzurePowerShellCredential.AzurePowerShellModuleNotInstalledError, ex.Message);
161-
}
162-
163150
[Test]
164151
public void AuthenticateWithAzurePowerShellCredential_AzurePowerShellUnknownError()
165152
{

0 commit comments

Comments
 (0)