Skip to content

Commit

Permalink
fix: Set the Quota Project ID only for ADC human accounts (#2761)
Browse files Browse the repository at this point in the history
* fix: Set quota project ID for ADC human accounts

* Add header to http/2 calls

* add unit tests
  • Loading branch information
lahirumaramba authored Nov 8, 2024
1 parent 4babb45 commit 4ee1bb2
Show file tree
Hide file tree
Showing 10 changed files with 67 additions and 11 deletions.
9 changes: 9 additions & 0 deletions src/app/credential-internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export class ApplicationDefaultCredential implements Credential {
private readonly googleAuth: GoogleAuth;
private authClient: AnyAuthClient;
private projectId?: string;
private quotaProjectId?: string;
private accountId?: string;

constructor(httpAgent?: Agent) {
Expand All @@ -58,6 +59,7 @@ export class ApplicationDefaultCredential implements Credential {
}
await this.authClient.getAccessToken();
const credentials = this.authClient.credentials;
this.quotaProjectId = this.authClient.quotaProjectId;
return populateCredential(credentials);
}

Expand All @@ -68,6 +70,13 @@ export class ApplicationDefaultCredential implements Credential {
return Promise.resolve(this.projectId);
}

public getQuotaProjectId(): string | undefined {
if (!this.quotaProjectId) {
this.quotaProjectId = this.authClient?.quotaProjectId;
}
return this.quotaProjectId;
}

public async isComputeEngineCredential(): Promise<boolean> {
if (!this.authClient) {
this.authClient = await this.googleAuth.getClient();
Expand Down
21 changes: 17 additions & 4 deletions src/utils/api-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import url = require('url');
import { EventEmitter } from 'events';
import { Readable } from 'stream';
import * as zlibmod from 'zlib';
import { ApplicationDefaultCredential } from '../app/credential-internal';
import { getMetricsHeader } from '../utils/index';

/** Http method type definition. */
Expand Down Expand Up @@ -1077,10 +1078,13 @@ export class AuthorizedHttpClient extends HttpClient {
const authHeader = 'Authorization';
requestCopy.headers[authHeader] = `Bearer ${token}`;

// Fix issue where firebase-admin does not specify quota project that is
// necessary for use when utilizing human account with ADC (RSDF)
if (!requestCopy.headers['x-goog-user-project'] && this.app.options.projectId) {
requestCopy.headers['x-goog-user-project'] = this.app.options.projectId
let quotaProjectId: string | undefined;
if (this.app.options.credential instanceof ApplicationDefaultCredential) {
quotaProjectId = this.app.options.credential.getQuotaProjectId();
}
quotaProjectId = process.env.GOOGLE_CLOUD_QUOTA_PROJECT || quotaProjectId;
if (!requestCopy.headers['x-goog-user-project'] && validator.isNonEmptyString(quotaProjectId)) {
requestCopy.headers['x-goog-user-project'] = quotaProjectId;
}

if (!requestCopy.httpAgent && this.app.options.httpAgent) {
Expand Down Expand Up @@ -1112,6 +1116,15 @@ export class AuthorizedHttp2Client extends Http2Client {
const authHeader = 'Authorization';
requestCopy.headers[authHeader] = `Bearer ${token}`;

let quotaProjectId: string | undefined;
if (this.app.options.credential instanceof ApplicationDefaultCredential) {
quotaProjectId = this.app.options.credential.getQuotaProjectId();
}
quotaProjectId = process.env.GOOGLE_CLOUD_QUOTA_PROJECT || quotaProjectId;
if (!requestCopy.headers['x-goog-user-project'] && validator.isNonEmptyString(quotaProjectId)) {
requestCopy.headers['x-goog-user-project'] = quotaProjectId;
}

requestCopy.headers['X-Goog-Api-Client'] = getMetricsHeader()

return super.send(requestCopy);
Expand Down
1 change: 0 additions & 1 deletion test/unit/app-check/app-check-api-client-internal.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ describe('AppCheckApiClient', () => {
const EXPECTED_HEADERS = {
'Authorization': 'Bearer mock-token',
'X-Firebase-Client': `fire-admin-node/${getSdkVersion()}`,
'x-goog-user-project': 'test-project',
'X-Goog-Api-Client': getMetricsHeader(),
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ describe('DataConnectApiClient', () => {
const EXPECTED_HEADERS = {
'Authorization': 'Bearer mock-token',
'X-Firebase-Client': `fire-admin-node/${getSdkVersion()}`,
'x-goog-user-project': 'test-project',
'X-Goog-Api-Client': getMetricsHeader(),
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ describe('Extension API client', () => {
const EXPECTED_HEADERS = {
'Authorization': 'Bearer mock-token',
'X-Firebase-Client': `fire-admin-node/${getSdkVersion()}`,
'x-goog-user-project': 'test-project',
'X-Goog-Api-Client': getMetricsHeader(),
}

Expand Down
1 change: 0 additions & 1 deletion test/unit/functions/functions-api-client-internal.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ describe('FunctionsApiClient', () => {
const EXPECTED_HEADERS = {
'X-Firebase-Client': `fire-admin-node/${getSdkVersion()}`,
'Authorization': 'Bearer mock-token',
'x-goog-user-project': 'test-project',
'X-Goog-Api-Client': getMetricsHeader(),
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@ describe('MachineLearningApiClient', () => {
const EXPECTED_HEADERS = {
'Authorization': 'Bearer mock-token',
'X-Firebase-Client': `fire-admin-node/${getSdkVersion()}`,
'x-goog-user-project': 'test-project',
'X-Goog-Api-Client': getMetricsHeader(),
};
const noProjectId = 'Failed to determine project ID. Initialize the SDK with service '
Expand Down
1 change: 0 additions & 1 deletion test/unit/remote-config/remote-config-api-client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ describe('RemoteConfigApiClient', () => {
'Authorization': 'Bearer mock-token',
'X-Firebase-Client': `fire-admin-node/${getSdkVersion()}`,
'Accept-Encoding': 'gzip',
'x-goog-user-project': 'test-project',
'X-Goog-Api-Client': getMetricsHeader(),
};

Expand Down
1 change: 0 additions & 1 deletion test/unit/security-rules/security-rules-api-client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ describe('SecurityRulesApiClient', () => {
const EXPECTED_HEADERS = {
'Authorization': 'Bearer mock-token',
'X-Firebase-Client': `fire-admin-node/${getSdkVersion()}`,
'x-goog-user-project': 'test-project',
'X-Goog-Api-Client': getMetricsHeader(),
};
const noProjectId = 'Failed to determine project ID. Initialize the SDK with service '
Expand Down
41 changes: 41 additions & 0 deletions test/unit/utils/api-request.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

'use strict';

import * as _ from 'lodash';
import * as chai from 'chai';
import * as nock from 'nock';
import * as sinon from 'sinon';
Expand All @@ -35,6 +36,7 @@ import {
import { deepCopy } from '../../../src/utils/deep-copy';
import { Agent } from 'http';
import * as zlib from 'zlib';
import { getMetricsHeader } from '../../../src/utils';

chai.should();
chai.use(sinonChai);
Expand Down Expand Up @@ -2648,6 +2650,45 @@ describe('AuthorizedHttpClient', () => {
});
});

describe('Quota Project', () => {
let stubs: sinon.SinonStub[] = [];

afterEach(() => {
_.forEach(stubs, (stub) => stub.restore());
stubs = [];
if (process.env.GOOGLE_CLOUD_QUOTA_PROJECT) {
delete process.env.GOOGLE_CLOUD_QUOTA_PROJECT;
}
});

it('should include quota project id in headers when GOOGLE_CLOUD_QUOTA_PROJECT is set', () => {
const reqData = { request: 'data' };
const stub = sinon
.stub(HttpClient.prototype, 'send')
.resolves(utils.responseFrom({}, 200));
stubs.push(stub);
process.env.GOOGLE_CLOUD_QUOTA_PROJECT = 'test-project-id';
const client = new AuthorizedHttpClient(mockApp);
return client.send({
method: 'POST',
url: mockUrl,
data: reqData,
})
.then(() => {
expect(stub).to.have.been.calledOnce.and.calledWith({
method: 'POST',
url: mockUrl,
headers: {
...requestHeaders.reqheaders,
'x-goog-user-project': 'test-project-id',
'X-Goog-Api-Client': getMetricsHeader(),
},
data: reqData
});
});
});
});

it('should not mutate the arguments', () => {
const reqData = { request: 'data' };
const options = {
Expand Down

0 comments on commit 4ee1bb2

Please sign in to comment.