1// Copyright (c) Microsoft Corporation. All rights reserved.2// Licensed under the MIT License.34using Azure.Core;5using System;6using System.Collections.Generic;7using System.Text;8using System.Threading;9using System.Threading.Tasks;10using Azure.Core.Pipeline;1112namespace Azure.Identity13{14 /// <summary>15 /// Provides a default <see cref="TokenCredential"/> authentication flow for applications that will be deployed to A16 /// types if enabled will be tried, in order:17 /// <list type="bullet">18 /// <item><description><see cref="EnvironmentCredential"/></description></item>19 /// <item><description><see cref="ManagedIdentityCredential"/></description></item>20 /// <item><description><see cref="SharedTokenCacheCredential"/></description></item>21 /// <item><description><see cref="VisualStudioCredential"/></description></item>22 /// <item><description><see cref="VisualStudioCodeCredential"/></description></item>23 /// <item><description><see cref="AzureCliCredential"/></description></item>24 /// <item><description><see cref="InteractiveBrowserCredential"/></description></item>25 /// </list>26 /// Consult the documentation of these credential types for more information on how they attempt authentication.27 /// </summary>28 /// <remarks>29 /// Note that credentials requiring user interaction, such as the <see cref="InteractiveBrowserCredential"/>, are no30 /// constructing the <see cref="DefaultAzureCredential"/> either by setting the includeInteractiveCredentials parame31 /// <see cref="DefaultAzureCredentialOptions.ExcludeInteractiveBrowserCredential"/> property to false when passing <32 /// </remarks>33 public class DefaultAzureCredential : TokenCredential34 {35 private const string DefaultExceptionMessage = "DefaultAzureCredential failed to retrieve a token from the inclu36 private const string UnhandledExceptionMessage = "DefaultAzureCredential authentication failed.";37 private static readonly TokenCredential[] s_defaultCredentialChain = GetDefaultAzureCredentialChain(new DefaultA3839 private readonly CredentialPipeline _pipeline;40 private readonly AsyncLockWithValue<TokenCredential> _credentialLock;4142 private TokenCredential[] _sources;4344 internal DefaultAzureCredential() : this(false) { }4546 /// <summary>47 /// Creates an instance of the DefaultAzureCredential class.48 /// </summary>49 /// <param name="includeInteractiveCredentials">Specifies whether credentials requiring user interaction will be50 public DefaultAzureCredential(bool includeInteractiveCredentials = false)51 : this(includeInteractiveCredentials ? new DefaultAzureCredentialOptions { ExcludeInteractiveBrowserCredenti52 {53 }5455 /// <summary>56 /// Creates an instance of the <see cref="DefaultAzureCredential"/> class.57 /// </summary>58 /// <param name="options">Options that configure the management of the requests sent to Azure Active Directory s59 public DefaultAzureCredential(DefaultAzureCredentialOptions options)60 : this(new DefaultAzureCredentialFactory(options), options)61 {62 }6364 internal DefaultAzureCredential(DefaultAzureCredentialFactory factory, DefaultAzureCredentialOptions options)65 {66 _pipeline = factory.Pipeline;67 _sources = GetDefaultAzureCredentialChain(factory, options);68 _credentialLock = new AsyncLockWithValue<TokenCredential>();69 }7071 /// <summary>72 /// Sequentially calls <see cref="TokenCredential.GetToken"/> on all the included credentials in the order <see 73 /// and <see cref="InteractiveBrowserCredential"/> returning the first successfully obtained <see cref="AccessTo74 /// </summary>75 /// <remarks>76 /// Note that credentials requiring user interaction, such as the <see cref="InteractiveBrowserCredential"/>, ar77 /// </remarks>78 /// <param name="requestContext">The details of the authentication request.</param>79 /// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>80 /// <returns>The first <see cref="AccessToken"/> returned by the specified sources. Any credential which raises 81 public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken = d82 {83 return GetTokenImplAsync(false, requestContext, cancellationToken).EnsureCompleted();84 }8586 /// <summary>87 /// Sequentially calls <see cref="TokenCredential.GetToken"/> on all the included credentials in the order <see 88 /// and <see cref="InteractiveBrowserCredential"/> returning the first successfully obtained <see cref="AccessTo89 /// </summary>90 /// <remarks>91 /// Note that credentials requiring user interaction, such as the <see cref="InteractiveBrowserCredential"/>, ar92 /// </remarks>93 /// <param name="requestContext">The details of the authentication request.</param>94 /// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>95 /// <returns>The first <see cref="AccessToken"/> returned by the specified sources. Any credential which raises 96 public override async ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken97 {98 return await GetTokenImplAsync(true, requestContext, cancellationToken).ConfigureAwait(false);99 }100101 private async ValueTask<AccessToken> GetTokenImplAsync(bool async, TokenRequestContext requestContext, Cancellat102 {103 using CredentialDiagnosticScope scope = _pipeline.StartGetTokenScopeGroup("DefaultAzureCredential.GetToken",104105 try106 {107 using var asyncLock = await _credentialLock.GetLockOrValueAsync(async, cancellationToken).ConfigureAwait108109 AccessToken token;110 if (asyncLock.HasValue)111 {112 token = await GetTokenFromCredentialAsync(asyncLock.Value, requestContext, async, cancellationToken)113 }114 else115 {116 TokenCredential credential;117 (token, credential) = await GetTokenFromSourcesAsync(_sources, requestContext, async, cancellationTo118 _sources = default;119 asyncLock.SetValue(credential);120 }121122 return scope.Succeeded(token);123 }124 catch (Exception e)125 {126 throw scope.FailWrapAndThrow(e);127 }128 }129130 private static async ValueTask<AccessToken> GetTokenFromCredentialAsync(TokenCredential credential, TokenRequest131 {132 try133 {134 return async135 ? await credential.GetTokenAsync(requestContext, cancellationToken).ConfigureAwait(false)136 : credential.GetToken(requestContext, cancellationToken);137 }138 catch (Exception e) when (!(e is CredentialUnavailableException))139 {140 throw new AuthenticationFailedException(UnhandledExceptionMessage, e);141 }142 }143144 private static async ValueTask<(AccessToken, TokenCredential)> GetTokenFromSourcesAsync(TokenCredential[] source145 {146 List<AuthenticationFailedException> exceptions = new List<AuthenticationFailedException>();147148 for (var i = 0; i < sources.Length && sources[i] != null; i++)149 {150 try151 {152 AccessToken token = async153 ? await sources[i].GetTokenAsync(requestContext, cancellationToken).ConfigureAwait(false)154 : sources[i].GetToken(requestContext, cancellationToken);155156 return (token, sources[i]);157 }158 catch (AuthenticationFailedException e)159 {160 exceptions.Add(e);161 }162 }163164 // Build the credential unavailable message, this code is only reachable if all credentials throw Authentica165 StringBuilder errorMsg = new StringBuilder(DefaultExceptionMessage);166167 bool allCredentialUnavailableException = true;168 foreach (AuthenticationFailedException ex in exceptions)169 {170 allCredentialUnavailableException &= ex is CredentialUnavailableException;171 errorMsg.Append(Environment.NewLine).Append("- ").Append(ex.Message);172 }173174 // If all credentials have thrown CredentialUnavailableException, throw CredentialUnavailableException,175 // otherwise throw AuthenticationFailedException176 throw allCredentialUnavailableException177 ? new CredentialUnavailableException(errorMsg.ToString())178 : new AuthenticationFailedException(errorMsg.ToString());179 }180181 private static TokenCredential[] GetDefaultAzureCredentialChain(DefaultAzureCredentialFactory factory, DefaultAz182 {183 if (options is null)184 {185 return s_defaultCredentialChain;186 }187188 int i = 0;189 TokenCredential[] chain = new TokenCredential[7];190191 if (!options.ExcludeEnvironmentCredential)192 {193 chain[i++] = factory.CreateEnvironmentCredential();194 }195196 if (!options.ExcludeManagedIdentityCredential)197 {198 chain[i++] = factory.CreateManagedIdentityCredential(options.ManagedIdentityClientId);199 }200201 if (!options.ExcludeSharedTokenCacheCredential)202 {203 chain[i++] = factory.CreateSharedTokenCacheCredential(options.SharedTokenCacheTenantId, options.SharedTo204 }205206 if (!options.ExcludeVisualStudioCredential)207 {208 chain[i++] = factory.CreateVisualStudioCredential(options.VisualStudioTenantId);209 }210211 if (!options.ExcludeVisualStudioCodeCredential)212 {213 chain[i++] = factory.CreateVisualStudioCodeCredential(options.VisualStudioCodeTenantId);214 }215216 if (!options.ExcludeAzureCliCredential)217 {218 chain[i++] = factory.CreateAzureCliCredential();219 }220221 if (!options.ExcludeInteractiveBrowserCredential)222 {223 chain[i++] = factory.CreateInteractiveBrowserCredential(options.InteractiveBrowserTenantId);224 }225226 if (i == 0)227 {228 throw new ArgumentException("At least one credential type must be included in the authentication flow.",229 }230231 return chain;232 }233 }234}ncG1vNJzZmiZqqq%2Fpr%2FDpJirrJmbrqTA0meZpaeSY7CwvsRnrqKmlKTEtHrNnqtomaqqv6Z50p2iZp6fp3qvsdNoeqiclVp%2FcY%2FOr5yrmZeafILG1KucZ4GUmru1tdOyln2dlpbCrcCgs6yrnXOnsqWxza2gmqRencGuuA%3D%3D