/
DefaultCredentialProvider.cs
306 lines (271 loc) · 14.3 KB
/
DefaultCredentialProvider.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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
/*
Copyright 2015 Google Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2.Flows;
using Google.Apis.Auth.OAuth2.Responses;
using Google.Apis.Json;
using Google.Apis.Logging;
namespace Google.Apis.Auth.OAuth2
{
// TODO(jtattermusch): look into getting rid of DefaultCredentialProvider and moving
// the logic into GoogleCredential.
/// <summary>
/// Provides the Application Default Credential from the environment.
/// An instance of this class represents the per-process state used to get and cache
/// the credential and allows overriding the state and environment for testing purposes.
/// </summary>
internal class DefaultCredentialProvider
{
private static readonly ILogger Logger = ApplicationContext.Logger.ForType<DefaultCredentialProvider>();
/// <summary>
/// Environment variable override which stores the default application credentials file path.
/// </summary>
public const string CredentialEnvironmentVariable = "GOOGLE_APPLICATION_CREDENTIALS";
/// <summary>Well known file which stores the default application credentials.</summary>
private const string WellKnownCredentialsFile = "application_default_credentials.json";
/// <summary>Environment variable which contains the Application Data settings.</summary>
private const string AppdataEnvironmentVariable = "APPDATA";
/// <summary>Environment variable which contains the location of home directory on UNIX systems.</summary>
private const string HomeEnvironmentVariable = "HOME";
/// <summary>GCloud configuration directory in Windows, relative to %APPDATA%.</summary>
private const string CloudSDKConfigDirectoryWindows = "gcloud";
/// <summary>Help link to the application default credentials feature.</summary>
private const string HelpPermalink =
"https://developers.google.com/accounts/docs/application-default-credentials";
/// <summary>GCloud configuration directory on Linux/Mac, relative to $HOME.</summary>
private static readonly string CloudSDKConfigDirectoryUnix = Path.Combine(".config", "gcloud");
/// <summary>Caches result from first call to <c>GetApplicationDefaultCredentialAsync</c> </summary>
private readonly Lazy<Task<GoogleCredential>> cachedCredentialTask;
/// <summary>Constructs a new default credential provider.</summary>
public DefaultCredentialProvider()
{
cachedCredentialTask = new Lazy<Task<GoogleCredential>>(CreateDefaultCredentialAsync);
}
/// <summary>
/// Returns the Application Default Credentials. Subsequent invocations return cached value from
/// first invocation.
/// See <see cref="M:Google.Apis.Auth.OAuth2.GoogleCredential.GetApplicationDefaultAsync"/> for details.
/// </summary>
public Task<GoogleCredential> GetDefaultCredentialAsync() => cachedCredentialTask.Value;
/// <summary>Creates a new default credential.</summary>
private async Task<GoogleCredential> CreateDefaultCredentialAsync()
{
// 1. First try the environment variable.
string credentialPath = GetEnvironmentVariable(CredentialEnvironmentVariable);
if (!string.IsNullOrWhiteSpace(credentialPath))
{
try
{
return await CreateDefaultCredentialFromFileAsync(credentialPath, default).ConfigureAwait(false);
}
catch (Exception e)
{
// Catching generic exception type because any corrupted file could manifest in different ways
// including but not limited to the System, System.IO or from the Newtonsoft.Json namespace.
throw new InvalidOperationException(
String.Format("Error reading credential file from location {0}: {1}"
+ "\nPlease check the value of the Environment Variable {2}",
credentialPath,
e.Message,
CredentialEnvironmentVariable), e);
}
}
// 2. Then try the well known file.
credentialPath = GetWellKnownCredentialFilePath();
if (!string.IsNullOrWhiteSpace(credentialPath))
{
try
{
return await CreateDefaultCredentialFromFileAsync(credentialPath, default).ConfigureAwait(false);
}
catch (FileNotFoundException)
{
// File is not present, eat the exception and move on to the next check.
Logger.Debug("Well-known credential file {0} not found.", credentialPath);
}
catch (DirectoryNotFoundException)
{
// Directory not present, eat the exception and move on to the next check.
Logger.Debug("Well-known credential file {0} not found.", credentialPath);
}
catch (Exception e)
{
throw new InvalidOperationException(
String.Format("Error reading credential file from location {0}: {1}"
+ "\nPlease rerun 'gcloud auth login' to regenerate credentials file.",
credentialPath,
e.Message), e);
}
}
// 3. Then try the compute engine.
Logger.Debug("Checking whether the application is running on ComputeEngine.");
if (await ComputeCredential.IsRunningOnComputeEngine().ConfigureAwait(false))
{
Logger.Debug("ComputeEngine check passed. Using ComputeEngine Credentials.");
return new GoogleCredential(new ComputeCredential());
}
// If everything we tried has failed, throw an exception.
throw new InvalidOperationException(
String.Format("The Application Default Credentials are not available. They are available if running"
+ " in Google Compute Engine. Otherwise, the environment variable {0} must be defined"
+ " pointing to a file defining the credentials. See {1} for more information.",
CredentialEnvironmentVariable,
HelpPermalink));
}
private async Task<GoogleCredential> CreateDefaultCredentialFromFileAsync(string credentialPath, CancellationToken cancellationToken)
{
Logger.Debug("Loading Credential from file {0}", credentialPath);
using Stream stream = GetStream(credentialPath);
return await CreateDefaultCredentialFromStreamAsync(stream, cancellationToken).ConfigureAwait(false);
}
/// <summary>Creates a default credential from a stream that contains JSON credential data.</summary>
internal GoogleCredential CreateDefaultCredentialFromStream(Stream stream)
{
JsonCredentialParameters credentialParameters;
try
{
credentialParameters = NewtonsoftJsonSerializer.Instance.Deserialize<JsonCredentialParameters>(stream);
}
catch (Exception e)
{
throw new InvalidOperationException("Error deserializing JSON credential data.", e);
}
return CreateDefaultCredentialFromParameters(credentialParameters);
}
/// <summary>Creates a default credential from a stream that contains JSON credential data.</summary>
internal async Task<GoogleCredential> CreateDefaultCredentialFromStreamAsync(Stream stream, CancellationToken cancellationToken)
{
JsonCredentialParameters credentialParameters;
try
{
credentialParameters = await NewtonsoftJsonSerializer.Instance
.DeserializeAsync<JsonCredentialParameters>(stream, cancellationToken)
.ConfigureAwait(false);
}
catch (Exception e)
{
throw new InvalidOperationException("Error deserializing JSON credential data.", e);
}
return CreateDefaultCredentialFromParameters(credentialParameters);
}
/// <summary>Creates a default credential from a string that contains JSON credential data.</summary>
internal GoogleCredential CreateDefaultCredentialFromJson(string json)
{
JsonCredentialParameters credentialParameters;
try
{
credentialParameters = NewtonsoftJsonSerializer.Instance.Deserialize<JsonCredentialParameters>(json);
}
catch (Exception e)
{
throw new InvalidOperationException("Error deserializing JSON credential data.", e);
}
return CreateDefaultCredentialFromParameters(credentialParameters);
}
/// <summary>Creates a default credential from JSON data.</summary>
private static GoogleCredential CreateDefaultCredentialFromParameters(JsonCredentialParameters credentialParameters)
{
switch (credentialParameters.Type)
{
case JsonCredentialParameters.AuthorizedUserCredentialType:
return new GoogleCredential(CreateUserCredentialFromParameters(credentialParameters));
case JsonCredentialParameters.ServiceAccountCredentialType:
return GoogleCredential.FromServiceAccountCredential(
CreateServiceAccountCredentialFromParameters(credentialParameters));
default:
throw new InvalidOperationException(
String.Format("Error creating credential from JSON. Unrecognized credential type {0}.",
credentialParameters.Type));
}
}
/// <summary>Creates a user credential from JSON data.</summary>
private static UserCredential CreateUserCredentialFromParameters(JsonCredentialParameters credentialParameters)
{
if (credentialParameters.Type != JsonCredentialParameters.AuthorizedUserCredentialType ||
string.IsNullOrEmpty(credentialParameters.ClientId) ||
string.IsNullOrEmpty(credentialParameters.ClientSecret))
{
throw new InvalidOperationException("JSON data does not represent a valid user credential.");
}
var token = new TokenResponse
{
RefreshToken = credentialParameters.RefreshToken
};
var initializer = new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = credentialParameters.ClientId,
ClientSecret = credentialParameters.ClientSecret
},
ProjectId = credentialParameters.ProjectId
};
var flow = new GoogleAuthorizationCodeFlow(initializer);
return new UserCredential(flow, "ApplicationDefaultCredentials", token, credentialParameters.QuotaProject);
}
/// <summary>Creates a <see cref="ServiceAccountCredential"/> from JSON data.</summary>
private static ServiceAccountCredential CreateServiceAccountCredentialFromParameters(
JsonCredentialParameters credentialParameters)
{
if (credentialParameters.Type != JsonCredentialParameters.ServiceAccountCredentialType ||
string.IsNullOrEmpty(credentialParameters.ClientEmail) ||
string.IsNullOrEmpty(credentialParameters.PrivateKey))
{
throw new InvalidOperationException("JSON data does not represent a valid service account credential.");
}
var initializer = new ServiceAccountCredential.Initializer(credentialParameters.ClientEmail)
{
ProjectId = credentialParameters.ProjectId,
QuotaProject = credentialParameters.QuotaProject,
KeyId = credentialParameters.PrivateKeyId
};
return new ServiceAccountCredential(initializer.FromPrivateKey(credentialParameters.PrivateKey));
}
/// <summary>
/// Returns platform-specific well known credential file path. This file is created by
/// <a href="https://cloud.google.com/sdk/gcloud/reference/auth/login">gcloud auth login</a>
/// </summary>
private string GetWellKnownCredentialFilePath()
{
var appData = GetEnvironmentVariable(AppdataEnvironmentVariable);
if (appData != null) {
return Path.Combine(appData, CloudSDKConfigDirectoryWindows, WellKnownCredentialsFile);
}
var unixHome = GetEnvironmentVariable(HomeEnvironmentVariable);
if (unixHome != null)
{
return Path.Combine(unixHome, CloudSDKConfigDirectoryUnix, WellKnownCredentialsFile);
}
return Path.Combine(CloudSDKConfigDirectoryWindows, WellKnownCredentialsFile);
}
/// <summary>
/// Gets the environment variable.
/// This method is protected so it could be overriden for testing purposes only.
/// </summary>
protected virtual string GetEnvironmentVariable(string variableName)
{
return Environment.GetEnvironmentVariable(variableName);
}
/// <summary>
/// Opens file as a stream.
/// This method is protected so it could be overriden for testing purposes only.
/// </summary>
protected virtual Stream GetStream(string filePath)
{
return new FileStream(filePath, FileMode.Open, FileAccess.Read);
}
}
}