googleauth.js
24.2 KB
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
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
"use strict";
/**
* Copyright 2014 Google Inc. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
var child_process_1 = require("child_process");
var fs = require("fs");
var os = require("os");
var path = require("path");
var util = require("util");
var transporters_1 = require("../transporters");
var computeclient_1 = require("./computeclient");
var iam_1 = require("./iam");
var jwtaccess_1 = require("./jwtaccess");
var jwtclient_1 = require("./jwtclient");
var oauth2client_1 = require("./oauth2client");
var refreshclient_1 = require("./refreshclient");
var GoogleAuth = /** @class */ (function () {
function GoogleAuth() {
/**
* Caches a value indicating whether the auth layer is running on Google
* Compute Engine.
* @private
*/
this._isGCE = undefined;
// To save the contents of the JSON credential file
this.jsonContent = null;
this.cachedCredential = null;
this.JWTClient = jwtclient_1.JWT;
this.ComputeClient = computeclient_1.Compute;
/**
* Convenience field mapping in the IAM credential type.
*/
this.IAMAuth = iam_1.IAMAuth;
/**
* Convenience field mapping in the Compute credential type.
*/
this.Compute = computeclient_1.Compute;
/**
* Convenience field mapping in the JWT credential type.
*/
this.JWT = jwtclient_1.JWT;
/**
* Convenience field mapping in the JWT Access credential type.
*/
this.JWTAccess = jwtaccess_1.JWTAccess;
/**
* Convenience field mapping in the OAuth2 credential type.
*/
this.OAuth2 = oauth2client_1.OAuth2Client;
/**
* Convenience field mapping to the UserRefreshClient credential type.
*/
this.UserRefreshClient = refreshclient_1.UserRefreshClient;
}
Object.defineProperty(GoogleAuth.prototype, "isGCE", {
// Note: this properly is only public to satisify unit tests.
// https://github.com/Microsoft/TypeScript/issues/5228
get: function () {
return this._isGCE;
},
enumerable: true,
configurable: true
});
Object.defineProperty(GoogleAuth.prototype, "cachedProjectId", {
// Note: this properly is only public to satisify unit tests.
// https://github.com/Microsoft/TypeScript/issues/5228
set: function (projectId) {
this._cachedProjectId = projectId;
},
enumerable: true,
configurable: true
});
/**
* Obtains the default project ID for the application..
* @param {function=} callback Optional callback.
*/
GoogleAuth.prototype.getDefaultProjectId = function (callback) {
// In implicit case, supports three environments. In order of precedence,
// the implicit environments are:
//
// * GCLOUD_PROJECT or GOOGLE_CLOUD_PROJECT environment variable
// * GOOGLE_APPLICATION_CREDENTIALS JSON file
// * Get default service project from
// ``$ gcloud beta auth application-default login``
// * Google App Engine application ID (Not implemented yet)
// * Google Compute Engine project ID (from metadata server) (Not
// implemented yet)
var _this = this;
if (this._cachedProjectId) {
setImmediate(function () {
_this.callback(callback, null, _this._cachedProjectId);
});
}
else {
var my_callback_1 = function (err, projectId) {
if (!err && projectId) {
_this._cachedProjectId = projectId;
}
setImmediate(function () {
_this.callback(callback, err, projectId);
});
};
// environment variable
if (this._getProductionProjectId(my_callback_1)) {
return;
}
// json file
this._getFileProjectId(function (err, projectId) {
if (err || projectId) {
my_callback_1(err, projectId);
return;
}
// Google Cloud SDK default project id
_this._getDefaultServiceProjectId(function (err2, projectId2) {
if (err2 || projectId2) {
my_callback_1(err2, projectId2);
return;
}
// Get project ID from Compute Engine metadata server
_this._getGCEProjectId(my_callback_1);
});
});
}
};
/**
* Run the Google Cloud SDK command that prints the default project ID
* @param {function} _callback Callback.
* @api private
*/
GoogleAuth.prototype._getSDKDefaultProjectId = function (_callback) {
child_process_1.exec('gcloud -q config list core/project --format=json', _callback);
};
/**
* Obtains the default service-level credentials for the application..
* @param {function=} callback Optional callback.
*/
GoogleAuth.prototype.getApplicationDefault = function (callback) {
var _this = this;
// If we've already got a cached credential, just return it.
if (this.cachedCredential) {
setImmediate(function () {
_this.callback(callback, null, _this.cachedCredential, _this._cachedProjectId);
});
}
else {
// Inject our own callback routine, which will cache the credential once
// it's been created. It also allows us to ensure that the ultimate
// callback is always async.
var my_callback_2 = function (err, result) {
if (!err && result) {
_this.cachedCredential = result;
_this.getDefaultProjectId(function (err2, projectId) {
setImmediate(function () {
// Ignore default project error
_this.callback(callback, null, result, projectId);
});
});
}
else {
setImmediate(function () {
_this.callback(callback, err, result);
});
}
};
// Check for the existence of a local environment variable pointing to the
// location of the credential file. This is typically used in local
// developer scenarios.
if (this._tryGetApplicationCredentialsFromEnvironmentVariable(my_callback_2)) {
return;
}
// Look in the well-known credential file location.
if (this._tryGetApplicationCredentialsFromWellKnownFile(my_callback_2)) {
return;
}
// Determine if we're running on GCE.
this._checkIsGCE(function (err, gce) {
if (gce) {
// For GCE, just return a default ComputeClient. It will take care of
// the rest.
my_callback_2(null, new _this.ComputeClient());
}
else if (err) {
my_callback_2(new Error('Unexpected error while acquiring application default ' +
'credentials: ' + err.message));
}
else {
// We failed to find the default credentials. Bail out with an error.
my_callback_2(new Error('Could not load the default credentials. Browse to ' +
'https://developers.google.com/accounts/docs/application-default-credentials for ' +
'more information.'));
}
});
}
};
/**
* Determines whether the auth layer is running on Google Compute Engine.
* @param {function=} callback The callback.
* @api private
*/
GoogleAuth.prototype._checkIsGCE = function (callback) {
var _this = this;
if (this._isGCE !== undefined) {
setImmediate(function () {
callback(null, _this._isGCE);
});
}
else {
if (!this.transporter) {
this.transporter = new transporters_1.DefaultTransporter();
}
this.transporter.request({ method: 'GET', uri: 'http://metadata.google.internal', json: true }, function (err, body, res) {
if (err) {
if (err.code !== 'ENOTFOUND') {
// Unexpected error occurred. TODO(ofrobots): retry if this was
// a transient error.
return callback(err);
}
_this._isGCE = false;
return callback(null, _this._isGCE);
}
_this._isGCE = res && res.headers &&
res.headers['metadata-flavor'] === 'Google';
callback(null, _this._isGCE);
});
}
};
/**
* Attempts to load default credentials from the environment variable path..
* @param {function=} callback Optional callback.
* @return {boolean} Returns true if the callback has been executed; false otherwise.
* @api private
*/
GoogleAuth.prototype._tryGetApplicationCredentialsFromEnvironmentVariable = function (callback) {
var _this = this;
var credentialsPath = this._getEnv('GOOGLE_APPLICATION_CREDENTIALS');
if (!credentialsPath || credentialsPath.length === 0) {
return false;
}
this._getApplicationCredentialsFromFilePath(credentialsPath, function (err, result) {
var wrappedError = null;
if (err) {
wrappedError = _this.createError('Unable to read the credential file specified by the GOOGLE_APPLICATION_CREDENTIALS ' +
'environment variable.', err);
}
_this.callback(callback, wrappedError, result);
});
return true;
};
/**
* Attempts to load default credentials from a well-known file location
* @param {function=} callback Optional callback.
* @return {boolean} Returns true if the callback has been executed; false otherwise.
* @api private
*/
GoogleAuth.prototype._tryGetApplicationCredentialsFromWellKnownFile = function (callback) {
// First, figure out the location of the file, depending upon the OS type.
var location = null;
if (this._isWindows()) {
// Windows
location = this._getEnv('APPDATA');
}
else {
// Linux or Mac
var home = this._getEnv('HOME');
if (home) {
location = this._pathJoin(home, '.config');
}
}
// If we found the root path, expand it.
if (location) {
location = this._pathJoin(location, 'gcloud');
location =
this._pathJoin(location, 'application_default_credentials.json');
location = this._mockWellKnownFilePath(location);
// Check whether the file exists.
if (!this._fileExists(location)) {
location = null;
}
}
// The file does not exist.
if (!location) {
return false;
}
// The file seems to exist. Try to use it.
this._getApplicationCredentialsFromFilePath(location, callback);
return true;
};
/**
* Attempts to load default credentials from a file at the given path..
* @param {string=} filePath The path to the file to read.
* @param {function=} callback Optional callback.
* @api private
*/
GoogleAuth.prototype._getApplicationCredentialsFromFilePath = function (filePath, callback) {
var error = null;
// Make sure the path looks like a string.
if (!filePath || filePath.length === 0) {
error = new Error('The file path is invalid.');
}
// Make sure there is a file at the path. lstatSync will throw if there is
// nothing there.
if (!error) {
try {
// Resolve path to actual file in case of symlink. Expect a thrown error
// if not resolvable.
filePath = fs.realpathSync(filePath);
if (!fs.lstatSync(filePath).isFile()) {
throw new Error();
}
}
catch (err) {
error = this.createError(util.format('The file at %s does not exist, or it is not a file.', filePath), err);
}
}
// Now open a read stream on the file, and parse it.
if (!error) {
try {
var stream_1 = this._createReadStream(filePath);
this.fromStream(stream_1, callback);
}
catch (err) {
error = this.createError(util.format('Unable to read the file at %s.', filePath), err);
}
}
if (error) {
this.callback(callback, error);
}
};
/**
* Create a credentials instance using the given input options.
* @param {object=} json The input object.
* @param {function=} callback Optional callback.
*/
GoogleAuth.prototype.fromJSON = function (json, callback) {
var _this = this;
var client = null;
if (!json) {
this.callback(callback, new Error('Must pass in a JSON object containing the Google auth settings.'));
return;
}
// Set the JSON contents
this.jsonContent = json;
if (json.type === 'authorized_user') {
client = new refreshclient_1.UserRefreshClient();
}
else {
client = new jwtclient_1.JWT();
}
client.fromJSON(json, function (err) {
if (err) {
_this.callback(callback, err);
}
else {
_this.callback(callback, null, client);
}
});
};
/**
* Create a credentials instance using the given input stream.
* @param {object=} stream The input stream.
* @param {function=} callback Optional callback.
*/
GoogleAuth.prototype.fromStream = function (stream, callback) {
var _this = this;
if (!stream) {
setImmediate(function () {
_this.callback(callback, new Error('Must pass in a stream containing the Google auth settings.'));
});
return;
}
var s = '';
stream.setEncoding('utf8');
stream.on('data', function (chunk) {
s += chunk;
});
stream.on('end', function () {
try {
var data = JSON.parse(s);
_this.fromJSON(data, callback);
}
catch (err) {
_this.callback(callback, err);
}
});
};
/**
* Create a credentials instance using the given API key string.
* @param {string} - The API key string
* @param {function=} - Optional callback function
*/
GoogleAuth.prototype.fromAPIKey = function (apiKey, callback) {
var _this = this;
var client = new this.JWTClient();
client.fromAPIKey(apiKey, function (err) {
if (err) {
_this.callback(callback, err);
}
else {
_this.callback(callback, null, client);
}
});
};
/**
* Determines whether the current operating system is Windows.
* @api private
*/
GoogleAuth.prototype._isWindows = function () {
var sys = this._osPlatform();
if (sys && sys.length >= 3) {
if (sys.substring(0, 3).toLowerCase() === 'win') {
return true;
}
}
return false;
};
/**
* Creates a file stream. Allows mocking.
* @api private
*/
GoogleAuth.prototype._createReadStream = function (filePath) {
return fs.createReadStream(filePath);
};
/**
* Gets the value of the environment variable with the given name. Allows
* mocking.
* @api private
*/
GoogleAuth.prototype._getEnv = function (name) {
return process.env[name];
};
/**
* Gets the current operating system platform. Allows mocking.
* @api private
*/
GoogleAuth.prototype._osPlatform = function () {
return os.platform();
};
/**
* Determines whether a file exists. Allows mocking.
* @api private
*/
GoogleAuth.prototype._fileExists = function (filePath) {
return fs.existsSync(filePath);
};
/**
* Joins two parts of a path. Allows mocking.
* @api private
*/
GoogleAuth.prototype._pathJoin = function (item1, item2) {
return path.join(item1, item2);
};
/**
* Allows mocking of the path to a well-known file.
* @api private
*/
GoogleAuth.prototype._mockWellKnownFilePath = function (filePath) {
return filePath;
};
// Executes the given callback if it is not null.
GoogleAuth.prototype.callback = function (c, err) {
var args = [];
for (var _i = 2; _i < arguments.length; _i++) {
args[_i - 2] = arguments[_i];
}
if (c) {
return c.apply(null, Array.prototype.slice.call(arguments, 1));
}
};
// Creates an Error containing the given message, and includes the message
// from the optional err passed in.
GoogleAuth.prototype.createError = function (message, err) {
var s = message || '';
if (err) {
var errorMessage = String(err);
if (errorMessage && errorMessage.length > 0) {
if (s.length > 0) {
s += ' ';
}
s += errorMessage;
}
}
return Error(s);
};
/**
* Loads the default project of the Google Cloud SDK.
* @param {function} _callback Callback.
* @api private
*/
GoogleAuth.prototype._getDefaultServiceProjectId = function (_callback) {
var _this = this;
this._getSDKDefaultProjectId(function (err, stdout) {
var projectId = null;
if (!err && stdout) {
try {
projectId = JSON.parse(stdout).core.project;
}
catch (err) {
projectId = null;
}
}
// Ignore any errors
_this.callback(_callback, null, projectId);
});
};
/**
* Loads the project id from environment variables.
* @param {function} _callback Callback.
* @api private
*/
GoogleAuth.prototype._getProductionProjectId = function (_callback) {
var _this = this;
var projectId = this._getEnv('GCLOUD_PROJECT') || this._getEnv('GOOGLE_CLOUD_PROJECT');
if (projectId) {
setImmediate(function () {
_this.callback(_callback, null, projectId);
});
}
return projectId;
};
/**
* Loads the project id from the GOOGLE_APPLICATION_CREDENTIALS json file.
* @param {function} _callback Callback.
* @api private
*/
GoogleAuth.prototype._getFileProjectId = function (_callback) {
var _this = this;
if (this.cachedCredential) {
// Try to read the project ID from the cached credentials file
setImmediate(function () {
_this.callback(_callback, null, _this.cachedCredential.projectId);
});
return;
}
// Try to load a credentials file and read its project ID
var pathExists = this._tryGetApplicationCredentialsFromEnvironmentVariable(function (err, result) {
if (!err && result) {
_this.callback(_callback, null, result.projectId);
return;
}
_this.callback(_callback, err);
});
if (!pathExists) {
this.callback(_callback, null);
}
};
/**
* Gets the Compute Engine project ID if it can be inferred.
* Uses 169.254.169.254 for the metadata server to avoid request
* latency from DNS lookup.
* See https://cloud.google.com/compute/docs/metadata#metadataserver
* for information about this IP address. (This IP is also used for
* Amazon EC2 instances, so the metadata flavor is crucial.)
* See https://github.com/google/oauth2client/issues/93 for context about
* DNS latency.
*
* @param {function} _callback Callback.
* @api private
*/
GoogleAuth.prototype._getGCEProjectId = function (_callback) {
var _this = this;
if (!this.transporter) {
this.transporter = new transporters_1.DefaultTransporter();
}
this.transporter.request({
method: 'GET',
uri: 'http://169.254.169.254/computeMetadata/v1/project/project-id',
headers: { 'Metadata-Flavor': 'Google' }
}, function (err, body, res) {
if (err || !res || res.statusCode !== 200 || !body) {
_this.callback(_callback);
return;
}
// Ignore any errors
_this.callback(_callback, null, body);
});
};
/**
* The callback function handles a credential object that contains the
* client_email and private_key (if exists).
* getCredentials checks for these values from the user JSON at first.
* If it doesn't exist, and the environment is on GCE, it gets the
* client_email from the cloud metadata server.
* @param callback Callback that handles the credential object that contains
* a client_email and optional private key, or the error.
* returned
*/
GoogleAuth.prototype.getCredentials = function (callback) {
var _this = this;
if (this.jsonContent) {
var credential = {
client_email: this.jsonContent.client_email,
private_key: this.jsonContent.private_key
};
callback(null, credential);
}
else {
this._checkIsGCE(function (err, gce) {
if (err) {
callback(err);
}
else if (!gce) {
callback(new Error('Unknown error.'));
}
else {
// For GCE, return the service account details from the metadata
// server
_this.transporter.request({
method: 'GET',
uri: 'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/?recursive=true',
headers: { 'Metadata-Flavor': 'Google' }
}, function (err, body, res) {
if (err || !res || res.statusCode !== 200 || !body ||
!body.default || !body.default.email) {
if (callback) {
callback(new Error('Failure from metadata server.'));
}
}
else {
// Callback with the body
var credential = { client_email: body.default.email };
if (callback) {
callback(null, credential);
}
}
});
}
});
}
};
/**
* Export DefaultTransporter as a static property of the class.
*/
GoogleAuth.DefaultTransporter = transporters_1.DefaultTransporter;
return GoogleAuth;
}());
exports.GoogleAuth = GoogleAuth;
//# sourceMappingURL=googleauth.js.map