UNPKG

@zowe/imperative

Version:
335 lines 15.4 kB
"use strict"; /* * This program and the accompanying materials are made available under the terms of the * Eclipse Public License v2.0 which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-v20.html * * SPDX-License-Identifier: EPL-2.0 * * Copyright Contributors to the Zowe Project. * */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 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) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DefaultCredentialManager = void 0; const AbstractCredentialManager_1 = require("./abstract/AbstractCredentialManager"); const error_1 = require("../../error"); const logger_1 = require("../../logger"); /** * Default Credential Manager is our implementation of the Imperative Credential Manager. This manager invokes methods * created by the keytar utility (https://www.npmjs.com/package/keytar) to access the secure credential vault on the * user's machine. * * ### Keychains Used by Keytar * * | OS | Vault | * |----|----------| * | Windows | Credential Vault | * | macOS | Keychain | * | Linux | Secret Sevice API/libsecret | * * ### Keytar must be installed by the app using imperative (like zowe-cli). * * On Linux, Keytar will not work out of the box without some additional * configuration to install libsecret. Keytar provides the following * documentation for Linux users to install libsecret: * * --- * * Depending on your distribution, you will need to run the following command: * * - Debian/Ubuntu: `sudo apt-get install libsecret-1-dev` * - Red Hat-based: `sudo yum install libsecret-devel` * - Arch Linux: `sudo pacman -S libsecret` */ class DefaultCredentialManager extends AbstractCredentialManager_1.AbstractCredentialManager { /** * Pass-through to the superclass constructor. * * @param {string} service The service string to send to the superclass constructor. * @param {string} displayName The display name for this credential manager to send to the superclass constructor */ constructor(service, displayName = "default credential manager") { // Always ensure that a manager instantiates the super class, even if the // constructor doesn't do anything. Who knows what things might happen in // the abstract class initialization in the future. super(service, displayName); /** * Maximum credential length allowed by Windows 7 and newer. * * We don't support older versions of Windows where the limit is 512 bytes. */ this.WIN32_CRED_MAX_STRING_LENGTH = 2560; /* Gather all services. We will load secure properties for the first * successful service found in the order that they are placed in this array. */ this.allServices = [service || DefaultCredentialManager.SVC_NAME]; if (this.defaultService === DefaultCredentialManager.SVC_NAME) { /* Previous services under which we will look for credentials. * We dropped @brightside/core because we no longer support the * lts-incremental version of the product. */ this.allServices.push("@zowe/cli", "Zowe-Plugin", "Broadcom-Plugin"); } } /** * Called by {@link CredentialManagerFactory.initialize} before the freeze of the object. This * gives us a chance to load keytar into the class before we are locked down. If a load failure * occurs, we will store the error and throw it once a method of this class tries to execute. This * prevents a missing keytar module from stopping all operation of the cli. * * In the future, we could go even further to have keytar load into a sub-object of this class so * that the load doesn't hold up the main class execution. * * @returns {Promise<void>} A promise that the function has completed. */ initialize() { return __awaiter(this, void 0, void 0, function* () { var _a; try { // Imperative overrides the value of require.main.filename to point to // our calling CLI. Since our caller must supply keytar, we search for keytar // within our caller's path. const requireOpts = {}; if (((_a = require.main) === null || _a === void 0 ? void 0 : _a.filename) != null) { requireOpts.paths = [require.main.filename, ...require.resolve.paths("@zowe/secrets-for-zowe-sdk")]; } // use helper function for require.resolve so it can be mocked in jest tests const keytarPath = require.resolve("@zowe/secrets-for-zowe-sdk", requireOpts); logger_1.Logger.getImperativeLogger().debug("Loading Keytar module from", keytarPath); this.keytar = (yield Promise.resolve(`${keytarPath}`).then(s => require(s))).keyring; } catch (error) { this.loadError = new error_1.ImperativeError({ msg: `Failed to load Keytar module: ${error.message}`, causeErrors: error }); logger_1.Logger.getImperativeLogger().debug("Failed to load Keytar module:\n", error.stack); } }); } get possibleSolutions() { return [ `Reinstall ${this.name}. On Linux systems, also make sure to install the prerequisites listed in ${this.name} documentation.`, `Ensure ${this.name} can access secure credential storage. ${this.name} needs access to the OS to securely save credentials.` ]; } /** * Calls the keytar deletePassword service with {@link DefaultCredentialManager#service} and the * account passed to the function by Imperative. * * @param {string} account The account for which to delete the password * * @returns {Promise<void>} A promise that the function has completed. * * @throws {@link ImperativeError} if keytar is not defined. */ deleteCredentials(account) { return __awaiter(this, void 0, void 0, function* () { this.checkForKeytar(); yield this.deleteCredentialsHelper(account); }); } /** * Calls the keytar getPassword service with {@link DefaultCredentialManager#service} and the * account passed to the function by Imperative. * * @param {string} account The account for which to get credentials * @param {boolean} optional Set to true if failure to find credentials should be ignored * * @returns {Promise<SecureCredential>} A promise containing the credentials stored in keytar. * * @throws {@link ImperativeError} if keytar is not defined. * @throws {@link ImperativeError} when keytar.getPassword returns null or undefined. */ loadCredentials(account, optional) { return __awaiter(this, void 0, void 0, function* () { this.checkForKeytar(); // load secure properties using the first successful value from our known services let secureValue = null; for (const nextService of this.allServices) { secureValue = yield this.getCredentialsHelper(nextService, account); if (secureValue != null) { break; } } if (secureValue == null && !optional) { throw new error_1.ImperativeError({ msg: "Unable to load credentials.", additionalDetails: this.getMissingEntryMessage(account) }); } if (secureValue != null) { const impLogger = logger_1.Logger.getImperativeLogger(); impLogger.info("Successfully loaded secure value for service = '" + this.service + "' account = '" + account + "'"); } return secureValue; }); } /** * Calls the keytar setPassword service with {@link DefaultCredentialManager#service} and the * account and credentials passed to the function by Imperative. * * @param {string} account The account to set credentials * @param {SecureCredential} credentials The credentials to store * * @returns {Promise<void>} A promise that the function has completed. * * @throws {@link ImperativeError} if keytar is not defined. */ saveCredentials(account, credentials) { return __awaiter(this, void 0, void 0, function* () { this.checkForKeytar(); yield this.deleteCredentialsHelper(account, true); yield this.setCredentialsHelper(this.service, account, credentials); }); } /** * The default service name for storing credentials. */ get defaultService() { return this.allServices[0]; } /** * This function is called before the {@link deletePassword}, {@link getPassword}, and * {@link setPassword} functions. It will check if keytar is not null and will throw an error * if it is. * * The error thrown will be the contents of {@link loadError} or a new {@link ImperativeError}. * The former error will be the most common one as we expect failures during the load since keytar * is optional. The latter error will indicate that some unknown condition has happened so we will * create a new ImperativeError with the report suppressed. The report is suppressed because it * may be possible that a detailed report could capture a username and password, which would * probably be a bad thing. * * @private * * @throws {@link ImperativeError} when keytar is null or undefined. */ checkForKeytar() { if (this.keytar == null) { if (this.loadError == null) { throw new error_1.ImperativeError({ msg: "Keytar was not properly loaded due to an unknown cause." }); } else { throw this.loadError; } } } /** * Helper to load credentials from vault that supports values longer than * `DefaultCredentialManager.WIN32_CRED_MAX_STRING_LENGTH` on Windows. * @private * @param service The string service name. * @param account The string account name. * @returns A promise for the credential string. */ getCredentialsHelper(service, account) { return __awaiter(this, void 0, void 0, function* () { // Try to load single-field value from vault let value = yield this.keytar.getPassword(service, account); // If not found, try to load multiple-field value on Windows if (value == null && process.platform === "win32") { let index = 1; // Load multiple fields from vault and concat them do { const tempValue = yield this.keytar.getPassword(service, `${account}-${index}`); if (tempValue != null) { value = (value || "") + tempValue; } index++; // Loop until we've finished reading null-terminated value } while (value != null && !value.endsWith('\0')); // Strip off trailing null char if (value != null) { value = value.replace(/\0$/, ""); } } return value; }); } /** * Helper to save credentials to vault that supports values longer than * `DefaultCredentialManager.WIN32_CRED_MAX_STRING_LENGTH` on Windows. * @private * @param service The string service name. * @param account The string account name. * @param value The string credential. */ setCredentialsHelper(service, account, value) { return __awaiter(this, void 0, void 0, function* () { // On Windows, save value across multiple fields if needed if (process.platform === "win32" && value.length > this.WIN32_CRED_MAX_STRING_LENGTH) { // First delete any fields previously used to store this value yield this.keytar.deletePassword(service, account); value += '\0'; let index = 1; while (value.length > 0) { const tempValue = value.slice(0, this.WIN32_CRED_MAX_STRING_LENGTH); yield this.keytar.setPassword(service, `${account}-${index}`, tempValue); value = value.slice(this.WIN32_CRED_MAX_STRING_LENGTH); index++; } } else { // Fall back to simple storage of single-field value yield this.keytar.setPassword(service, account, value); } }); } deleteCredentialsHelper(account, keepCurrentSvc) { return __awaiter(this, void 0, void 0, function* () { let wasDeleted = false; for (const service of this.allServices) { if (keepCurrentSvc && service === this.defaultService) { continue; } if (yield this.keytar.deletePassword(service, account)) { wasDeleted = true; } } if (process.platform === "win32") { // Handle deletion of long values stored across multiple fields let index = 1; while (yield this.keytar.deletePassword(this.defaultService, `${account}-${index}`)) { index++; } if (index > 1) { wasDeleted = true; } } return wasDeleted; }); } getMissingEntryMessage(account) { let listOfServices = ` Service = `; for (const service of this.allServices) { listOfServices += `${service}, `; } const commaAndSpace = 2; listOfServices = listOfServices.slice(0, -1 * commaAndSpace) + `\n Account = ${account}\n\n`; return "Could not find an entry in the credential vault for the following:\n" + listOfServices + "Possible Causes:\n" + " This could have been caused by any manual removal of credentials from your vault.\n\n" + "Resolutions: \n" + " Recreate the credentials in the vault for the particular service in the vault.\n"; } } exports.DefaultCredentialManager = DefaultCredentialManager; /** * The service name for our built-in credential manager. */ DefaultCredentialManager.SVC_NAME = "Zowe"; //# sourceMappingURL=DefaultCredentialManager.js.map