UNPKG

owltech

Version:
658 lines 25.2 kB
"use strict"; /** * Copyright 2019 Google LLC. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const child_process_1 = require("child_process"); const fs = require("fs"); const gcpMetadata = require("gcp-metadata"); const os = require("os"); const path = require("path"); const crypto_1 = require("../crypto/crypto"); const isbrowser_1 = require("../isbrowser"); const messages = require("../messages"); const transporters_1 = require("../transporters"); const computeclient_1 = require("./computeclient"); const envDetect_1 = require("./envDetect"); const jwtclient_1 = require("./jwtclient"); const refreshclient_1 = require("./refreshclient"); exports.CLOUD_SDK_CLIENT_ID = '764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com'; class GoogleAuth { constructor(opts) { /** * Caches a value indicating whether the auth layer is running on Google * Compute Engine. * @private */ this.checkIsGCE = undefined; // To save the contents of the JSON credential file this.jsonContent = null; this.cachedCredential = null; opts = opts || {}; this._cachedProjectId = opts.projectId || null; this.keyFilename = opts.keyFilename || opts.keyFile; this.scopes = opts.scopes; this.jsonContent = opts.credentials || null; this.clientOptions = opts.clientOptions; } // Note: this properly is only public to satisify unit tests. // https://github.com/Microsoft/TypeScript/issues/5228 get isGCE() { return this.checkIsGCE; } getDefaultProjectId(callback) { messages.warn(messages.DEFAULT_PROJECT_ID_DEPRECATED); if (callback) { this.getProjectIdAsync().then(r => callback(null, r), callback); } else { return this.getProjectIdAsync(); } } getProjectId(callback) { if (callback) { this.getProjectIdAsync().then(r => callback(null, r), callback); } else { return this.getProjectIdAsync(); } } getProjectIdAsync() { if (this._cachedProjectId) { return Promise.resolve(this._cachedProjectId); } // In implicit case, supports three environments. In order of precedence, // the implicit environments are: // - GCLOUD_PROJECT or GOOGLE_CLOUD_PROJECT environment variable // - GOOGLE_APPLICATION_CREDENTIALS JSON file // - Cloud SDK: `gcloud config config-helper --format json` // - GCE project ID from metadata server) if (!this._getDefaultProjectIdPromise) { this._getDefaultProjectIdPromise = new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { try { const projectId = this.getProductionProjectId() || (yield this.getFileProjectId()) || (yield this.getDefaultServiceProjectId()) || (yield this.getGCEProjectId()); this._cachedProjectId = projectId; resolve(projectId); } catch (e) { reject(e); } })); } return this._getDefaultProjectIdPromise; } getApplicationDefault(optionsOrCallback = {}, callback) { let options; if (typeof optionsOrCallback === 'function') { callback = optionsOrCallback; } else { options = optionsOrCallback; } if (callback) { this.getApplicationDefaultAsync(options).then(r => callback(null, r.credential, r.projectId), callback); } else { return this.getApplicationDefaultAsync(options); } } getApplicationDefaultAsync(options) { return __awaiter(this, void 0, void 0, function* () { // If we've already got a cached credential, just return it. if (this.cachedCredential) { return { credential: this.cachedCredential, projectId: yield this.getProjectIdAsync() }; } let credential; let projectId; // Check for the existence of a local environment variable pointing to the // location of the credential file. This is typically used in local // developer scenarios. credential = yield this._tryGetApplicationCredentialsFromEnvironmentVariable(options); if (credential) { if (credential instanceof jwtclient_1.JWT) { credential.scopes = this.scopes; } this.cachedCredential = credential; projectId = yield this.getProjectId(); return { credential, projectId }; } // Look in the well-known credential file location. credential = yield this._tryGetApplicationCredentialsFromWellKnownFile(options); if (credential) { if (credential instanceof jwtclient_1.JWT) { credential.scopes = this.scopes; } this.cachedCredential = credential; projectId = yield this.getProjectId(); return { credential, projectId }; } // Determine if we're running on GCE. let isGCE; try { isGCE = yield this._checkIsGCE(); } catch (e) { throw new Error('Unexpected error determining execution environment: ' + e.message); } if (!isGCE) { // We failed to find the default credentials. Bail out with an error. throw new Error('Could not load the default credentials. Browse to https://cloud.google.com/docs/authentication/getting-started for more information.'); } // For GCE, just return a default ComputeClient. It will take care of // the rest. this.cachedCredential = new computeclient_1.Compute(options); projectId = yield this.getProjectId(); return { projectId, credential: this.cachedCredential }; }); } /** * Determines whether the auth layer is running on Google Compute Engine. * @returns A promise that resolves with the boolean. * @api private */ _checkIsGCE() { return __awaiter(this, void 0, void 0, function* () { if (this.checkIsGCE === undefined) { this.checkIsGCE = yield gcpMetadata.isAvailable(); } return this.checkIsGCE; }); } /** * Attempts to load default credentials from the environment variable path.. * @returns Promise that resolves with the OAuth2Client or null. * @api private */ _tryGetApplicationCredentialsFromEnvironmentVariable(options) { return __awaiter(this, void 0, void 0, function* () { const credentialsPath = process.env['GOOGLE_APPLICATION_CREDENTIALS'] || process.env['google_application_credentials']; if (!credentialsPath || credentialsPath.length === 0) { return null; } try { return this._getApplicationCredentialsFromFilePath(credentialsPath, options); } catch (e) { throw this.createError('Unable to read the credential file specified by the GOOGLE_APPLICATION_CREDENTIALS environment variable.', e); } }); } /** * Attempts to load default credentials from a well-known file location * @return Promise that resolves with the OAuth2Client or null. * @api private */ _tryGetApplicationCredentialsFromWellKnownFile(options) { return __awaiter(this, void 0, void 0, function* () { // First, figure out the location of the file, depending upon the OS type. let location = null; if (this._isWindows()) { // Windows location = process.env['APPDATA']; } else { // Linux or Mac const home = process.env['HOME']; if (home) { location = this._pathJoin(home, '.config'); } } // If we found the root path, expand it. if (location) { location = this._pathJoin(location, 'gcloud'); location = this._pathJoin(location, 'application_default_credentials.json'); location = this._mockWellKnownFilePath(location); // Check whether the file exists. if (!this._fileExists(location)) { location = null; } } // The file does not exist. if (!location) { return null; } // The file seems to exist. Try to use it. const client = yield this._getApplicationCredentialsFromFilePath(location, options); this.warnOnProblematicCredentials(client); return client; }); } /** * Attempts to load default credentials from a file at the given path.. * @param filePath The path to the file to read. * @returns Promise that resolves with the OAuth2Client * @api private */ _getApplicationCredentialsFromFilePath(filePath, options = {}) { return __awaiter(this, void 0, void 0, function* () { // Make sure the path looks like a string. if (!filePath || filePath.length === 0) { throw new Error('The file path is invalid.'); } // Make sure there is a file at the path. lstatSync will throw if there is // nothing there. try { // Resolve path to actual file in case of symlink. Expect a thrown error // if not resolvable. filePath = fs.realpathSync(filePath); if (!fs.lstatSync(filePath).isFile()) { throw new Error(); } } catch (err) { throw this.createError(`The file at ${filePath} does not exist, or it is not a file.`, err); } // Now open a read stream on the file, and parse it. try { const readStream = this._createReadStream(filePath); return this.fromStream(readStream, options); } catch (err) { throw this.createError(`Unable to read the file at ${filePath}.`, err); } }); } /** * Credentials from the Cloud SDK that are associated with Cloud SDK's project * are problematic because they may not have APIs enabled and have limited * quota. If this is the case, warn about it. */ warnOnProblematicCredentials(client) { if (client.email === exports.CLOUD_SDK_CLIENT_ID) { messages.warn(messages.PROBLEMATIC_CREDENTIALS_WARNING); } } /** * Create a credentials instance using the given input options. * @param json The input object. * @param options The JWT or UserRefresh options for the client * @returns JWT or UserRefresh Client with data */ fromJSON(json, options) { let client; if (!json) { throw new Error('Must pass in a JSON object containing the Google auth settings.'); } this.jsonContent = json; options = options || {}; if (json.type === 'authorized_user') { client = new refreshclient_1.UserRefreshClient(options); } else { options.scopes = this.scopes; client = new jwtclient_1.JWT(options); } client.fromJSON(json); return client; } fromStream(inputStream, optionsOrCallback = {}, callback) { let options = {}; if (typeof optionsOrCallback === 'function') { callback = optionsOrCallback; } else { options = optionsOrCallback; } if (callback) { this.fromStreamAsync(inputStream, options) .then(r => callback(null, r), callback); } else { return this.fromStreamAsync(inputStream, options); } } fromStreamAsync(inputStream, options) { return new Promise((resolve, reject) => { if (!inputStream) { throw new Error('Must pass in a stream containing the Google auth settings.'); } let s = ''; inputStream.setEncoding('utf8') .on('error', reject) .on('data', (chunk) => s += chunk) .on('end', () => { try { const data = JSON.parse(s); const r = this.fromJSON(data, options); return resolve(r); } catch (err) { return reject(err); } }); }); } /** * Create a credentials instance using the given API key string. * @param apiKey The API key string * @param options An optional options object. * @returns A JWT loaded from the key */ fromAPIKey(apiKey, options) { options = options || {}; const client = new jwtclient_1.JWT(options); client.fromAPIKey(apiKey); return client; } /** * Determines whether the current operating system is Windows. * @api private */ _isWindows() { const sys = this._osPlatform(); if (sys && sys.length >= 3) { if (sys.substring(0, 3).toLowerCase() === 'win') { return true; } } return false; } /** * Creates a file stream. Allows mocking. * @api private */ _createReadStream(filePath) { return fs.createReadStream(filePath); } /** * Gets the current operating system platform. Allows mocking. * @api private */ _osPlatform() { return os.platform(); } /** * Determines whether a file exists. Allows mocking. * @api private */ _fileExists(filePath) { return fs.existsSync(filePath); } /** * Joins two parts of a path. Allows mocking. * @api private */ _pathJoin(item1, item2) { return path.join(item1, item2); } /** * Allows mocking of the path to a well-known file. * @api private */ _mockWellKnownFilePath(filePath) { return filePath; } // Creates an Error containing the given message, and includes the message // from the optional err passed in. createError(message, err) { let s = message || ''; if (err) { const errorMessage = String(err); if (errorMessage && errorMessage.length > 0) { if (s.length > 0) { s += ' '; } s += errorMessage; } } return Error(s); } /** * Run the Google Cloud SDK command that prints the default project ID */ getDefaultServiceProjectId() { return __awaiter(this, void 0, void 0, function* () { return new Promise(resolve => { child_process_1.exec('gcloud config config-helper --format json', (err, stdout, stderr) => { if (!err && stdout) { try { const projectId = JSON.parse(stdout).configuration.properties.core.project; resolve(projectId); return; } catch (e) { // ignore errors } } resolve(null); }); }); }); } /** * Loads the project id from environment variables. * @api private */ getProductionProjectId() { return process.env['GCLOUD_PROJECT'] || process.env['GOOGLE_CLOUD_PROJECT'] || process.env['gcloud_project'] || process.env['google_cloud_project']; } /** * Loads the project id from the GOOGLE_APPLICATION_CREDENTIALS json file. * @api private */ getFileProjectId() { return __awaiter(this, void 0, void 0, function* () { if (this.cachedCredential) { // Try to read the project ID from the cached credentials file return this.cachedCredential.projectId; } // Ensure the projectId is loaded from the keyFile if available. if (this.keyFilename) { const creds = yield this.getClient(); if (creds && creds.projectId) { return creds.projectId; } } // Try to load a credentials file and read its project ID const r = yield this._tryGetApplicationCredentialsFromEnvironmentVariable(); if (r) { return r.projectId; } else { return null; } }); } /** * Gets the Compute Engine project ID if it can be inferred. */ getGCEProjectId() { return __awaiter(this, void 0, void 0, function* () { try { const r = yield gcpMetadata.project('project-id'); return r; } catch (e) { // Ignore any errors return null; } }); } getCredentials(callback) { if (callback) { this.getCredentialsAsync().then(r => callback(null, r), callback); } else { return this.getCredentialsAsync(); } } getCredentialsAsync() { return __awaiter(this, void 0, void 0, function* () { yield this.getClient(); if (this.jsonContent) { const credential = { client_email: this.jsonContent.client_email, private_key: this.jsonContent.private_key }; return credential; } const isGCE = yield this._checkIsGCE(); if (!isGCE) { throw new Error('Unknown error.'); } // For GCE, return the service account details from the metadata server // NOTE: The trailing '/' at the end of service-accounts/ is very important! // The GCF metadata server doesn't respect querystring params if this / is // not included. const data = yield gcpMetadata.instance({ property: 'service-accounts/', params: { recursive: 'true' } }); if (!data || !data.default || !data.default.email) { throw new Error('Failure from metadata server.'); } return { client_email: data.default.email }; }); } /** * Automatically obtain a client based on the provided configuration. If no * options were passed, use Application Default Credentials. */ getClient(options) { return __awaiter(this, void 0, void 0, function* () { if (options) { this.keyFilename = options.keyFilename || options.keyFile || this.keyFilename; this.scopes = options.scopes || this.scopes; this.jsonContent = options.credentials || this.jsonContent; this.clientOptions = options.clientOptions; } if (!this.cachedCredential) { if (this.jsonContent) { this.cachedCredential = yield this.fromJSON(this.jsonContent, this.clientOptions); } else if (this.keyFilename) { const filePath = path.resolve(this.keyFilename); const stream = fs.createReadStream(filePath); this.cachedCredential = yield this.fromStreamAsync(stream, this.clientOptions); } else { yield this.getApplicationDefaultAsync(this.clientOptions); } } return this.cachedCredential; }); } /** * Automatically obtain application default credentials, and return * an access token for making requests. */ getAccessToken() { return __awaiter(this, void 0, void 0, function* () { const client = yield this.getClient(); return (yield client.getAccessToken()).token; }); } /** * Obtain the HTTP headers that will provide authorization for a given * request. */ getRequestHeaders(url) { return __awaiter(this, void 0, void 0, function* () { const client = yield this.getClient(); return client.getRequestHeaders(url); }); } /** * Obtain credentials for a request, then attach the appropriate headers to * the request options. * @param opts Axios or Request options on which to attach the headers */ authorizeRequest(opts) { return __awaiter(this, void 0, void 0, function* () { opts = opts || {}; const url = opts.url || opts.uri; const client = yield this.getClient(); const headers = yield client.getRequestHeaders(url); opts.headers = Object.assign(opts.headers || {}, headers); return opts; }); } /** * Automatically obtain application default credentials, and make an * HTTP request using the given options. * @param opts Axios request options for the HTTP request. */ // tslint:disable-next-line no-any request(opts) { return __awaiter(this, void 0, void 0, function* () { const client = yield this.getClient(); return client.request(opts); }); } /** * Determine the compute environment in which the code is running. */ getEnv() { return envDetect_1.getEnv(); } /** * Sign the given data with the current private key, or go out * to the IAM API to sign it. * @param data The data to be signed. */ sign(data) { return __awaiter(this, void 0, void 0, function* () { const client = yield this.getClient(); const crypto = crypto_1.createCrypto(); if (client instanceof jwtclient_1.JWT && client.key && !isbrowser_1.isBrowser()) { const sign = crypto.createSign('RSA-SHA256'); sign.update(data); return sign.sign(client.key, 'base64'); } const projectId = yield this.getProjectId(); if (!projectId) { throw new Error('Cannot sign data without a project ID.'); } const creds = yield this.getCredentials(); if (!creds.client_email) { throw new Error('Cannot sign data without `client_email`.'); } const id = `projects/${projectId}/serviceAccounts/${creds.client_email}`; const res = yield this.request({ method: 'POST', url: `https://iam.googleapis.com/v1/${id}:signBlob`, data: { bytesToSign: crypto.encodeBase64StringUtf8(data) } }); return res.data.signature; }); } } /** * Export DefaultTransporter as a static property of the class. */ GoogleAuth.DefaultTransporter = transporters_1.DefaultTransporter; exports.GoogleAuth = GoogleAuth; //# sourceMappingURL=googleauth.js.map