diff --git a/sdk/identity/Azure.Identity/CHANGELOG.md b/sdk/identity/Azure.Identity/CHANGELOG.md index 2a7dbd016cfba..c8ab91606a862 100644 --- a/sdk/identity/Azure.Identity/CHANGELOG.md +++ b/sdk/identity/Azure.Identity/CHANGELOG.md @@ -7,7 +7,8 @@ ### Breaking Changes ### Bugs Fixed - - Fixed an issue where setting `DefaultAzureCredentialOptions.TenantId` twice throws an `InvalidOperationException` ([#47035](https://github.com/Azure/azure-sdk-for-net/issues/47035)) + - Fixed an issue where setting `DefaultAzureCredentialOptions.TenantId` twice throws an `InvalidOperationException`. ([#47035](https://github.com/Azure/azure-sdk-for-net/issues/47035)) + - Fixed an issue where some credentials in `DefaultAzureCredential` would not fall through to the next credential in the chain under certain exception conditions. ### Other Changes diff --git a/sdk/identity/Azure.Identity/src/CredentialDiagnosticScope.cs b/sdk/identity/Azure.Identity/src/CredentialDiagnosticScope.cs index e2759e86af298..3f97b935f9ea0 100644 --- a/sdk/identity/Azure.Identity/src/CredentialDiagnosticScope.cs +++ b/sdk/identity/Azure.Identity/src/CredentialDiagnosticScope.cs @@ -58,7 +58,7 @@ private void RegisterFailed(Exception ex) private bool TryWrapException(ref Exception exception, string additionalMessageText = null, bool isCredentialUnavailable = false) { - if (exception is OperationCanceledException || exception is AuthenticationFailedException) + if (!isCredentialUnavailable && (exception is OperationCanceledException || exception is AuthenticationFailedException || exception is CredentialUnavailableException)) { return false; } diff --git a/sdk/identity/Azure.Identity/src/Credentials/AzureCliCredential.cs b/sdk/identity/Azure.Identity/src/Credentials/AzureCliCredential.cs index 6ac687b003330..1cd55c18c11ab 100644 --- a/sdk/identity/Azure.Identity/src/Credentials/AzureCliCredential.cs +++ b/sdk/identity/Azure.Identity/src/Credentials/AzureCliCredential.cs @@ -116,7 +116,7 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC } catch (Exception e) { - throw scope.FailWrapAndThrow(e); + throw scope.FailWrapAndThrow(e, isCredentialUnavailable: _isChainedCredential); } } diff --git a/sdk/identity/Azure.Identity/src/Credentials/AzureDeveloperCliCredential.cs b/sdk/identity/Azure.Identity/src/Credentials/AzureDeveloperCliCredential.cs index c8bbb86b94487..cbc554372d40c 100644 --- a/sdk/identity/Azure.Identity/src/Credentials/AzureDeveloperCliCredential.cs +++ b/sdk/identity/Azure.Identity/src/Credentials/AzureDeveloperCliCredential.cs @@ -108,7 +108,7 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC } catch (Exception e) { - throw scope.FailWrapAndThrow(e); + throw scope.FailWrapAndThrow(e, isCredentialUnavailable: _isChainedCredential); } } diff --git a/sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs b/sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs index 23184ec20b6ac..d893f19049e04 100644 --- a/sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs @@ -113,24 +113,52 @@ public static IEnumerable AzureCliExceptionScenarios() { // params // az thrown Exception message, expected message, expected exception - yield return new object[] { AzureCliCredential.WinAzureCLIError, AzureCliCredential.AzureCLINotInstalled, typeof(CredentialUnavailableException) }; - yield return new object[] { "az: command not found", AzureCliCredential.AzureCLINotInstalled, typeof(CredentialUnavailableException) }; - yield return new object[] { "az: not found", AzureCliCredential.AzureCLINotInstalled, typeof(CredentialUnavailableException) }; - yield return new object[] { AzureCliCredential.AzNotLogIn, AzureCliCredential.AzNotLogIn, typeof(CredentialUnavailableException) }; - yield return new object[] { RefreshTokenExpiredError, AzureCliCredential.InteractiveLoginRequired, typeof(CredentialUnavailableException) }; - yield return new object[] { AzureCliCredential.CLIInternalError, AzureCliCredential.InteractiveLoginRequired, typeof(CredentialUnavailableException) }; - yield return new object[] { "random unknown exception", AzureCliCredential.AzureCliFailedError + " " + AzureCliCredential.Troubleshoot + " random unknown exception", typeof(AuthenticationFailedException) }; - yield return new object[] { "AADSTS12345: Some AAD error. To re-authenticate, please run: az login", AzureCliCredential.AzureCliFailedError + " " + AzureCliCredential.Troubleshoot + " AADSTS12345: Some AAD error. To re-authenticate, please run: az login", typeof(AuthenticationFailedException) }; + yield return new object[] { null, AzureCliCredential.WinAzureCLIError, AzureCliCredential.AzureCLINotInstalled, typeof(CredentialUnavailableException) }; + yield return new object[] { null, "az: command not found", AzureCliCredential.AzureCLINotInstalled, typeof(CredentialUnavailableException) }; + yield return new object[] { null, "az: not found", AzureCliCredential.AzureCLINotInstalled, typeof(CredentialUnavailableException) }; + yield return new object[] { null, AzureCliCredential.AzNotLogIn, AzureCliCredential.AzNotLogIn, typeof(CredentialUnavailableException) }; + yield return new object[] { null, RefreshTokenExpiredError, AzureCliCredential.InteractiveLoginRequired, typeof(CredentialUnavailableException) }; + yield return new object[] { null, AzureCliCredential.CLIInternalError, AzureCliCredential.InteractiveLoginRequired, typeof(CredentialUnavailableException) }; + yield return new object[] { null, "random unknown exception", AzureCliCredential.AzureCliFailedError + " " + AzureCliCredential.Troubleshoot + " random unknown exception", typeof(AuthenticationFailedException) }; + yield return new object[] { GetExceptionAction(new AuthenticationFailedException("foo")), string.Empty, "foo", typeof(AuthenticationFailedException) }; + yield return new object[] { GetExceptionAction(new OperationCanceledException("foo")), string.Empty, "Azure CLI authentication timed out.", typeof(AuthenticationFailedException) }; + yield return new object[] { null, "AADSTS12345: Some AAD error. To re-authenticate, please run: az login", AzureCliCredential.AzureCliFailedError + " " + AzureCliCredential.Troubleshoot + " AADSTS12345: Some AAD error. To re-authenticate, please run: az login", typeof(AuthenticationFailedException) }; + } + + public static IEnumerable AzureCliExceptionScenarios_IsChained() + { + // params + // az thrown Exception message, expected message, expected exception + yield return new object[] { null, AzureCliCredential.WinAzureCLIError, AzureCliCredential.AzureCLINotInstalled, typeof(CredentialUnavailableException) }; + yield return new object[] { null, "az: command not found", AzureCliCredential.AzureCLINotInstalled, typeof(CredentialUnavailableException) }; + yield return new object[] { null, "az: not found", AzureCliCredential.AzureCLINotInstalled, typeof(CredentialUnavailableException) }; + yield return new object[] { null, AzureCliCredential.AzNotLogIn, AzureCliCredential.AzNotLogIn, typeof(CredentialUnavailableException) }; + yield return new object[] { null, RefreshTokenExpiredError, AzureCliCredential.InteractiveLoginRequired, typeof(CredentialUnavailableException) }; + yield return new object[] { null, AzureCliCredential.CLIInternalError, AzureCliCredential.InteractiveLoginRequired, typeof(CredentialUnavailableException) }; + yield return new object[] { null, "random unknown exception", AzureCliCredential.AzureCliFailedError + " " + AzureCliCredential.Troubleshoot + " random unknown exception", typeof(CredentialUnavailableException) }; + yield return new object[] { GetExceptionAction(new AuthenticationFailedException("foo")), string.Empty, "foo", typeof(CredentialUnavailableException) }; + yield return new object[] { GetExceptionAction(new OperationCanceledException("foo")), string.Empty, "Azure CLI authentication timed out.", typeof(CredentialUnavailableException) }; + yield return new object[] { null, "AADSTS12345: Some AAD error. To re-authenticate, please run: az login", AzureCliCredential.AzureCliFailedError + " " + AzureCliCredential.Troubleshoot + " AADSTS12345: Some AAD error. To re-authenticate, please run: az login", typeof(CredentialUnavailableException) }; } [Test] [TestCaseSource(nameof(AzureCliExceptionScenarios))] - public void AuthenticateWithCliCredential_ExceptionScenarios(string errorMessage, string expectedMessage, Type exceptionType) + public void AuthenticateWithCliCredential_ExceptionScenarios(Action exceptionOnStartHandler, string errorMessage, string expectedMessage, Type exceptionType) { - var testProcess = new TestProcess { Error = errorMessage }; + var testProcess = new TestProcess { Error = errorMessage, ExceptionOnStartHandler = exceptionOnStartHandler }; AzureCliCredential credential = InstrumentClient(new AzureCliCredential(CredentialPipeline.GetInstance(null), new TestProcessService(testProcess))); var ex = Assert.ThrowsAsync(exceptionType, async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default))); - Assert.AreEqual(expectedMessage, ex.Message); + Assert.That(ex.Message, Does.Contain(expectedMessage)); + } + + [Test] + [TestCaseSource(nameof(AzureCliExceptionScenarios_IsChained))] + public void AuthenticateWithCliCredential_ExceptionScenarios_IsChained(Action exceptionOnStartHandler, string errorMessage, string expectedMessage, Type exceptionType) + { + var testProcess = new TestProcess { Error = errorMessage, ExceptionOnStartHandler = exceptionOnStartHandler }; + AzureCliCredential credential = InstrumentClient(new AzureCliCredential(CredentialPipeline.GetInstance(null), new TestProcessService(testProcess), new AzureCliCredentialOptions() { IsChainedCredential = true })); + var ex = Assert.ThrowsAsync(exceptionType, async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default))); + Assert.That(ex.Message, Does.Contain(expectedMessage)); } [Test] @@ -161,7 +189,7 @@ public void ConfigureCliProcessTimeout_ProcessTimeout([Values(true, false)] bool { ex = Assert.ThrowsAsync(async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default))); } - Assert.AreEqual(AzureCliCredential.AzureCliTimeoutError, ex.Message); + Assert.That(ex.Message, Does.Contain(AzureCliCredential.AzureCliTimeoutError)); } [TestCaseSource(nameof(NegativeTestCharacters))] diff --git a/sdk/identity/Azure.Identity/tests/AzureDeveloperCliCredentialTests.cs b/sdk/identity/Azure.Identity/tests/AzureDeveloperCliCredentialTests.cs index 34e6feb4d72bb..303ca5a7d0004 100644 --- a/sdk/identity/Azure.Identity/tests/AzureDeveloperCliCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/AzureDeveloperCliCredentialTests.cs @@ -87,26 +87,57 @@ public static IEnumerable AzureDeveloperCliExceptionScenarios() { // params // azd thrown Exception message, expected message, expected exception - yield return new object[] { AzureDeveloperCliCredential.WinAzdCliError, AzureDeveloperCliCredential.AzdCliNotInstalled, typeof(CredentialUnavailableException) }; - yield return new object[] { "azd: command not found", AzureDeveloperCliCredential.AzdCliNotInstalled, typeof(CredentialUnavailableException) }; - yield return new object[] { "azd: not found", AzureDeveloperCliCredential.AzdCliNotInstalled, typeof(CredentialUnavailableException) }; - yield return new object[] { AzureDeveloperCliCredential.AzdNotLogIn, AzureDeveloperCliCredential.AzdNotLogIn, typeof(CredentialUnavailableException) }; - yield return new object[] { RefreshTokenExpiredError, AzureDeveloperCliCredential.InteractiveLoginRequired, typeof(CredentialUnavailableException) }; - yield return new object[] { AzureDeveloperCliCredential.AzdCLIInternalError, AzureDeveloperCliCredential.InteractiveLoginRequired, typeof(CredentialUnavailableException) }; - yield return new object[] { "random unknown exception", AzureDeveloperCliCredential.AzdCliFailedError + " " + AzureDeveloperCliCredential.Troubleshoot + " random unknown exception", typeof(AuthenticationFailedException) }; - yield return new object[] { "AADSTS12345: Some AAD error. To re-authenticate, please run: azd auth login", AzureDeveloperCliCredential.AzdCliFailedError + " " + AzureDeveloperCliCredential.Troubleshoot + " AADSTS12345: Some AAD error. To re-authenticate, please run: azd auth login", typeof(AuthenticationFailedException) }; + yield return new object[] {null, AzureDeveloperCliCredential.WinAzdCliError, AzureDeveloperCliCredential.AzdCliNotInstalled, typeof(CredentialUnavailableException) }; + yield return new object[] {null, "azd: command not found", AzureDeveloperCliCredential.AzdCliNotInstalled, typeof(CredentialUnavailableException) }; + yield return new object[] {null, "azd: not found", AzureDeveloperCliCredential.AzdCliNotInstalled, typeof(CredentialUnavailableException) }; + yield return new object[] {null, AzureDeveloperCliCredential.AzdNotLogIn, AzureDeveloperCliCredential.AzdNotLogIn, typeof(CredentialUnavailableException) }; + yield return new object[] {null, RefreshTokenExpiredError, AzureDeveloperCliCredential.InteractiveLoginRequired, typeof(CredentialUnavailableException) }; + yield return new object[] {null, AzureDeveloperCliCredential.AzdCLIInternalError, AzureDeveloperCliCredential.InteractiveLoginRequired, typeof(CredentialUnavailableException) }; + yield return new object[] {null, "random unknown exception", AzureDeveloperCliCredential.AzdCliFailedError + " " + AzureDeveloperCliCredential.Troubleshoot + " random unknown exception", typeof(AuthenticationFailedException) }; + yield return new object[] {GetExceptionAction(new AuthenticationFailedException("foo")), string.Empty, "foo", typeof(AuthenticationFailedException) }; + yield return new object[] {GetExceptionAction(new OperationCanceledException("foo")), string.Empty, "Azure Developer CLI authentication timed out.", typeof(AuthenticationFailedException) }; + yield return new object[] {null, "AADSTS12345: Some AAD error. To re-authenticate, please run: azd auth login", AzureDeveloperCliCredential.AzdCliFailedError + " " + AzureDeveloperCliCredential.Troubleshoot + " AADSTS12345: Some AAD error. To re-authenticate, please run: azd auth login", typeof(AuthenticationFailedException) }; + } + + public static IEnumerable AzureDeveloperCliExceptionScenarios_IsChained() + { + // params + // azd thrown Exception message, expected message, expected exception + yield return new object[] {null, AzureDeveloperCliCredential.WinAzdCliError, AzureDeveloperCliCredential.AzdCliNotInstalled, typeof(CredentialUnavailableException) }; + yield return new object[] {null, "azd: command not found", AzureDeveloperCliCredential.AzdCliNotInstalled, typeof(CredentialUnavailableException) }; + yield return new object[] {null, "azd: not found", AzureDeveloperCliCredential.AzdCliNotInstalled, typeof(CredentialUnavailableException) }; + yield return new object[] {null, AzureDeveloperCliCredential.AzdNotLogIn, AzureDeveloperCliCredential.AzdNotLogIn, typeof(CredentialUnavailableException) }; + yield return new object[] {null, RefreshTokenExpiredError, AzureDeveloperCliCredential.InteractiveLoginRequired, typeof(CredentialUnavailableException) }; + yield return new object[] {null, AzureDeveloperCliCredential.AzdCLIInternalError, AzureDeveloperCliCredential.InteractiveLoginRequired, typeof(CredentialUnavailableException) }; + yield return new object[] {null, "random unknown exception", AzureDeveloperCliCredential.AzdCliFailedError + " " + AzureDeveloperCliCredential.Troubleshoot + " random unknown exception", typeof(CredentialUnavailableException) }; + yield return new object[] {GetExceptionAction(new AuthenticationFailedException("foo")), string.Empty, "foo", typeof(CredentialUnavailableException) }; + yield return new object[] {GetExceptionAction(new OperationCanceledException("foo")), string.Empty, "Azure Developer CLI authentication timed out.", typeof(CredentialUnavailableException) }; + yield return new object[] {null, "AADSTS12345: Some AAD error. To re-authenticate, please run: azd auth login", AzureDeveloperCliCredential.AzdCliFailedError + " " + AzureDeveloperCliCredential.Troubleshoot + " AADSTS12345: Some AAD error. To re-authenticate, please run: azd auth login", typeof(CredentialUnavailableException) }; } [Test] [TestCaseSource(nameof(AzureDeveloperCliExceptionScenarios))] - public void AuthenticateWithDeveloperCliCredential_ExceptionScenarios(string errorMessage, string expectedMessage, Type exceptionType) + public void AuthenticateWithDeveloperCliCredential_ExceptionScenarios(Action exceptionOnStartHandler, string errorMessage, string expectedMessage, Type exceptionType) { - var testProcess = new TestProcess { Error = errorMessage }; + var testProcess = new TestProcess { Error = errorMessage, ExceptionOnStartHandler = exceptionOnStartHandler }; AzureDeveloperCliCredential credential = InstrumentClient(new AzureDeveloperCliCredential(CredentialPipeline.GetInstance(null), new TestProcessService(testProcess))); var ex = Assert.ThrowsAsync(exceptionType, async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default))); Assert.AreEqual(expectedMessage, ex.Message); } + [Test] + [TestCaseSource(nameof(AzureDeveloperCliExceptionScenarios_IsChained))] + public void AuthenticateWithDeveloperCliCredential_ExceptionScenarios_IsChained(Action exceptionOnStartHandler, string errorMessage, string expectedMessage, Type exceptionType) + { + var testProcess = new TestProcess { Error = errorMessage, ExceptionOnStartHandler = exceptionOnStartHandler }; + AzureDeveloperCliCredential credential = InstrumentClient(new AzureDeveloperCliCredential( + CredentialPipeline.GetInstance(null), + new TestProcessService(testProcess), + new AzureDeveloperCliCredentialOptions() { IsChainedCredential = true })); + var ex = Assert.ThrowsAsync(exceptionType, async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default))); + Assert.That(ex.Message, Does.Contain(expectedMessage)); + } + [Test] public void AuthenticateWithDeveloperCliCredential_CanceledByUser() { @@ -134,7 +165,7 @@ public void ConfigureCliProcessTimeout_ProcessTimeout([Values(true, false)] bool { ex = Assert.ThrowsAsync(async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default))); } - Assert.AreEqual(AzureDeveloperCliCredential.AzdCliTimeoutError, ex.Message); + Assert.That(ex.Message, Does.Contain(AzureDeveloperCliCredential.AzdCliTimeoutError)); } [TestCaseSource(nameof(NegativeTestCharacters))] diff --git a/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs b/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs index 885c6ce2ffb31..a1e47101ce788 100644 --- a/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs +++ b/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs @@ -89,27 +89,58 @@ public async Task AuthenticateWithAzurePowerShellCredential( private static IEnumerable ErrorScenarios() { - yield return new object[] { "Run Connect-AzAccount to login", AzurePowerShellCredential.AzurePowerShellNotLogInError, typeof(CredentialUnavailableException) }; - yield return new object[] { "NoAzAccountModule", AzurePowerShellCredential.AzurePowerShellModuleNotInstalledError, typeof(CredentialUnavailableException) }; - yield return new object[] { "Get-AzAccessToken: Run Connect-AzAccount to login.", AzurePowerShellCredential.AzurePowerShellNotLogInError, typeof(CredentialUnavailableException) }; - yield return new object[] { "No accounts were found in the cache", AzurePowerShellCredential.AzurePowerShellNotLogInError, typeof(CredentialUnavailableException) }; - yield return new object[] { "cannot retrieve access token", AzurePowerShellCredential.AzurePowerShellNotLogInError, typeof(CredentialUnavailableException) }; - yield return new object[] { "Some random exception", AzurePowerShellCredential.AzurePowerShellFailedError + " Some random exception", typeof(AuthenticationFailedException) }; + yield return new object[] { null, "Run Connect-AzAccount to login", AzurePowerShellCredential.AzurePowerShellNotLogInError, typeof(CredentialUnavailableException) }; + yield return new object[] { null, "NoAzAccountModule", AzurePowerShellCredential.AzurePowerShellModuleNotInstalledError, typeof(CredentialUnavailableException) }; + yield return new object[] { null, "Get-AzAccessToken: Run Connect-AzAccount to login.", AzurePowerShellCredential.AzurePowerShellNotLogInError, typeof(CredentialUnavailableException) }; + yield return new object[] { null, "No accounts were found in the cache", AzurePowerShellCredential.AzurePowerShellNotLogInError, typeof(CredentialUnavailableException) }; + yield return new object[] { null, "cannot retrieve access token", AzurePowerShellCredential.AzurePowerShellNotLogInError, typeof(CredentialUnavailableException) }; + yield return new object[] { null, "Some random exception", AzurePowerShellCredential.AzurePowerShellFailedError + " Some random exception", typeof(AuthenticationFailedException) }; + yield return new object[] { GetExceptionAction(new AuthenticationFailedException("foo")), string.Empty, "foo", typeof(AuthenticationFailedException) }; + yield return new object[] { GetExceptionAction(new OperationCanceledException("foo")), string.Empty, "Azure PowerShell authentication timed out.", typeof(AuthenticationFailedException) }; yield return new object[] { + null, "AADSTS500011: The resource principal named was not found in the tenant named", AzurePowerShellCredential.AzurePowerShellFailedError + " AADSTS500011: The resource principal named was not found in the tenant named", typeof(AuthenticationFailedException) }; } + private static IEnumerable ErrorScenarios_IsChained() + { + yield return new object[] { null, "Run Connect-AzAccount to login", AzurePowerShellCredential.AzurePowerShellNotLogInError, typeof(CredentialUnavailableException) }; + yield return new object[] { null, "NoAzAccountModule", AzurePowerShellCredential.AzurePowerShellModuleNotInstalledError, typeof(CredentialUnavailableException) }; + yield return new object[] { null, "Get-AzAccessToken: Run Connect-AzAccount to login.", AzurePowerShellCredential.AzurePowerShellNotLogInError, typeof(CredentialUnavailableException) }; + yield return new object[] { null, "No accounts were found in the cache", AzurePowerShellCredential.AzurePowerShellNotLogInError, typeof(CredentialUnavailableException) }; + yield return new object[] { null, "cannot retrieve access token", AzurePowerShellCredential.AzurePowerShellNotLogInError, typeof(CredentialUnavailableException) }; + yield return new object[] { null, "Some random exception", AzurePowerShellCredential.AzurePowerShellFailedError + " Some random exception", typeof(CredentialUnavailableException) }; + yield return new object[] { GetExceptionAction(new AuthenticationFailedException("foo")), string.Empty, "foo", typeof(CredentialUnavailableException) }; + yield return new object[] { GetExceptionAction(new OperationCanceledException("foo")), string.Empty, "Azure PowerShell authentication timed out.", typeof(CredentialUnavailableException) }; + yield return new object[] { + null, + "AADSTS500011: The resource principal named was not found in the tenant named", + AzurePowerShellCredential.AzurePowerShellFailedError + " AADSTS500011: The resource principal named was not found in the tenant named", + typeof(CredentialUnavailableException) }; + } + [Test] [TestCaseSource(nameof(ErrorScenarios))] - public void AuthenticateWithAzurePowerShellCredential_ErrorScenarios(string errorMessage, string expectedError, Type expectedException) + public void AuthenticateWithAzurePowerShellCredential_ErrorScenarios(Action exceptionOnStartHandler, string errorMessage, string expectedError, Type expectedException) { - var testProcess = new TestProcess { Error = errorMessage }; + var testProcess = new TestProcess { Error = errorMessage, ExceptionOnStartHandler = exceptionOnStartHandler }; AzurePowerShellCredential credential = InstrumentClient( new AzurePowerShellCredential(new AzurePowerShellCredentialOptions(), CredentialPipeline.GetInstance(null), new TestProcessService(testProcess))); var ex = Assert.ThrowsAsync(expectedException, async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default))); - Assert.AreEqual(expectedError, ex.Message); + Assert.That(ex.Message, Does.Contain(expectedError)); + } + + [Test] + [TestCaseSource(nameof(ErrorScenarios_IsChained))] + public void AuthenticateWithAzurePowerShellCredential_ErrorScenarios_IsChained(Action exceptionOnStartHandler, string errorMessage, string expectedError, Type expectedException) + { + var testProcess = new TestProcess { Error = errorMessage, ExceptionOnStartHandler = exceptionOnStartHandler }; + AzurePowerShellCredential credential = InstrumentClient( + new AzurePowerShellCredential(new AzurePowerShellCredentialOptions() { IsChainedCredential = true }, CredentialPipeline.GetInstance(null), new TestProcessService(testProcess))); + var ex = Assert.ThrowsAsync(expectedException, async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default))); + Assert.That(ex.Message, Does.Contain(expectedError)); } /// diff --git a/sdk/identity/Azure.Identity/tests/CredentialTestBase.cs b/sdk/identity/Azure.Identity/tests/CredentialTestBase.cs index 8460bb24025df..899746f6c9fbc 100644 --- a/sdk/identity/Azure.Identity/tests/CredentialTestBase.cs +++ b/sdk/identity/Azure.Identity/tests/CredentialTestBase.cs @@ -857,6 +857,11 @@ protected async Task ReadMockRequestContent(MockRequest request) } } + public static Action GetExceptionAction(Exception exceptionToThrow) + { + return (p) => throw exceptionToThrow; + } + public class CommonCredentialTestConfig : TokenCredentialOptions, ISupportsAdditionallyAllowedTenants, ISupportsDisableInstanceDiscovery { public bool DisableInstanceDiscovery { get; set; } diff --git a/sdk/identity/Azure.Identity/tests/VisualStudioCredentialTests.cs b/sdk/identity/Azure.Identity/tests/VisualStudioCredentialTests.cs index 19ad6c81cd1eb..8ac12300d5a82 100644 --- a/sdk/identity/Azure.Identity/tests/VisualStudioCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/VisualStudioCredentialTests.cs @@ -308,5 +308,35 @@ public void GenericException_throws_CredentialUnavailableException_WhenChained([ async () => await credential.GetTokenAsync(new TokenRequestContext(new[] { "https://vault.azure.net/" }), CancellationToken.None)); } } + + [Test] + public void AuthenticationFailedException_throws_CredentialUnavailableException_WhenChained([Values(true, false)] bool isChainedCredential) + { + var testProcess = new TestProcess() { ExceptionOnStartHandler = p => throw new AuthenticationFailedException("Test exception") }; + var fileSystem = CredentialTestHelpers.CreateFileSystemForVisualStudio(); + var credential = InstrumentClient(new VisualStudioCredential(default, default, fileSystem, new TestProcessService(testProcess), new VisualStudioCredentialOptions { IsChainedCredential = isChainedCredential })); + + if (isChainedCredential) + { + Assert.ThrowsAsync( + async () => await credential.GetTokenAsync(new TokenRequestContext(new[] { "https://vault.azure.net/" }), CancellationToken.None)); + } + else + { + Assert.ThrowsAsync( + async () => await credential.GetTokenAsync(new TokenRequestContext(new[] { "https://vault.azure.net/" }), CancellationToken.None)); + } + } + + [Test] + public void OperationCanceledException_throws_CredentialUnavailableException_WhenChained() + { + var testProcess = new TestProcess() { ExceptionOnStartHandler = p => throw new OperationCanceledException("Test exception") }; + var fileSystem = CredentialTestHelpers.CreateFileSystemForVisualStudio(); + var credential = InstrumentClient(new VisualStudioCredential(default, default, fileSystem, new TestProcessService(testProcess), new VisualStudioCredentialOptions { IsChainedCredential = true })); + + Assert.ThrowsAsync( + async () => await credential.GetTokenAsync(new TokenRequestContext(new[] { "https://vault.azure.net/" }), CancellationToken.None)); + } } }