-
Notifications
You must be signed in to change notification settings - Fork 4.7k
/
EnvironmentCredential.cs
185 lines (166 loc) · 11.2 KB
/
EnvironmentCredential.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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
using Azure.Core.Pipeline;
namespace Azure.Identity
{
/// <summary>
/// Enables authentication to Microsoft Entra ID using a client secret or certificate, or as a user
/// with a username and password.
/// <para>
/// Configuration is attempted in this order, using these environment variables:
/// </para>
///
/// <b>Service principal with secret:</b>
/// <list type="table">
/// <listheader><term>Variable</term><description>Description</description></listheader>
/// <item><term>AZURE_TENANT_ID</term><description>The Microsoft Entra tenant (directory) ID.</description></item>
/// <item><term>AZURE_CLIENT_ID</term><description>The client (application) ID of an App Registration in the tenant.</description></item>
/// <item><term>AZURE_CLIENT_SECRET</term><description>A client secret that was generated for the App Registration.</description></item>
/// </list>
///
/// <b>Service principal with certificate:</b>
/// <list type="table">
/// <listheader><term>Variable</term><description>Description</description></listheader>
/// <item><term>AZURE_TENANT_ID</term><description>The Microsoft Entra tenant (directory) ID.</description></item>
/// <item><term>AZURE_CLIENT_ID</term><description>The client (application) ID of an App Registration in the tenant.</description></item>
/// <item><term>AZURE_CLIENT_CERTIFICATE_PATH</term><description>A path to certificate and private key pair in PEM or PFX format, which can authenticate the App Registration.</description></item>
/// <item><term>AZURE_CLIENT_CERTIFICATE_PASSWORD</term><description>(Optional) The password protecting the certificate file (currently only supported for PFX (PKCS12) certificates).</description></item>
/// <item><term>AZURE_CLIENT_SEND_CERTIFICATE_CHAIN</term><description>(Optional) Specifies whether an authentication request will include an x5c header to support subject name / issuer based authentication. When set to `true` or `1`, authentication requests include the x5c header.</description></item>
/// </list>
///
/// <b>Username and password:</b>
/// <list type="table">
/// <listheader><term>Variable</term><description>Description</description></listheader>
/// <item><term>AZURE_TENANT_ID</term><description>The Microsoft Entra tenant (directory) ID.</description></item>
/// <item><term>AZURE_CLIENT_ID</term><description>The client (application) ID of an App Registration in the tenant.</description></item>
/// <item><term>AZURE_USERNAME</term><description>The username, also known as upn, of a Microsoft Entra user account.</description></item>
/// <item><term>AZURE_PASSWORD</term><description>The password of the Microsoft Entra user account. Note this does not support accounts with MFA enabled.</description></item>
/// </list>
///
/// This credential ultimately uses a <see cref="ClientSecretCredential"/>, <see cref="ClientCertificateCredential"/>, or <see cref="UsernamePasswordCredential"/> to
/// perform the authentication using these details. Please consult the
/// documentation of that class for more details.
/// </summary>
public class EnvironmentCredential : TokenCredential
{
private const string UnavailableErrorMessage = "EnvironmentCredential authentication unavailable. Environment variables are not fully configured. See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/environmentcredential/troubleshoot";
private readonly CredentialPipeline _pipeline;
internal TokenCredential Credential { get; }
/// <summary>
/// Creates an instance of the EnvironmentCredential class and reads client secret details from environment variables.
/// If the expected environment variables are not found at this time, the GetToken method will return the default <see cref="AccessToken"/> when invoked.
/// </summary>
public EnvironmentCredential()
: this(CredentialPipeline.GetInstance(null))
{ }
/// <summary>
/// Creates an instance of the EnvironmentCredential class and reads client secret details from environment variables.
/// If the expected environment variables are not found at this time, the GetToken method will return the default <see cref="AccessToken"/> when invoked.
/// </summary>
/// <param name="options">Options that allow to configure the management of the requests sent to Microsoft Entra ID.</param>
[EditorBrowsable(EditorBrowsableState.Never)]
public EnvironmentCredential(TokenCredentialOptions options)
: this(CredentialPipeline.GetInstance(options), options)
{ }
/// <summary>
/// Creates an instance of the EnvironmentCredential class and reads client secret details from environment variables.
/// If the expected environment variables are not found at this time, the GetToken method will return the default <see cref="AccessToken"/> when invoked.
/// </summary>
/// <param name="options">Options that allow to configure the management of the requests sent to Microsoft Entra ID.</param>
public EnvironmentCredential(EnvironmentCredentialOptions options)
: this(CredentialPipeline.GetInstance(options), options)
{ }
internal EnvironmentCredential(CredentialPipeline pipeline, TokenCredentialOptions options = null)
{
_pipeline = pipeline;
options = options ?? new EnvironmentCredentialOptions();
EnvironmentCredentialOptions envCredOptions = (options as EnvironmentCredentialOptions) ?? options.Clone<EnvironmentCredentialOptions>();
string tenantId = envCredOptions.TenantId;
string clientId = envCredOptions.ClientId;
string clientSecret = envCredOptions.ClientSecret;
string clientCertificatePath = envCredOptions.ClientCertificatePath;
string clientCertificatePassword = envCredOptions.ClientCertificatePassword;
bool sendCertificateChain = envCredOptions.SendCertificateChain;
string username = envCredOptions.Username;
string password = envCredOptions.Password;
if (!string.IsNullOrEmpty(tenantId) && !string.IsNullOrEmpty(clientId))
{
if (!string.IsNullOrEmpty(clientSecret))
{
Credential = new ClientSecretCredential(tenantId, clientId, clientSecret, envCredOptions, _pipeline, envCredOptions.MsalConfidentialClient);
}
else if (!string.IsNullOrEmpty(clientCertificatePath))
{
ClientCertificateCredentialOptions clientCertificateCredentialOptions = envCredOptions.Clone<ClientCertificateCredentialOptions>();
clientCertificateCredentialOptions.SendCertificateChain = sendCertificateChain;
Credential = new ClientCertificateCredential(tenantId, clientId, clientCertificatePath, clientCertificatePassword, clientCertificateCredentialOptions, _pipeline, envCredOptions.MsalConfidentialClient);
}
else if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password))
{
Credential = new UsernamePasswordCredential(username, password, tenantId, clientId, envCredOptions, _pipeline, envCredOptions.MsalPublicClient);
}
}
}
internal EnvironmentCredential(CredentialPipeline pipeline, TokenCredential credential)
{
_pipeline = pipeline;
Credential = credential;
}
/// <summary>
/// Obtains a token from Microsoft Entra ID, using the specified client details specified in the environment variables
/// AZURE_TENANT_ID, AZURE_CLIENT_ID, and AZURE_CLIENT_SECRET or AZURE_USERNAME and AZURE_PASSWORD to authenticate.
/// Acquired tokens are cached by the credential instance. Token lifetime and refreshing is handled automatically. Where possible,
/// reuse credential instances to optimize cache effectiveness.
/// </summary>
/// <remarks>
/// If the environment variables AZURE_TENANT_ID, AZURE_CLIENT_ID, and AZURE_CLIENT_SECRET are not specified, the default <see cref="AccessToken"/>
/// </remarks>
/// <param name="requestContext">The details of the authentication request.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
/// <returns>An <see cref="AccessToken"/> which can be used to authenticate service client calls.</returns>
public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken = default)
{
return GetTokenImplAsync(false, requestContext, cancellationToken).EnsureCompleted();
}
/// <summary>
/// Obtains a token from Microsoft Entra ID, using the specified client details specified in the environment variables
/// AZURE_TENANT_ID, AZURE_CLIENT_ID, and AZURE_CLIENT_SECRET or AZURE_USERNAME and AZURE_PASSWORD to authenticate.
/// Acquired tokens are cached by the credential instance. Token lifetime and refreshing is handled automatically. Where possible,
/// reuse credential instances to optimize cache effectiveness.
/// </summary>
/// <remarks>
/// If the environment variables AZURE_TENANT_ID, AZURE_CLIENT_ID, and AZURE_CLIENT_SECRET are not specified, the default <see cref="AccessToken"/>
/// </remarks>
/// <param name="requestContext">The details of the authentication request.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
/// <returns>An <see cref="AccessToken"/> which can be used to authenticate service client calls, or a default <see cref="AccessToken"/>.</returns>
public override async ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken = default)
{
return await GetTokenImplAsync(true, requestContext, cancellationToken).ConfigureAwait(false);
}
private async ValueTask<AccessToken> GetTokenImplAsync(bool async, TokenRequestContext requestContext, CancellationToken cancellationToken)
{
using CredentialDiagnosticScope scope = _pipeline.StartGetTokenScope("EnvironmentCredential.GetToken", requestContext);
if (Credential is null)
{
throw scope.FailWrapAndThrow(new CredentialUnavailableException(UnavailableErrorMessage));
}
try
{
AccessToken token = async
? await Credential.GetTokenAsync(requestContext, cancellationToken).ConfigureAwait(false)
: Credential.GetToken(requestContext, cancellationToken);
return scope.Succeeded(token);
}
catch (Exception e)
{
throw scope.FailWrapAndThrow(e);
}
}
}
}