@zowe/imperative
Version:
framework for building configurable CLIs
203 lines • 12.1 kB
JavaScript
/*
* 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.CredentialManagerFactory = void 0;
const AbstractCredentialManager_1 = require("./abstract/AbstractCredentialManager");
const error_1 = require("../../error");
const DefaultCredentialManager_1 = require("./DefaultCredentialManager");
/**
* This is a wrapper class that controls access to the credential manager used within
* the Imperative framework. All calls to the credential manager done by Imperative
* must go through this class for security reasons.
*/
class CredentialManagerFactory {
/**
* Initialize the credential manager, then lock the door and throw away the
* key. This method can only be called once and should be called by {@link Imperative.init}
* immediately after the CLI configuration has been loaded.
*
* This is where any Credential Manager your cli provides will be initialized. First
* Imperative will instantiate your manager (or the {@link DefaultCredentialManager} if none was provided to
* {@link Imperative.init}) and will then call your class's initialize method.
*
* ### Dynamic Import of Module
*
* This method will perform a dynamic import of your {@link IImperativeOverrides.CredentialManager} module when the
* Manager parameter is passed as a string. If anything goes wrong during this import or if the module that was exported
* doesn't extend the {@link AbstractCredentialManager}, this method will throw an error.
*
* @see {@link IImperativeOverrides.CredentialManager}
*
* ### Immutable Class Creation
*
* After this method is complete, the instantiated credential manager will no longer allow changes
* to it's direct variable assignments. This means that even your class can only change the values of it's direct
* properties in the constructor and the initialize method. However, this does not prevent you from changing values
* of properties of one of your classes objects.
*
* For example, after initialization, your class can not do something like this: `this.someProp = 5`. This will result
* in a JavaScript "Cannot assign to read only property" exception because your class is immutable.
* You still will be able to do stuff like this if someProp was already an object: `this.someProp.someValue = 5`. This
* occurs because while Imperative marks your class as immutable (using Object.freeze) the underlying `this.someProp`
* object is still mutable.
*
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze
*
* ### Plugin Provided Overrides
*
* This class attempts to handle a failed plugin override as well. If this method errors out because of problems
* with the `Manager` parameter, it will check to see if the override was provided by a plugin loaded in the
* {@link PluginManagementFacility}. The check is done by looking at the value present in the {@link AppSettings#settings}
* of the singleton present in {@link AppSettings.instance}
*
* If the it was detected that the Manager was not provided by a plugin, the error encountered is thrown to
* the calling function.
*
* If the initialization option "invalidOnFailure" is true, we will default to using the {@link InvalidCredentialManager}
* which implements the {@link AbstractCredentialManager} methods. All these methods have been designed to throw
* the error we caught in the **CredentialManagerFactory.initialize** function.
*
* @param {ICredentialManagerInit} params - Initialization parameters, see interface for details.
*
* @throws {@link ImperativeError} When it has been detected that this method has been called before.
* It is important that this method only executes once.
*
* @throws {@link ImperativeError} When the module specified by the Manager string references a module that
* does not extend {@link AbstractCredentialManager} and the override was not provided by a plugin.
* When the override is provided by a plugin, we will fall back to the {@link InvalidCredentialManager}.
*/
static initialize(params) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c;
// The means to override the CredMgr exists, but we only use our built-in CredMgr.
if (params.service == null) {
params.service = DefaultCredentialManager_1.DefaultCredentialManager.SVC_NAME;
}
// If the display name is not passed, use the cli name
const displayName = (_a = params.displayName) !== null && _a !== void 0 ? _a : params.service;
// If a manager override was not passed, use the default keytar manager
const Manager = (_b = params.Manager) !== null && _b !== void 0 ? _b : DefaultCredentialManager_1.DefaultCredentialManager;
// Default invalid on failure to false if not specified
(_c = params.invalidOnFailure) !== null && _c !== void 0 ? _c : (params.invalidOnFailure = false);
if (this.mManager != null) {
// Something tried to change the already existing credential manager, we should stop this.
throw new error_1.ImperativeError({
msg: "A call to CredentialManagerFactory.initialize has already been made! This method can only be called once",
});
}
try {
let manager;
// Dynamically determine which manager to load.
if (typeof Manager === "string") {
// In the case of a string, we make the assumption that it is pointing to the absolute file path of something
// that exports a manager class. So we'll load that class and initialize it with the same constructor parameters
// that we would do with an actual constructor parameter.
const LoadedManager = yield Promise.resolve(`${Manager}`).then(s => require(s));
manager = new LoadedManager(params.service, displayName);
}
else {
manager = new Manager(params.service, displayName);
}
// After constructing the object, we will ensure that the thing loaded is indeed an
// instance of an abstract credential manager. Since we cannot assume that our internal
// load of a plugin provided a correct object to the function :/
if (manager instanceof AbstractCredentialManager_1.AbstractCredentialManager) {
this.mManager = manager;
}
else {
const message = typeof Manager === "string" ?
`The manager provided at ${Manager} does not extend AbstractCredentialManager properly!` :
"A bad object was provided to the CredentialManagerFactory.initialize() method. This could be " +
"due to a bad plugin.";
throw new error_1.ImperativeError({
msg: message
});
}
if (this.mManager.initialize) {
yield this.mManager.initialize();
const { Logger } = yield Promise.resolve().then(() => require("../../logger"));
Logger.getImperativeLogger().debug(`Initialized the "${displayName}" credential manager for "${params.service}".`);
}
}
catch (error) {
// Perform dynamic requires when an error happens
const { InvalidCredentialManager } = yield Promise.resolve().then(() => require("./InvalidCredentialManager"));
const { Logger } = yield Promise.resolve().then(() => require("../../logger"));
// Log appropriate error messages
if (Manager !== DefaultCredentialManager_1.DefaultCredentialManager) {
const logError = `Failed to load the credential manager named "${displayName}"`;
// Be sure to log the messages both to the console and to a file
// so that support can also see these messages.
Logger.getImperativeLogger().error(logError);
Logger.getConsoleLogger().error(logError);
Logger.getImperativeLogger().error(error.toString());
Logger.getConsoleLogger().error(error.toString());
}
// If requested, we will instantiate the credential manager with the invalid credential manager,
// which will cause any usage of the manager to fail with an error.
if (params.invalidOnFailure) {
this.mManager = new InvalidCredentialManager(params.service, error);
}
else {
this.mManager = undefined;
// The crash was caused by a bad override provided by a base cli
// so this should be thrown up as a hard crash
throw error;
}
}
// Freeze both the wrapper class and the credential manager we just created
// to prevent anyone from making changes to this class after the first
// initialization. This plugs up a security hole so that a plugin can never
// trash the security manager created on init.
Object.freeze(this);
Object.freeze(this.mManager);
});
}
/**
* @returns {AbstractCredentialManager} - The credential manager that Imperative should use to
* retrieve user credentials.
*
* @throws {ImperativeError} - When the Credential Manager has not been initialized yet.
*/
static get manager() {
if (this.mManager == null) {
throw new error_1.ImperativeError({
msg: "Credential Manager not yet initialized! CredentialManagerFactory.initialize must " +
"be called prior to CredentialManagerFactory.mananger"
});
}
return this.mManager;
}
/**
* The credential manager may not be initialized if Keytar (or a plugin override) is not present. In this
* scenario, the default is to store credentials in plain text.
* @returns {boolean} - True if the credential manager has been initialized.
*/
static get initialized() {
return !(this.mManager == null);
}
}
exports.CredentialManagerFactory = CredentialManagerFactory;
// This also prevents someone being able to hijack the exported object. Essentially all of these Object.freeze
// calls make this class the programming equivalent of Fort Knox. Hard to get in but if you do get in, then
// there are lots of valuables
Object.freeze(exports);
//# sourceMappingURL=CredentialManagerFactory.js.map
;