@zowe/imperative
Version:
framework for building configurable CLIs
319 lines • 14.4 kB
JavaScript
"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.ConfigSecure = void 0;
const fs = require("fs");
const JSONC = require("comment-json");
const lodash = require("lodash");
const ConfigApi_1 = require("./ConfigApi");
const ConfigConstants_1 = require("../ConfigConstants");
const security_1 = require("../../../security");
const ConfigUtils_1 = require("../ConfigUtils");
const EventConstants_1 = require("../../../events/src/EventConstants");
const EventOperator_1 = require("../../../events/src/EventOperator");
/**
* API Class for manipulating config layers.
*/
class ConfigSecure extends ConfigApi_1.ConfigApi {
// _______________________________________________________________________
/**
* Load the secure application properties from secure storage using the
* specified vault interface. The vault interface is placed into our
* Config object. The secure values are placed into our Config layers.
*
* @param vault Interface for loading and saving to secure storage.
*/
load(vault) {
return __awaiter(this, void 0, void 0, function* () {
if (vault != null) {
this.mConfig.mVault = vault;
}
if (this.mConfig.mVault == null)
return;
// load the secure fields
try {
const s = yield this.mConfig.mVault.load(ConfigConstants_1.ConfigConstants.SECURE_ACCT);
if (s == null)
return;
// Typecasting because of this issue: https://github.com/kaelzhang/node-comment-json/issues/42
this.mConfig.mSecure = JSONC.parse(s);
}
catch (error) {
this.mLoadFailed = true;
throw error;
}
this.mLoadFailed = false;
// populate each layers properties
this.mConfig.mLayers.forEach(this.loadCached.bind(this));
});
}
// _______________________________________________________________________
/**
* Copy secure config properties from cached secure properties into the
* specified layer. To load secure config properties directly from the
* vault, use the asynchronous method `load` instead.
*
* @internal
* @param opts The user and global flags that specify one of the four
* config files (aka layers).
*/
loadCached(opts) {
if (this.mConfig.mVault == null)
return;
const layer = opts ? this.mConfig.findLayer(opts.user, opts.global) : this.mConfig.layerActive();
// Find the matching layer
for (const [filePath, secureProps] of Object.entries(this.mConfig.mSecure)) {
if (filePath === layer.path) {
// Only set those indicated by the config
for (const p of this.secureFields(layer)) {
// Extract and set secure properties
for (const [sPath, sValue] of Object.entries(secureProps)) {
if (sPath === p) {
const segments = sPath.split(".");
let obj = layer.properties;
for (let x = 0; x < segments.length; x++) {
const segment = segments[x];
if (x === segments.length - 1) {
obj[segment] = sValue;
break;
}
obj = obj[segment];
if (obj == null)
break;
}
}
}
}
}
}
}
// _______________________________________________________________________
/**
* Save the secure application properties into secure storage using
* the vault interface from our config object.
*
* @param allLayers Specify true to save all config layers instead of only the active one
*/
save(allLayers) {
return __awaiter(this, void 0, void 0, function* () {
if (this.mConfig.mVault == null)
return;
const beforeLen = Object.keys(this.mConfig.mSecure).length;
// Build the entries for each layer
for (const { user, global } of this.mConfig.mLayers) {
if (allLayers || user === this.mConfig.mActive.user && global === this.mConfig.mActive.global) {
this.cacheAndPrune({ user, global });
}
}
// Save the entries if needed
if (Object.keys(this.mConfig.mSecure).length > 0 || beforeLen > 0) {
yield this.directSave();
}
});
}
// _______________________________________________________________________
/**
* Save secure properties to the vault without rebuilding the JSON object.
* @internal
*/
directSave() {
return __awaiter(this, void 0, void 0, function* () {
yield this.mConfig.mVault.save(ConfigConstants_1.ConfigConstants.SECURE_ACCT, JSONC.stringify(this.mConfig.mSecure));
EventOperator_1.EventOperator.getZoweProcessor().emitZoweEvent(EventConstants_1.ZoweUserEvents.ON_VAULT_CHANGED);
});
}
// _______________________________________________________________________
/**
* Copy secure config properties from the specified layer into cached
* secure properties. To save secure config properties directly to the
* vault, use the asynchronous method `save` instead.
*
* Warning: Do not pass an `IConfigLayer` object into this method unless
* you want its properties to be edited.
*
* @internal
* @param opts The user and global flags that specify one of the four
* config files (aka layers).
* @param opts.properties `IConfig` object cloned from the specified layer.
* If specified, secure properties will be removed.
*/
cacheAndPrune(opts) {
var _a;
const layer = opts ? this.mConfig.findLayer(opts.user, opts.global) : this.mConfig.layerActive();
// Create all the secure property entries
const sp = {};
for (const path of this.secureFields(layer)) {
const segments = path.split(".");
let obj = (_a = opts === null || opts === void 0 ? void 0 : opts.properties) !== null && _a !== void 0 ? _a : layer.properties;
for (let x = 0; x < segments.length; x++) {
const segment = segments[x];
const value = obj[segment];
if (value == null)
break;
if (x === segments.length - 1) {
sp[path] = value;
if ((opts === null || opts === void 0 ? void 0 : opts.properties) != null) {
delete obj[segment];
}
break;
}
obj = obj[segment];
}
}
if (this.mConfig.mVault != null) {
// Clear the entry and rebuild it
delete this.mConfig.mSecure[layer.path];
// Create the entry to set the secure properties
if (Object.keys(sp).length > 0) {
this.mConfig.mSecure[layer.path] = sp;
}
}
}
// _______________________________________________________________________
/**
* List full paths of all secure properties found in a team config file.
*
* @param opts Specify `true` to get secure fields for all layers. Specify `false` to get secure fields for the active layer
* @param opts.user The user flag that specifies the user-related config files (aka layers).
* @param opts.global The global flag that specifies the project-related config files (aka layers).
* @default opts = false
* @returns Array of secure property paths
* (e.g., "profiles.lpar1.properties.password")
*/
secureFields(opts = false) {
if (opts === true) {
return this.findSecure(this.mConfig.mProperties.profiles, "profiles");
}
if (opts === false) {
return this.findSecure(this.mConfig.layerActive().properties.profiles, "profiles");
}
return this.findSecure(this.mConfig.findLayer(opts.user, opts.global).properties.profiles, "profiles");
}
// _______________________________________________________________________
/**
* List names of secure properties for a profile. They may be defined at
* the profile's level, or at a higher level if the config is nested.
* @param profileName Profile name to search for
* @returns Array of secure property names
*/
securePropsForProfile(profileName) {
const profilePath = this.mConfig.api.profiles.getProfilePathFromName(profileName);
const secureProps = new Set();
for (const propPath of this.findSecure(this.mConfig.mProperties.profiles, "profiles")) {
const pathSegments = propPath.split("."); // profiles.XXX.properties.YYY
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
const propProfilePath = pathSegments.slice(0, -2).join(".");
if (ConfigUtils_1.ConfigUtils.jsonPathMatches(profilePath, propProfilePath)) {
secureProps.add(pathSegments.pop());
}
}
return [...secureProps];
}
/**
* Recursively find secure property paths inside a team config
* "profiles" object.
* @internal
* @param profiles The "profiles" object that is present at the top level
* of team config files, and may also be present at lower
* levels.
* @param path The JSON path to the "profiles" object
* @returns Array of secure property paths
*/
findSecure(profiles, path) {
const secureProps = [];
for (const profName of Object.keys(profiles)) {
for (const propName of profiles[profName].secure || []) {
secureProps.push(`${path}.${profName}.properties.${propName}`);
}
if (profiles[profName].profiles != null) {
secureProps.push(...this.findSecure(profiles[profName].profiles, `${path}.${profName}.profiles`));
}
}
return secureProps;
}
/**
* Retrieve secure properties for a given layer path
*
* @param layerPath Path of the layer to get secure properties for
* @returns the secure properties for the given layer, or null if not found
*/
securePropsForLayer(layerPath) {
const secureLayer = Object.keys(this.mConfig.mSecure).find(osLocation => osLocation === layerPath);
return secureLayer ? this.mConfig.mSecure[secureLayer] : null;
}
/**
* Retrieve info that can be used to store a profile property securely.
*
* For example, to securely store "profiles.lpar1.properties.password", the
* name "password" would be stored in "profiles.lpar1.secure".
*
* @internal
* @param propertyPath The full path of the profile property
* @param findUp Specify true to search up in the config file for higher level secure arrays
* @returns Object with the following attributes:
* - `path` The JSON path of the secure array
* - `prop` The name of the property
*/
secureInfoForProp(propertyPath, findUp) {
var _a;
if (!propertyPath.includes(".properties.")) {
return;
}
const pathSegments = propertyPath.split("."); // profiles.XXX.properties.YYY
const secureProp = pathSegments.pop();
let securePath = propertyPath.replace(/\.properties.+/, ".secure");
if (findUp) {
const layer = this.mConfig.layerActive();
while (layer.exists && pathSegments.length > 2) {
pathSegments.pop();
const testSecurePath = pathSegments.join(".") + ".secure";
if ((_a = lodash.get(layer.properties, testSecurePath)) === null || _a === void 0 ? void 0 : _a.includes(secureProp)) {
securePath = testSecurePath;
break;
}
}
}
return { path: securePath, prop: secureProp };
}
/**
* Delete secure properties stored for team config files that do not exist.
* @returns Array of file paths for which properties were deleted
*/
rmUnusedProps() {
const prunedFiles = [];
for (const filename of Object.keys(this.mConfig.mSecure)) {
if (!fs.existsSync(filename)) {
delete this.mConfig.mSecure[filename];
prunedFiles.push(filename);
}
}
return prunedFiles;
}
/**
* Return true if the secure load method was called and threw an error, or
* it was never called because the CredentialManager failed to initialize.
*/
get loadFailed() {
return this.mLoadFailed != null ? this.mLoadFailed : !security_1.CredentialManagerFactory.initialized;
}
}
exports.ConfigSecure = ConfigSecure;
//# sourceMappingURL=ConfigSecure.js.map