Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for detecting project ID. Fixes #97 #98

Merged
merged 1 commit into from
Oct 14, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 175 additions & 4 deletions lib/auth/googleauth.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

var JWTClient = require('./jwtclient.js');
var ComputeClient = require('./computeclient.js');
var exec = require('child_process').exec;
var fs = require('fs');
var os = require('os');
var path = require('path');
Expand Down Expand Up @@ -101,6 +102,169 @@ GoogleAuth.prototype._isGCE = false;
*/
GoogleAuth.prototype._checked_isGCE = false;

/**
* Obtains the default project ID for the application..
* @param {function=} opt_callback Optional callback.
*/
GoogleAuth.prototype.getDefaultProjectId = function(opt_callback) {
var that = this;

// 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)

if (that._cachedProjectId) {
process.nextTick(function() {
callback(opt_callback, null, that._cachedProjectId);
});
} else {
var my_callback = function(err, projectId) {
if (!err && projectId) {
that._cachedprojectId = projectId;
}
process.nextTick(function() {
callback(opt_callback, err, projectId);
});
};

// environment variable
if (that._getProductionProjectId(my_callback)) {
return;
}

// json file
that._getFileProjectId(function(err, projectId) {
if (err || projectId) {
my_callback(err, projectId);
return;
}

// Google Cloud SDK default project id
that._getDefaultServiceProjectId(function(err, projectId) {
if (err || projectId) {
my_callback(err, projectId);
return;
}

// Get project ID from Compute Engine metadata server
that._getGCEProjectId(my_callback);
});
});
}
};

/**
* Loads the project id from environment variables.
* @param {function} _callback Callback.
* @api private
*/
GoogleAuth.prototype._getProductionProjectId = function(_callback) {
var projectId = this._getEnv('GCLOUD_PROJECT') || this._getEnv('GOOGLE_CLOUD_PROJECT');
if (projectId) {
process.nextTick(function() {
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 that = this;
if (that._cachedCredential) {
// Try to read the project ID from the cached credentials file
process.nextTick(function() {
callback(_callback, null, that._cachedCredential.projectId);
});
return;
}

// Try to load a credentials file and read its project ID
var pathExists = that._tryGetApplicationCredentialsFromEnvironmentVariable(function(err, result) {
if (!err && result) {
callback(_callback, null, result.projectId);
return;
}
callback(_callback, err);
});

if (!pathExists) {
callback(_callback, null);
}
};

/**
* Loads the default project of the Google Cloud SDK.
* @param {function} _callback Callback.
* @api private
*/
GoogleAuth.prototype._getDefaultServiceProjectId = function(_callback) {
this._getSDKDefaultProjectId(function(err, stdout) {
var projectId;
if (!err && stdout) {
try {
projectId = JSON.parse(stdout).core.project;
} catch (err) {
projectId = null;
}
}
// Ignore any errors
callback(_callback, null, projectId);
});
};

/**
* Run the Google Cloud SDK command that prints the default project ID
* @param {function} _callback Callback.
* @api private
*/
GoogleAuth.prototype._getSDKDefaultProjectId = function(_callback) {
exec('gcloud -q config list core/project --format=json', _callback);
};

/**
* 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) {
if (!this.transporter) {
this.transporter = new 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) {
callback(_callback, null);
return;
}
// Ignore any errors
callback(_callback, null, body);
});
};

/**
* Obtains the default service-level credentials for the application..
* @param {function=} opt_callback Optional callback.
Expand All @@ -111,18 +275,25 @@ GoogleAuth.prototype.getApplicationDefault = function(opt_callback) {
// If we've already got a cached credential, just return it.
if (that._cachedCredential) {
process.nextTick(function() {
callback(opt_callback, null, that._cachedCredential);
callback(opt_callback, null, that._cachedCredential, that._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 = function(err, result) {
if (!err && result) {
that._cachedCredential = result;
that.getDefaultProjectId(function(err, projectId) {
process.nextTick(function() {
// Ignore default project error
callback(opt_callback, null, result, projectId);
});
});
} else {
process.nextTick(function() {
callback(opt_callback, err, result);
});
}
process.nextTick(function() {
callback(opt_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.
Expand Down
1 change: 1 addition & 0 deletions lib/auth/jwtaccess.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ JWTAccess.prototype.fromJSON = function(json, opt_callback) {
// Extract the relevant information from the json key file.
that.email = json.client_email;
that.key = json.private_key;
that.projectId = json.project_id;
done();
};

Expand Down
1 change: 1 addition & 0 deletions lib/auth/jwtclient.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ JWT.prototype.fromJSON = function(json, opt_callback) {
// Extract the relevant information from the json key file.
that.email = json.client_email;
that.key = json.private_key;
that.projectId = json.project_id;
done();
};

Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
"name": "Jason Allor",
"email": "[email protected]"
},
{
"name": "Jason Dobry",
"email": "[email protected]"
},
{
"name": "Tim Emiola",
"email": "[email protected]"
Expand Down
3 changes: 2 additions & 1 deletion test/fixtures/private2.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"private_key": "privatekey2",
"client_email": "[email protected]",
"client_id": "client456",
"type": "service_account"
"type": "service_account",
"project_id": "my-awesome-project"
}
Loading