Skip to content

Commit

Permalink
Support un-authenticated requests. (#2712)
Browse files Browse the repository at this point in the history
  • Loading branch information
stephenplusplus authored Nov 3, 2017
1 parent 7160d17 commit 6fafb06
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 40 deletions.
17 changes: 10 additions & 7 deletions packages/common/src/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,25 @@ var PROJECT_ID_TOKEN = '{{projectId}}';
function Service(config, options) {
options = options || {};

this.baseUrl = config.baseUrl;
this.globalInterceptors = arrify(options.interceptors_);
this.interceptors = [];
this.packageJson = config.packageJson;
this.projectId = options.projectId || PROJECT_ID_TOKEN;
this.projectIdRequired = config.projectIdRequired !== false;
this.Promise = options.promise || Promise;

var reqCfg = extend({}, config, {
projectIdRequired: this.projectIdRequired,
projectId: this.projectId,
credentials: options.credentials,
keyFile: options.keyFilename,
email: options.email
});

this.makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory(reqCfg);
this.authClient = this.makeAuthenticatedRequest.authClient;
this.baseUrl = config.baseUrl;
this.getCredentials = this.makeAuthenticatedRequest.getCredentials;
this.globalInterceptors = arrify(options.interceptors_);
this.interceptors = [];
this.packageJson = config.packageJson;
this.projectId = options.projectId || PROJECT_ID_TOKEN;
this.projectIdRequired = config.projectIdRequired !== false;
this.Promise = options.promise || Promise;

var isCloudFunctionEnv = !!process.env.FUNCTION_NAME;

Expand Down
39 changes: 28 additions & 11 deletions packages/common/src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -346,14 +346,33 @@ function makeAuthenticatedRequestFactory(config) {
}

function onAuthenticated(err, authenticatedReqOpts) {
if (!err) {
var autoAuthFailed =
err &&
err.message.indexOf('Could not load the default credentials') > -1;

if (autoAuthFailed) {
// Even though authentication failed, the API might not actually care.
authenticatedReqOpts = reqOpts;
}

if (!err || autoAuthFailed) {
var projectId = authClient.projectId;

if (config.projectId && config.projectId !== '{{projectId}}') {
projectId = config.projectId;
}

try {
authenticatedReqOpts = util.decorateRequest(
authenticatedReqOpts,
extend({ projectId: authClient.projectId }, config)
projectId
);
err = null;
} catch(e) {
err = e;
// A projectId was required, but we don't have one.
// Re-use the "Could not load the default credentials error" if auto
// auth failed.
err = err || e;
}
}

Expand Down Expand Up @@ -473,29 +492,27 @@ util.makeRequest = makeRequest;
* Decorate the options about to be made in a request.
*
* @param {object} reqOpts - The options to be passed to `request`.
* @param {object} config - Service config.
* @param {string} projectId - The project ID.
* @return {object} reqOpts - The decorated reqOpts.
*/
function decorateRequest(reqOpts, config) {
config = config || {};

function decorateRequest(reqOpts, projectId) {
delete reqOpts.autoPaginate;
delete reqOpts.autoPaginateVal;
delete reqOpts.objectMode;

if (is.object(reqOpts.qs)) {
delete reqOpts.qs.autoPaginate;
delete reqOpts.qs.autoPaginateVal;
reqOpts.qs = util.replaceProjectIdToken(reqOpts.qs, config.projectId);
reqOpts.qs = util.replaceProjectIdToken(reqOpts.qs, projectId);
}

if (is.object(reqOpts.json)) {
delete reqOpts.json.autoPaginate;
delete reqOpts.json.autoPaginateVal;
reqOpts.json = util.replaceProjectIdToken(reqOpts.json, config.projectId);
reqOpts.json = util.replaceProjectIdToken(reqOpts.json, projectId);
}

reqOpts.uri = util.replaceProjectIdToken(reqOpts.uri, config.projectId);
reqOpts.uri = util.replaceProjectIdToken(reqOpts.uri, projectId);

return reqOpts;
}
Expand Down Expand Up @@ -528,7 +545,7 @@ function replaceProjectIdToken(value, projectId) {
}

if (is.string(value) && value.indexOf('{{projectId}}') > -1) {
if (!projectId) {
if (!projectId || projectId === '{{projectId}}') {
throw util.missingProjectIdError;
}
value = value.replace(/{{projectId}}/g, projectId);
Expand Down
4 changes: 3 additions & 1 deletion packages/common/test/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ describe('Service', function() {
var expectedConfig = extend({}, CONFIG, {
credentials: OPTIONS.credentials,
keyFile: OPTIONS.keyFilename,
email: OPTIONS.email
email: OPTIONS.email,
projectIdRequired: CONFIG.projectIdRequired,
projectId: OPTIONS.projectId
});

assert.deepEqual(config, expectedConfig);
Expand Down
116 changes: 95 additions & 21 deletions packages/common/test/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,7 @@ describe('common/util', function() {
var config = {
customEndpoint: true
};
var expectedConfig = extend({ projectId: authClient.projectId }, config);
var expectedProjectId = authClient.projectId;

beforeEach(function() {
makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory(config);
Expand All @@ -719,9 +719,9 @@ describe('common/util', function() {
var reqOpts = { a: 'b', c: 'd' };
var decoratedRequest = {};

utilOverrides.decorateRequest = function(reqOpts_, config_) {
utilOverrides.decorateRequest = function(reqOpts_, projectId) {
assert.strictEqual(reqOpts_, reqOpts);
assert.deepEqual(config_, expectedConfig);
assert.deepEqual(projectId, expectedProjectId);
return decoratedRequest;
};

Expand Down Expand Up @@ -793,6 +793,47 @@ describe('common/util', function() {
assert(makeAuthenticatedRequest({}) instanceof stream.Stream);
});

describe('projectId', function() {
it('should default to authClient projectId', function(done) {
authClient.projectId = 'authclient-project-id';

utilOverrides.decorateRequest = function(reqOpts, projectId) {
assert.strictEqual(projectId, authClient.projectId);
setImmediate(done);
};

var makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory({
customEndpoint: true
});

makeAuthenticatedRequest({}, {
onAuthenticated: assert.ifError
});
});

it('should use user-provided projectId', function(done) {
authClient.projectId = 'authclient-project-id';

var config = {
customEndpoint: true,
projectId: 'project-id'
};

utilOverrides.decorateRequest = function(reqOpts, projectId) {
assert.strictEqual(projectId, config.projectId);
setImmediate(done);
};

var makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory(
config
);

makeAuthenticatedRequest({}, {
onAuthenticated: assert.ifError
});
});
});

describe('authentication errors', function() {
var error = new Error('Error.');

Expand All @@ -804,6 +845,45 @@ describe('common/util', function() {
};
});

it('should attempt request anyway', function(done) {
var makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory();

var correctReqOpts = {};
var incorrectReqOpts = {};

authClient.authorizeRequest = function(rOpts, callback) {
var error = new Error('Could not load the default credentials');
callback(error, incorrectReqOpts);
};

makeAuthenticatedRequest(correctReqOpts, {
onAuthenticated: function(err, reqOpts) {
assert.ifError(err);

assert.strictEqual(reqOpts, correctReqOpts);
assert.notStrictEqual(reqOpts, incorrectReqOpts);

done();
}
});
});

it('should block decorateRequest error', function(done) {
var decorateRequestError = new Error('Error.');
utilOverrides.decorateRequest = function() {
throw decorateRequestError;
};

var makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory();
makeAuthenticatedRequest({}, {
onAuthenticated: function(err) {
assert.notStrictEqual(err, decorateRequestError);
assert.strictEqual(err, error);
done();
}
});
});

it('should invoke the callback with error', function(done) {
var makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory();
makeAuthenticatedRequest({}, function(err) {
Expand Down Expand Up @@ -1281,64 +1361,58 @@ describe('common/util', function() {
});

it('should replace project ID tokens for qs object', function() {
var config = {
projectId: 'project-id'
};
var projectId = 'project-id';
var reqOpts = {
uri: 'http://',
qs: {}
};
var decoratedQs = {};

utilOverrides.replaceProjectIdToken = function(qs, projectId) {
utilOverrides.replaceProjectIdToken = function(qs, projectId_) {
utilOverrides = {};
assert.strictEqual(qs, reqOpts.qs);
assert.strictEqual(projectId, config.projectId);
assert.strictEqual(projectId_, projectId);
return decoratedQs;
};

var decoratedRequest = util.decorateRequest(reqOpts, config);
var decoratedRequest = util.decorateRequest(reqOpts, projectId);
assert.strictEqual(decoratedRequest.qs, decoratedQs);
});

it('should replace project ID tokens for json object', function() {
var config = {
projectId: 'project-id'
};
var projectId = 'project-id';
var reqOpts = {
uri: 'http://',
json: {}
};
var decoratedJson = {};

utilOverrides.replaceProjectIdToken = function(json, projectId) {
utilOverrides.replaceProjectIdToken = function(json, projectId_) {
utilOverrides = {};
assert.strictEqual(reqOpts.json, json);
assert.strictEqual(projectId, config.projectId);
assert.strictEqual(projectId_, projectId);
return decoratedJson;
};

var decoratedRequest = util.decorateRequest(reqOpts, config);
var decoratedRequest = util.decorateRequest(reqOpts, projectId);
assert.strictEqual(decoratedRequest.json, decoratedJson);
});

it('should decorate the request', function() {
var config = {
projectId: 'project-id'
};
var projectId = 'project-id';
var reqOpts = {
uri: 'http://'
};
var decoratedUri = 'http://decorated';

utilOverrides.replaceProjectIdToken = function(uri, projectId) {
utilOverrides.replaceProjectIdToken = function(uri, projectId_) {
assert.strictEqual(uri, reqOpts.uri);
assert.strictEqual(projectId, config.projectId);
assert.strictEqual(projectId_, projectId);
return decoratedUri;
};

assert.deepEqual(
util.decorateRequest(reqOpts, config),
util.decorateRequest(reqOpts, projectId),
{ uri: decoratedUri }
);
});
Expand Down

0 comments on commit 6fafb06

Please sign in to comment.