Skip to content
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
41 changes: 29 additions & 12 deletions src/auth/credential.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ function getDetailFromResponse(response: HttpResponse): string {
/**
* Implementation of Credential that uses a service account certificate.
*/
export class CertCredential implements Credential {
export class CertCredential implements FirebaseCredential {

private readonly certificate: Certificate;
private readonly httpClient: HttpClient;
Expand Down Expand Up @@ -290,7 +290,32 @@ export class CertCredential implements Credential {
*/
export interface Credential {
getAccessToken(): Promise<GoogleOAuthAccessToken>;
getCertificate(): Certificate;
}

/**
* Internal interface for credentials that can both generate access tokens and may have a Certificate
* associated with them.
*/
export interface FirebaseCredential extends Credential {
getCertificate(): Certificate | null;
}

/**
* Attempts to extract a Certificate from the given credential.
*
* @param {Credential} credential A Credential instance.
* @return {Certificate} A Certificate instance or null.
*/
export function tryGetCertificate(credential: Credential): Certificate | null {
if (isFirebaseCredential(credential)) {
return credential.getCertificate();
}

return null;
}

function isFirebaseCredential(credential: Credential): credential is FirebaseCredential {
return 'getCertificate' in credential;
}

/**
Expand Down Expand Up @@ -326,10 +351,6 @@ export class RefreshTokenCredential implements Credential {
};
return requestAccessToken(this.httpClient, request);
}

public getCertificate(): Certificate {
return null;
}
}


Expand Down Expand Up @@ -358,18 +379,14 @@ export class MetadataServiceCredential implements Credential {
};
return requestAccessToken(this.httpClient, request);
}

public getCertificate(): Certificate {
return null;
}
}


/**
* ApplicationDefaultCredential implements the process for loading credentials as
* described in https://developers.google.com/identity/protocols/application-default-credentials
*/
export class ApplicationDefaultCredential implements Credential {
export class ApplicationDefaultCredential implements FirebaseCredential {
private credential_: Credential;

constructor(httpAgent?: Agent) {
Expand All @@ -393,7 +410,7 @@ export class ApplicationDefaultCredential implements Credential {
}

public getCertificate(): Certificate {
return this.credential_.getCertificate();
return tryGetCertificate(this.credential_);
}

// Used in testing to verify we are delegating to the correct implementation.
Expand Down
6 changes: 3 additions & 3 deletions src/auth/token-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
*/

import { FirebaseApp } from '../firebase-app';
import {Certificate} from './credential';
import {AuthClientErrorCode, FirebaseAuthError, FirebaseError} from '../utils/error';
import {Certificate, tryGetCertificate} from './credential';
import {AuthClientErrorCode, FirebaseAuthError } from '../utils/error';
import { AuthorizedHttpClient, HttpError, HttpRequestConfig, HttpClient } from '../utils/api-request';

import * as validator from '../utils/validator';
Expand Down Expand Up @@ -220,7 +220,7 @@ export class IAMSigner implements CryptoSigner {
* @return {CryptoSigner} A CryptoSigner instance.
*/
export function cryptoSignerFromApp(app: FirebaseApp): CryptoSigner {
const cert = app.options.credential.getCertificate();
const cert = tryGetCertificate(app.options.credential);
if (cert != null && validator.isNonEmptyString(cert.privateKey) && validator.isNonEmptyString(cert.clientEmail)) {
return new ServiceAccountSigner(cert);
}
Expand Down
4 changes: 2 additions & 2 deletions src/firestore/firestore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import {FirebaseApp} from '../firebase-app';
import {FirebaseFirestoreError} from '../utils/error';
import {FirebaseServiceInterface, FirebaseServiceInternalsInterface} from '../firebase-service';
import {ApplicationDefaultCredential, Certificate} from '../auth/credential';
import {ApplicationDefaultCredential, Certificate, tryGetCertificate} from '../auth/credential';
import {Firestore, Settings} from '@google-cloud/firestore';

import * as validator from '../utils/validator';
Expand Down Expand Up @@ -72,7 +72,7 @@ export function getFirestoreOptions(app: FirebaseApp): Settings {
}

const projectId: string = utils.getProjectId(app);
const cert: Certificate = app.options.credential.getCertificate();
const cert: Certificate = tryGetCertificate(app.options.credential);
const { version: firebaseVersion } = require('../../package.json');
if (cert != null) {
// cert is available when the SDK has been initialized with a service account JSON file,
Expand Down
4 changes: 2 additions & 2 deletions src/storage/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import {FirebaseApp} from '../firebase-app';
import {FirebaseError} from '../utils/error';
import {FirebaseServiceInterface, FirebaseServiceInternalsInterface} from '../firebase-service';
import {ApplicationDefaultCredential, Certificate} from '../auth/credential';
import {ApplicationDefaultCredential, Certificate, tryGetCertificate} from '../auth/credential';
import {Bucket, Storage as StorageClient} from '@google-cloud/storage';

import * as validator from '../utils/validator';
Expand Down Expand Up @@ -70,7 +70,7 @@ export class Storage implements FirebaseServiceInterface {
});
}

const cert: Certificate = app.options.credential.getCertificate();
const cert: Certificate = tryGetCertificate(app.options.credential);
if (cert != null) {
// cert is available when the SDK has been initialized with a service account JSON file,
// or by setting the GOOGLE_APPLICATION_CREDENTIALS envrionment variable.
Expand Down
4 changes: 2 additions & 2 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

import {FirebaseApp, FirebaseAppOptions} from '../firebase-app';
import {Certificate} from '../auth/credential';
import {Certificate, tryGetCertificate} from '../auth/credential';

import * as validator from './validator';

Expand Down Expand Up @@ -69,7 +69,7 @@ export function getProjectId(app: FirebaseApp): string {
return options.projectId;
}

const cert: Certificate = options.credential.getCertificate();
const cert: Certificate = tryGetCertificate(options.credential);
if (cert != null && validator.isNonEmptyString(cert.projectId)) {
return cert.projectId;
}
Expand Down
25 changes: 22 additions & 3 deletions test/unit/auth/credential.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import * as mocks from '../../resources/mocks';

import {
ApplicationDefaultCredential, CertCredential, Certificate, GoogleOAuthAccessToken,
MetadataServiceCredential, RefreshTokenCredential,
MetadataServiceCredential, RefreshTokenCredential, tryGetCertificate,
} from '../../../src/auth/credential';
import { HttpClient } from '../../../src/utils/api-request';
import {Agent} from 'https';
Expand Down Expand Up @@ -255,6 +255,15 @@ describe('Credential', () => {
});
});

it('should return a certificate', () => {
const c = new CertCredential(mockCertificateObject);
expect(tryGetCertificate(c)).to.deep.equal({
projectId: mockCertificateObject.project_id,
clientEmail: mockCertificateObject.client_email,
privateKey: mockCertificateObject.private_key,
});
});

it('should create access tokens', () => {
const c = new CertCredential(mockCertificateObject);
return c.getAccessToken().then((token) => {
Expand Down Expand Up @@ -292,7 +301,7 @@ describe('Credential', () => {
describe('RefreshTokenCredential', () => {
it('should not return a certificate', () => {
const c = new RefreshTokenCredential(MOCK_REFRESH_TOKEN_CONFIG);
expect(c.getCertificate()).to.be.null;
expect(tryGetCertificate(c)).to.be.null;
});

it('should create access tokens', () => {
Expand Down Expand Up @@ -322,7 +331,7 @@ describe('Credential', () => {

it('should not return a certificate', () => {
const c = new MetadataServiceCredential();
expect(c.getCertificate()).to.be.null;
expect(tryGetCertificate(c)).to.be.null;
});

it('should create access tokens', () => {
Expand Down Expand Up @@ -428,6 +437,16 @@ describe('Credential', () => {
});
});

it('should return a Certificate', () => {
process.env.GOOGLE_APPLICATION_CREDENTIALS = path.resolve(__dirname, '../../resources/mock.key.json');
const c = new ApplicationDefaultCredential();
expect(tryGetCertificate(c)).to.deep.equal({
projectId: mockCertificateObject.project_id,
clientEmail: mockCertificateObject.client_email,
privateKey: mockCertificateObject.private_key,
});
});

it('should parse valid RefreshTokenCredential if GOOGLE_APPLICATION_CREDENTIALS environment variable ' +
'points to default refresh token location', () => {
process.env.GOOGLE_APPLICATION_CREDENTIALS = GCLOUD_CREDENTIAL_PATH;
Expand Down