Skip to content

Commit

Permalink
feat: populate x-goog-user-project for requestAsync (#837)
Browse files Browse the repository at this point in the history
  • Loading branch information
bcoe authored and JustinBeckwith committed Dec 2, 2019
1 parent 8933966 commit 5a068fb
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 44 deletions.
28 changes: 16 additions & 12 deletions src/auth/oauth2client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -742,15 +742,6 @@ export class OAuth2Client extends AuthClient {
*/
async getRequestHeaders(url?: string): Promise<Headers> {
const headers = (await this.getRequestMetadataAsync(url)).headers;
// quota_project_id, stored in application_default_credentials.json, is set in
// the x-goog-user-project header, to indicate an alternate account for
// billing and quota:
if (
!headers['x-goog-user-project'] && // don't override a value the user sets.
this.quotaProjectId
) {
headers['x-goog-user-project'] = this.quotaProjectId;
}
return headers;
}

Expand Down Expand Up @@ -793,9 +784,20 @@ export class OAuth2Client extends AuthClient {
credentials.token_type = credentials.token_type || 'Bearer';
tokens.refresh_token = credentials.refresh_token;
this.credentials = tokens;
const headers = {
const headers: {[index: string]: string} = {
Authorization: credentials.token_type + ' ' + tokens.access_token,
};

// quota_project_id, stored in application_default_credentials.json, is set in
// the x-goog-user-project header, to indicate an alternate account for
// billing and quota:
if (
!headers['x-goog-user-project'] && // don't override a value the user sets.
this.quotaProjectId
) {
headers['x-goog-user-project'] = this.quotaProjectId;
}

return {headers, res: r.res};
}

Expand Down Expand Up @@ -896,12 +898,14 @@ export class OAuth2Client extends AuthClient {
let r2: GaxiosResponse;
try {
const r = await this.getRequestMetadataAsync(opts.url);
opts.headers = opts.headers || {};
if (r.headers?.['x-goog-user-project']) {
opts.headers['x-goog-user-project'] = r.headers['x-goog-user-project'];
}
if (r.headers && r.headers.Authorization) {
opts.headers = opts.headers || {};
opts.headers.Authorization = r.headers.Authorization;
}
if (this.apiKey) {
opts.headers = opts.headers || {};
opts.headers['X-Goog-Api-Key'] = this.apiKey;
}
r2 = await this.transporter.request<T>(opts);
Expand Down
80 changes: 48 additions & 32 deletions test/test.googleauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1444,54 +1444,70 @@ describe('googleauth', () => {
});

it('getRequestHeaders populates x-goog-user-project with quota_project if present', async () => {
// Fake a home directory in our fixtures path.
mockEnvVar('GCLOUD_PROJECT', 'my-fake-project');
mockEnvVar('HOME', './test/fixtures/config-with-quota');
mockEnvVar('APPDATA', './test/fixtures/config-with-quota/.config');
// The first time auth.getClient() is called /token endpoint is used to
// fetch a JWT.
const req = nock('https://oauth2.googleapis.com')
.post('/token')
.reply(200, {});

const tokenReq = mockApplicationDefaultCredentials(
'./test/fixtures/config-with-quota'
);
const auth = new GoogleAuth();
const headers = await auth.getRequestHeaders();
assert.strictEqual(headers['x-goog-user-project'], 'my-quota-project');
req.done();
tokenReq.done();
});

it('getRequestHeaders does not populate x-goog-user-project if quota_project is not present', async () => {
// Fake a home directory in our fixtures path.
mockEnvVar('GCLOUD_PROJECT', 'my-fake-project');
mockEnvVar('HOME', './test/fixtures/config-no-quota');
mockEnvVar('APPDATA', './test/fixtures/config-no-quota/.config');
// The first time auth.getClient() is called /token endpoint is used to
// fetch a JWT.
const req = nock('https://oauth2.googleapis.com')
.post('/token')
.reply(200, {});

const tokenReq = mockApplicationDefaultCredentials(
'./test/fixtures/config-no-quota'
);
const auth = new GoogleAuth();
const headers = await auth.getRequestHeaders();
assert.strictEqual(headers['x-goog-user-project'], undefined);
req.done();
tokenReq.done();
});

it('getRequestHeaders populates x-goog-user-project when called on returned client', async () => {
const tokenReq = mockApplicationDefaultCredentials(
'./test/fixtures/config-with-quota'
);
const auth = new GoogleAuth();
const client = await auth.getClient();
const headers = await client.getRequestHeaders();
assert.strictEqual(headers['x-goog-user-project'], 'my-quota-project');
tokenReq.done();
});

it('populates x-goog-user-project when request is made', async () => {
const tokenReq = mockApplicationDefaultCredentials(
'./test/fixtures/config-with-quota'
);
const auth = new GoogleAuth();
const client = await auth.getClient();
const apiReq = nock(BASE_URL)
.post(ENDPOINT)
.reply(function(uri) {
assert.strictEqual(
this.req.headers['x-goog-user-project'][0],
'my-quota-project'
);
return [200, RESPONSE_BODY];
});
const res = await client.request({
url: BASE_URL + ENDPOINT,
method: 'POST',
data: {test: true},
});
assert.strictEqual(RESPONSE_BODY, res.data);
tokenReq.done();
apiReq.done();
});

function mockApplicationDefaultCredentials(path: string) {
// Fake a home directory in our fixtures path.
mockEnvVar('GCLOUD_PROJECT', 'my-fake-project');
mockEnvVar('HOME', './test/fixtures/config-with-quota');
mockEnvVar('APPDATA', './test/fixtures/config-with-quota/.config');
mockEnvVar('HOME', path);
mockEnvVar('APPDATA', `${path}/.config`);
// The first time auth.getClient() is called /token endpoint is used to
// fetch a JWT.
const req = nock('https://oauth2.googleapis.com')
return nock('https://oauth2.googleapis.com')
.post('/token')
.reply(200, {});

const auth = new GoogleAuth();
const client = await auth.getClient();
const headers = await client.getRequestHeaders();
assert.strictEqual(headers['x-goog-user-project'], 'my-quota-project');
req.done();
});
}
});

0 comments on commit 5a068fb

Please sign in to comment.