-
Notifications
You must be signed in to change notification settings - Fork 4.9k
/
Copy pathChainedTokenCredential.cs
132 lines (119 loc) · 7.07 KB
/
ChainedTokenCredential.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Azure.Core;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core.Pipeline;
namespace Azure.Identity
{
/// <summary>
/// Provides a <see cref="TokenCredential"/> implementation which chains multiple <see cref="TokenCredential"/> implementations
/// to be tried in order until one of the GetToken methods returns a non-default <see cref="AccessToken"/>. For more information,
/// see <see href="https://aka.ms/azsdk/net/identity/credential-chains#chainedtokencredential-overview">ChainedTokenCredential overview</see>.
/// </summary>
/// <example>
/// <para>
/// The following example demonstrates creating a credential which will attempt to authenticate using managed identity and fall
/// back to Azure CLI for authentication if a managed identity is unavailable in the current environment.
/// </para>
/// <code snippet="Snippet:CustomChainedTokenCredential" language="csharp">
/// // Authenticate using managed identity if it is available; otherwise use the Azure CLI to authenticate.
///
/// var credential = new ChainedTokenCredential(new ManagedIdentityCredential(), new AzureCliCredential());
///
/// var eventHubProducerClient = new EventHubProducerClient("myeventhub.eventhubs.windows.net", "myhubpath", credential);
/// </code>
/// </example>
public class ChainedTokenCredential : TokenCredential
{
private const string AggregateAllUnavailableErrorMessage = "The ChainedTokenCredential failed to retrieve a token from the included credentials.";
private const string AuthenticationFailedErrorMessage = "The ChainedTokenCredential failed due to an unhandled exception: ";
private readonly TokenCredential[] _sources;
/// <summary>
/// Protected constructor for <see href="https://aka.ms/azsdk/net/mocking">mocking</see>.
/// </summary>
protected ChainedTokenCredential()
{
_sources = Array.Empty<TokenCredential>();
}
/// <summary>
/// Creates an instance with the specified <see cref="TokenCredential"/> sources.
/// </summary>
/// <param name="sources">The ordered chain of <see cref="TokenCredential"/> implementations to tried when calling <see cref="GetToken"/> or <see cref="GetTokenAsync"/></param>
public ChainedTokenCredential(params TokenCredential[] sources)
{
if (sources is null) throw new ArgumentNullException(nameof(sources));
if (sources.Length == 0)
{
throw new ArgumentException("sources must not be empty", nameof(sources));
}
for (int i = 0; i < sources.Length; i++)
{
if (sources[i] == null)
{
throw new ArgumentException("sources must not contain null", nameof(sources));
}
}
_sources = sources;
}
/// <summary>
/// Sequentially calls <see cref="TokenCredential.GetToken"/> on all the specified sources, returning the first successfully obtained
/// <see cref="AccessToken"/>. Acquired tokens are <see href="https://aka.ms/azsdk/net/identity/token-cache">cached</see> by the
/// credential instance. Token lifetime and refreshing is handled automatically. Where possible, reuse credential instances to
/// optimize cache effectiveness.
/// </summary>
/// <param name="requestContext">The details of the authentication request.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
/// <returns>The first <see cref="AccessToken"/> returned by the specified sources. Any credential which raises a <see cref="CredentialUnavailableException"/> will be skipped.</returns>
/// <exception cref="AuthenticationFailedException">Thrown when the authentication failed.</exception>
public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken = default)
=> GetTokenImplAsync(false, requestContext, cancellationToken).EnsureCompleted();
/// <summary>
/// Sequentially calls <see cref="TokenCredential.GetToken"/> on all the specified sources, returning the first successfully obtained
/// <see cref="AccessToken"/>. Acquired tokens are <see href="https://aka.ms/azsdk/net/identity/token-cache">cached</see> by the
/// credential instance. Token lifetime and refreshing is handled automatically. Where possible, reuse credential instances to
/// optimize cache effectiveness.
/// </summary>
/// <param name="requestContext">The details of the authentication request.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
/// <returns>The first <see cref="AccessToken"/> returned by the specified sources. Any credential which raises a <see cref="CredentialUnavailableException"/> will be skipped.</returns>
/// <exception cref="AuthenticationFailedException">Thrown when the authentication failed.</exception>
public override async ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken = default)
=> await GetTokenImplAsync(true, requestContext, cancellationToken).ConfigureAwait(false);
private async ValueTask<AccessToken> GetTokenImplAsync(bool async, TokenRequestContext requestContext, CancellationToken cancellationToken)
{
var groupScopeHandler = new ScopeGroupHandler(default);
try
{
List<CredentialUnavailableException> exceptions = new List<CredentialUnavailableException>();
foreach (TokenCredential source in _sources)
{
try
{
AccessToken token = async
? await source.GetTokenAsync(requestContext, cancellationToken).ConfigureAwait(false)
: source.GetToken(requestContext, cancellationToken);
groupScopeHandler.Dispose(default, default);
return token;
}
catch (CredentialUnavailableException e)
{
exceptions.Add(e);
}
catch (Exception e) when (!cancellationToken.IsCancellationRequested)
{
throw new AuthenticationFailedException(AuthenticationFailedErrorMessage + e.Message, e);
}
}
throw CredentialUnavailableException.CreateAggregateException(AggregateAllUnavailableErrorMessage, exceptions);
}
catch (Exception exception)
{
groupScopeHandler.Fail(default, default, exception);
throw;
}
}
}
}