Skip to content

Commit 4ee1bb2

Browse files
fix: Set the Quota Project ID only for ADC human accounts (#2761)
* fix: Set quota project ID for ADC human accounts * Add header to http/2 calls * add unit tests
1 parent 4babb45 commit 4ee1bb2

10 files changed

+67
-11
lines changed

src/app/credential-internal.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export class ApplicationDefaultCredential implements Credential {
3939
private readonly googleAuth: GoogleAuth;
4040
private authClient: AnyAuthClient;
4141
private projectId?: string;
42+
private quotaProjectId?: string;
4243
private accountId?: string;
4344

4445
constructor(httpAgent?: Agent) {
@@ -58,6 +59,7 @@ export class ApplicationDefaultCredential implements Credential {
5859
}
5960
await this.authClient.getAccessToken();
6061
const credentials = this.authClient.credentials;
62+
this.quotaProjectId = this.authClient.quotaProjectId;
6163
return populateCredential(credentials);
6264
}
6365

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

73+
public getQuotaProjectId(): string | undefined {
74+
if (!this.quotaProjectId) {
75+
this.quotaProjectId = this.authClient?.quotaProjectId;
76+
}
77+
return this.quotaProjectId;
78+
}
79+
7180
public async isComputeEngineCredential(): Promise<boolean> {
7281
if (!this.authClient) {
7382
this.authClient = await this.googleAuth.getClient();

src/utils/api-request.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import url = require('url');
2626
import { EventEmitter } from 'events';
2727
import { Readable } from 'stream';
2828
import * as zlibmod from 'zlib';
29+
import { ApplicationDefaultCredential } from '../app/credential-internal';
2930
import { getMetricsHeader } from '../utils/index';
3031

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

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

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

1119+
let quotaProjectId: string | undefined;
1120+
if (this.app.options.credential instanceof ApplicationDefaultCredential) {
1121+
quotaProjectId = this.app.options.credential.getQuotaProjectId();
1122+
}
1123+
quotaProjectId = process.env.GOOGLE_CLOUD_QUOTA_PROJECT || quotaProjectId;
1124+
if (!requestCopy.headers['x-goog-user-project'] && validator.isNonEmptyString(quotaProjectId)) {
1125+
requestCopy.headers['x-goog-user-project'] = quotaProjectId;
1126+
}
1127+
11151128
requestCopy.headers['X-Goog-Api-Client'] = getMetricsHeader()
11161129

11171130
return super.send(requestCopy);

test/unit/app-check/app-check-api-client-internal.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ describe('AppCheckApiClient', () => {
4545
const EXPECTED_HEADERS = {
4646
'Authorization': 'Bearer mock-token',
4747
'X-Firebase-Client': `fire-admin-node/${getSdkVersion()}`,
48-
'x-goog-user-project': 'test-project',
4948
'X-Goog-Api-Client': getMetricsHeader(),
5049
};
5150

test/unit/data-connect/data-connect-api-client-internal.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ describe('DataConnectApiClient', () => {
4343
const EXPECTED_HEADERS = {
4444
'Authorization': 'Bearer mock-token',
4545
'X-Firebase-Client': `fire-admin-node/${getSdkVersion()}`,
46-
'x-goog-user-project': 'test-project',
4746
'X-Goog-Api-Client': getMetricsHeader(),
4847
};
4948

test/unit/extensions/extensions-api-client-internal.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ describe('Extension API client', () => {
4343
const EXPECTED_HEADERS = {
4444
'Authorization': 'Bearer mock-token',
4545
'X-Firebase-Client': `fire-admin-node/${getSdkVersion()}`,
46-
'x-goog-user-project': 'test-project',
4746
'X-Goog-Api-Client': getMetricsHeader(),
4847
}
4948

test/unit/functions/functions-api-client-internal.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ describe('FunctionsApiClient', () => {
4646
const EXPECTED_HEADERS = {
4747
'X-Firebase-Client': `fire-admin-node/${getSdkVersion()}`,
4848
'Authorization': 'Bearer mock-token',
49-
'x-goog-user-project': 'test-project',
5049
'X-Goog-Api-Client': getMetricsHeader(),
5150
};
5251

test/unit/machine-learning/machine-learning-api-client.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@ describe('MachineLearningApiClient', () => {
117117
const EXPECTED_HEADERS = {
118118
'Authorization': 'Bearer mock-token',
119119
'X-Firebase-Client': `fire-admin-node/${getSdkVersion()}`,
120-
'x-goog-user-project': 'test-project',
121120
'X-Goog-Api-Client': getMetricsHeader(),
122121
};
123122
const noProjectId = 'Failed to determine project ID. Initialize the SDK with service '

test/unit/remote-config/remote-config-api-client.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ describe('RemoteConfigApiClient', () => {
5757
'Authorization': 'Bearer mock-token',
5858
'X-Firebase-Client': `fire-admin-node/${getSdkVersion()}`,
5959
'Accept-Encoding': 'gzip',
60-
'x-goog-user-project': 'test-project',
6160
'X-Goog-Api-Client': getMetricsHeader(),
6261
};
6362

test/unit/security-rules/security-rules-api-client.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ describe('SecurityRulesApiClient', () => {
4444
const EXPECTED_HEADERS = {
4545
'Authorization': 'Bearer mock-token',
4646
'X-Firebase-Client': `fire-admin-node/${getSdkVersion()}`,
47-
'x-goog-user-project': 'test-project',
4847
'X-Goog-Api-Client': getMetricsHeader(),
4948
};
5049
const noProjectId = 'Failed to determine project ID. Initialize the SDK with service '

test/unit/utils/api-request.spec.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
'use strict';
1919

20+
import * as _ from 'lodash';
2021
import * as chai from 'chai';
2122
import * as nock from 'nock';
2223
import * as sinon from 'sinon';
@@ -35,6 +36,7 @@ import {
3536
import { deepCopy } from '../../../src/utils/deep-copy';
3637
import { Agent } from 'http';
3738
import * as zlib from 'zlib';
39+
import { getMetricsHeader } from '../../../src/utils';
3840

3941
chai.should();
4042
chai.use(sinonChai);
@@ -2648,6 +2650,45 @@ describe('AuthorizedHttpClient', () => {
26482650
});
26492651
});
26502652

2653+
describe('Quota Project', () => {
2654+
let stubs: sinon.SinonStub[] = [];
2655+
2656+
afterEach(() => {
2657+
_.forEach(stubs, (stub) => stub.restore());
2658+
stubs = [];
2659+
if (process.env.GOOGLE_CLOUD_QUOTA_PROJECT) {
2660+
delete process.env.GOOGLE_CLOUD_QUOTA_PROJECT;
2661+
}
2662+
});
2663+
2664+
it('should include quota project id in headers when GOOGLE_CLOUD_QUOTA_PROJECT is set', () => {
2665+
const reqData = { request: 'data' };
2666+
const stub = sinon
2667+
.stub(HttpClient.prototype, 'send')
2668+
.resolves(utils.responseFrom({}, 200));
2669+
stubs.push(stub);
2670+
process.env.GOOGLE_CLOUD_QUOTA_PROJECT = 'test-project-id';
2671+
const client = new AuthorizedHttpClient(mockApp);
2672+
return client.send({
2673+
method: 'POST',
2674+
url: mockUrl,
2675+
data: reqData,
2676+
})
2677+
.then(() => {
2678+
expect(stub).to.have.been.calledOnce.and.calledWith({
2679+
method: 'POST',
2680+
url: mockUrl,
2681+
headers: {
2682+
...requestHeaders.reqheaders,
2683+
'x-goog-user-project': 'test-project-id',
2684+
'X-Goog-Api-Client': getMetricsHeader(),
2685+
},
2686+
data: reqData
2687+
});
2688+
});
2689+
});
2690+
});
2691+
26512692
it('should not mutate the arguments', () => {
26522693
const reqData = { request: 'data' };
26532694
const options = {

0 commit comments

Comments
 (0)