From e6f738e2e48e980dc6e8380dbcf03f49a366683b Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Fri, 23 Aug 2024 16:59:31 -0400 Subject: [PATCH 1/7] feat: add secureFieldsWithDetails :yum: Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- .../imperative/src/config/src/ProfileInfo.ts | 55 ++++++++++++++++++- .../src/config/src/api/ConfigSecure.ts | 12 ++++ .../src/config/src/doc/IConfigSecure.ts | 10 ++++ 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/packages/imperative/src/config/src/ProfileInfo.ts b/packages/imperative/src/config/src/ProfileInfo.ts index 54dcc32713..bca1c0f791 100644 --- a/packages/imperative/src/config/src/ProfileInfo.ts +++ b/packages/imperative/src/config/src/ProfileInfo.ts @@ -16,7 +16,7 @@ import * as lodash from "lodash"; import * as semver from "semver"; // for ProfileInfo structures -import { IProfArgAttrs } from "./doc/IProfArgAttrs"; +import { IProfArgAttrs, IProfDataType } from "./doc/IProfArgAttrs"; import { IProfAttrs } from "./doc/IProfAttrs"; import { IArgTeamConfigLoc, IProfLoc, IProfLocOsLoc, IProfLocOsLocLayer, ProfLocType } from "./doc/IProfLoc"; import { IProfMergeArgOpts } from "./doc/IProfMergeArgOpts"; @@ -50,7 +50,7 @@ import { IProfInfoRemoveKnownPropOpts } from "./doc/IProfInfoRemoveKnownPropOpts import { ConfigUtils } from "./ConfigUtils"; import { ConfigBuilder } from "./ConfigBuilder"; import { IAddProfTypeResult, IExtenderTypeInfo, IExtendersJsonOpts } from "./doc/IExtenderOpts"; -import { IConfigLayer } from ".."; +import { IConfigLayer, ISecureFieldDetails } from ".."; import { Constants } from "../../constants"; /** @@ -1388,6 +1388,57 @@ export class ProfileInfo { return finalSchema; } + // _______________________________________________________________________ + /** + * List of secure properties with more details, like value, location, and type + * + * @param opts The user and global flags that specify one of the four + * config files (aka layers). + * @returns Array of secure property details + */ + public secureFieldsWithDetails(opts?: { user: boolean; global: boolean }): ISecureFieldDetails[] { + const config = this.getTeamConfig(); + const layer = opts ? config.findLayer(opts.user, opts.global) : config.layerActive(); + const fields = config.api.secure.findSecure(layer.properties.profiles, "profiles"); + const vault = config.api.secure.getSecureFieldsForLayer(layer.path); + + const response: ISecureFieldDetails[] = []; + + // Search the vault for each secure field + fields.forEach(fieldPath => { + // Scan the cached contents of the vault + for (const [loc, val] of Object.entries(vault)) { + // Search inside the secure fields for this layer + Object.entries(val).map(([propPath, propValue]) => { + if (propPath === fieldPath) { + response.push({ + path: fieldPath, + name: fieldPath.split(".properties.")[1], + type: this.argDataType(typeof propValue), + value: propValue as IProfDataType, + loc, + }); + } + }); + } + }); + + fields.forEach(fieldPath => { + if (response.find(details => details.path === fieldPath) == null) { + response.push({ + path: fieldPath, + name: fieldPath.split(".properties.")[1], + type: undefined, + value: undefined, + loc: undefined + }); + } + }); + + return response; + } + + // _______________________________________________________________________ /** * Get all of the subprofiles in the configuration. diff --git a/packages/imperative/src/config/src/api/ConfigSecure.ts b/packages/imperative/src/config/src/api/ConfigSecure.ts index 7586cfaab1..8a6178234f 100644 --- a/packages/imperative/src/config/src/api/ConfigSecure.ts +++ b/packages/imperative/src/config/src/api/ConfigSecure.ts @@ -22,6 +22,7 @@ import { CredentialManagerFactory } from "../../../security"; import { ConfigUtils } from "../ConfigUtils"; import { ZoweUserEvents } from "../../../events/src/EventConstants"; import { EventOperator } from "../../../events/src/EventOperator"; +import { IConfigLayer } from "../doc/IConfigLayer"; /** * API Class for manipulating config layers. @@ -242,6 +243,17 @@ export class ConfigSecure extends ConfigApi { return secureProps; } + /** + * Retrieve secure properties for a givne layer path + * + * @param layerPath Path of the layer to get secure properties for + * @returns the secure properties for the given layer + */ + public getSecureFieldsForLayer(layerPath: string): IConfigSecureProperties { + const secureLayer = Object.keys(this.mConfig.mSecure).find(osLocation => osLocation === layerPath); + return secureLayer ? { [secureLayer] : this.mConfig.mSecure[secureLayer] } : null; + } + /** * Retrieve info that can be used to store a profile property securely. * diff --git a/packages/imperative/src/config/src/doc/IConfigSecure.ts b/packages/imperative/src/config/src/doc/IConfigSecure.ts index 283054446e..b66a96b4ca 100644 --- a/packages/imperative/src/config/src/doc/IConfigSecure.ts +++ b/packages/imperative/src/config/src/doc/IConfigSecure.ts @@ -9,6 +9,16 @@ * */ +import { IProfArgValue, IProfDataType } from "./IProfArgAttrs"; + export type IConfigSecureProperties = { [key: string]: any }; export type IConfigSecure = { [path: string]: IConfigSecureProperties }; + +export interface ISecureFieldDetails { + path: string; + name: string; + type: IProfDataType; + value: IProfArgValue; + loc: string; +} \ No newline at end of file From 7461902fe909a1b359aa016d299081264b2bbf3b Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Mon, 26 Aug 2024 10:01:13 -0400 Subject: [PATCH 2/7] Use the IProfArgAttrs interface (tentative) :yum: Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- .../imperative/src/config/src/ProfileInfo.ts | 35 +++++++++++-------- .../src/config/src/api/ConfigSecure.ts | 5 ++- .../src/config/src/doc/IConfigSecure.ts | 10 ------ 3 files changed, 23 insertions(+), 27 deletions(-) diff --git a/packages/imperative/src/config/src/ProfileInfo.ts b/packages/imperative/src/config/src/ProfileInfo.ts index bca1c0f791..1afe64e2e1 100644 --- a/packages/imperative/src/config/src/ProfileInfo.ts +++ b/packages/imperative/src/config/src/ProfileInfo.ts @@ -50,7 +50,7 @@ import { IProfInfoRemoveKnownPropOpts } from "./doc/IProfInfoRemoveKnownPropOpts import { ConfigUtils } from "./ConfigUtils"; import { ConfigBuilder } from "./ConfigBuilder"; import { IAddProfTypeResult, IExtenderTypeInfo, IExtendersJsonOpts } from "./doc/IExtenderOpts"; -import { IConfigLayer, ISecureFieldDetails } from ".."; +import { IConfigLayer } from ".."; import { Constants } from "../../constants"; /** @@ -1396,13 +1396,13 @@ export class ProfileInfo { * config files (aka layers). * @returns Array of secure property details */ - public secureFieldsWithDetails(opts?: { user: boolean; global: boolean }): ISecureFieldDetails[] { + public secureFieldsWithDetails(opts?: { user: boolean; global: boolean }): IProfArgAttrs[] { const config = this.getTeamConfig(); const layer = opts ? config.findLayer(opts.user, opts.global) : config.layerActive(); const fields = config.api.secure.findSecure(layer.properties.profiles, "profiles"); const vault = config.api.secure.getSecureFieldsForLayer(layer.path); - const response: ISecureFieldDetails[] = []; + const response: IProfArgAttrs[] = []; // Search the vault for each secure field fields.forEach(fieldPath => { @@ -1412,11 +1412,15 @@ export class ProfileInfo { Object.entries(val).map(([propPath, propValue]) => { if (propPath === fieldPath) { response.push({ - path: fieldPath, - name: fieldPath.split(".properties.")[1], - type: this.argDataType(typeof propValue), - value: propValue as IProfDataType, - loc, + argName: fieldPath.split(".properties.")[1], + // name: , + dataType: this.argDataType(typeof propValue), + argValue: propValue as IProfDataType, + argLoc: { + locType: ProfLocType.TEAM_CONFIG, + osLoc: [loc], + jsonLoc: fieldPath + }, }); } }); @@ -1424,13 +1428,16 @@ export class ProfileInfo { }); fields.forEach(fieldPath => { - if (response.find(details => details.path === fieldPath) == null) { + if (response.find(details => details.argLoc.jsonLoc === fieldPath) == null) { response.push({ - path: fieldPath, - name: fieldPath.split(".properties.")[1], - type: undefined, - value: undefined, - loc: undefined + argName: fieldPath.split(".properties.")[1], + dataType: undefined, + argValue: undefined, + argLoc: { + locType: ProfLocType.TEAM_CONFIG, + osLoc: [], + jsonLoc: fieldPath + } }); } }); diff --git a/packages/imperative/src/config/src/api/ConfigSecure.ts b/packages/imperative/src/config/src/api/ConfigSecure.ts index 8a6178234f..0c67f833aa 100644 --- a/packages/imperative/src/config/src/api/ConfigSecure.ts +++ b/packages/imperative/src/config/src/api/ConfigSecure.ts @@ -22,7 +22,6 @@ import { CredentialManagerFactory } from "../../../security"; import { ConfigUtils } from "../ConfigUtils"; import { ZoweUserEvents } from "../../../events/src/EventConstants"; import { EventOperator } from "../../../events/src/EventOperator"; -import { IConfigLayer } from "../doc/IConfigLayer"; /** * API Class for manipulating config layers. @@ -244,10 +243,10 @@ export class ConfigSecure extends ConfigApi { } /** - * Retrieve secure properties for a givne layer path + * 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 + * @returns the secure properties for the given layer, or null if not found */ public getSecureFieldsForLayer(layerPath: string): IConfigSecureProperties { const secureLayer = Object.keys(this.mConfig.mSecure).find(osLocation => osLocation === layerPath); diff --git a/packages/imperative/src/config/src/doc/IConfigSecure.ts b/packages/imperative/src/config/src/doc/IConfigSecure.ts index b66a96b4ca..283054446e 100644 --- a/packages/imperative/src/config/src/doc/IConfigSecure.ts +++ b/packages/imperative/src/config/src/doc/IConfigSecure.ts @@ -9,16 +9,6 @@ * */ -import { IProfArgValue, IProfDataType } from "./IProfArgAttrs"; - export type IConfigSecureProperties = { [key: string]: any }; export type IConfigSecure = { [path: string]: IConfigSecureProperties }; - -export interface ISecureFieldDetails { - path: string; - name: string; - type: IProfDataType; - value: IProfArgValue; - loc: string; -} \ No newline at end of file From 1b289164d20d598a8fb35bfe8975155660c88889 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Mon, 26 Aug 2024 10:05:48 -0400 Subject: [PATCH 3/7] update changelog Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- packages/imperative/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/imperative/CHANGELOG.md b/packages/imperative/CHANGELOG.md index fce85b547f..dad4cf2b1f 100644 --- a/packages/imperative/CHANGELOG.md +++ b/packages/imperative/CHANGELOG.md @@ -6,6 +6,8 @@ All notable changes to the Imperative package will be documented in this file. - LTS Breaking: Fixed command parsing error where `string` typed options would be converted into `number`s if the value provided by the user consists only of numeric characters. [#1881](https://github.com/zowe/zowe-cli/issues/1881) - LTS Breaking: Renamed `Proxy` class to `ProxySettings` to avoid name conflict with JS built-in `Proxy` object. [#2230](https://github.com/zowe/zowe-cli/issues/2230) +- Enhancement: Added a new SDK method (`ConfigSecure.getSecureFieldsForLayer`) to allow developers to get vault content in the context of the specified layer. [#2206](https://github.com/zowe/zowe-cli/issues/2206) +- Enhancement: Added a new SDK method (`ProfileInfo.secureFieldsWithDetails`) to allow developers to the more details regarding the securely stored properties. [#2206](https://github.com/zowe/zowe-cli/issues/2206) ## `8.0.0-next.202408191401` From 9e77bf9e4549b5aa87ec0daa266199d792bf4486 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Mon, 26 Aug 2024 10:08:49 -0400 Subject: [PATCH 4/7] audit: update dev dependency version :yum: Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- npm-shrinkwrap.json | 19 ++++++++++++++++++- packages/imperative/CHANGELOG.md | 7 +++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 63b8d3d5ca..ec28a4025e 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -8703,6 +8703,21 @@ "dev": true, "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-arguments": { "version": "1.0.9", "dev": true, @@ -11354,7 +11369,9 @@ } }, "node_modules/micromatch": { - "version": "4.0.7", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "license": "MIT", "dependencies": { "braces": "^3.0.3", diff --git a/packages/imperative/CHANGELOG.md b/packages/imperative/CHANGELOG.md index 16d1e069c2..3f265eac66 100644 --- a/packages/imperative/CHANGELOG.md +++ b/packages/imperative/CHANGELOG.md @@ -2,12 +2,15 @@ All notable changes to the Imperative package will be documented in this file. +## Recent Changes + +- Enhancement: Added a new SDK method (`ConfigSecure.getSecureFieldsForLayer`) to allow developers to get vault content in the context of the specified layer. [#2206](https://github.com/zowe/zowe-cli/issues/2206) +- Enhancement: Added a new SDK method (`ProfileInfo.secureFieldsWithDetails`) to allow developers to the more details regarding the securely stored properties. [#2206](https://github.com/zowe/zowe-cli/issues/2206) + ## `8.0.0-next.202408231832` - LTS Breaking: Fixed command parsing error where `string` typed options would be converted into `number`s if the value provided by the user consists only of numeric characters. [#1881](https://github.com/zowe/zowe-cli/issues/1881) - LTS Breaking: Renamed `Proxy` class to `ProxySettings` to avoid name conflict with JS built-in `Proxy` object. [#2230](https://github.com/zowe/zowe-cli/issues/2230) -- Enhancement: Added a new SDK method (`ConfigSecure.getSecureFieldsForLayer`) to allow developers to get vault content in the context of the specified layer. [#2206](https://github.com/zowe/zowe-cli/issues/2206) -- Enhancement: Added a new SDK method (`ProfileInfo.secureFieldsWithDetails`) to allow developers to the more details regarding the securely stored properties. [#2206](https://github.com/zowe/zowe-cli/issues/2206) ## `8.0.0-next.202408191401` From cf06aa06f4ae1de21a360cc7827828cc2d164ba3 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Mon, 26 Aug 2024 10:23:55 -0400 Subject: [PATCH 5/7] tests: rename function in ConfigSecure and add unit tests :yum: Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- packages/imperative/CHANGELOG.md | 2 +- .../__tests__/Config.secure.unit.test.ts | 23 +++++++++++++++---- .../imperative/src/config/src/ProfileInfo.ts | 2 +- .../src/config/src/api/ConfigSecure.ts | 2 +- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/packages/imperative/CHANGELOG.md b/packages/imperative/CHANGELOG.md index 3f265eac66..8eec0c2ea9 100644 --- a/packages/imperative/CHANGELOG.md +++ b/packages/imperative/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to the Imperative package will be documented in this file. ## Recent Changes -- Enhancement: Added a new SDK method (`ConfigSecure.getSecureFieldsForLayer`) to allow developers to get vault content in the context of the specified layer. [#2206](https://github.com/zowe/zowe-cli/issues/2206) +- Enhancement: Added a new SDK method (`ConfigSecure.secureFieldsForLayer`) to allow developers to get vault content in the context of the specified layer. [#2206](https://github.com/zowe/zowe-cli/issues/2206) - Enhancement: Added a new SDK method (`ProfileInfo.secureFieldsWithDetails`) to allow developers to the more details regarding the securely stored properties. [#2206](https://github.com/zowe/zowe-cli/issues/2206) ## `8.0.0-next.202408231832` diff --git a/packages/imperative/src/config/__tests__/Config.secure.unit.test.ts b/packages/imperative/src/config/__tests__/Config.secure.unit.test.ts index 72b632c70a..bce9b9693a 100644 --- a/packages/imperative/src/config/__tests__/Config.secure.unit.test.ts +++ b/packages/imperative/src/config/__tests__/Config.secure.unit.test.ts @@ -80,7 +80,7 @@ describe("Config secure tests", () => { config.mLayers = [ { path: "fake fakety fake", - properties: { profiles: {fake: { secure: ["fake"], properties: {fake: "fake"}}}} + properties: { profiles: { fake: { secure: ["fake"], properties: { fake: "fake" } } } } } ]; config.mVault = mockVault; @@ -137,10 +137,10 @@ describe("Config secure tests", () => { jest.spyOn(fs, "readFileSync"); let secureError: any; const vault: IConfigVault = { - load: jest.fn().mockRejectedValue(new ImperativeError({msg: "The vault failed"})), + load: jest.fn().mockRejectedValue(new ImperativeError({ msg: "The vault failed" })), save: jest.fn() }; - const config = await Config.load(MY_APP, {noLoad: true, vault: vault}); + const config = await Config.load(MY_APP, { noLoad: true, vault: vault }); config.mVault = vault; try { await config.api.secure.load(vault); @@ -167,6 +167,21 @@ describe("Config secure tests", () => { ]); }); + it("should list all secure fields for a layer", async () => { + jest.spyOn(Config, "search").mockReturnValue(projectConfigPath); + jest.spyOn(fs, "existsSync") + .mockReturnValueOnce(false) // Project user layer + .mockReturnValueOnce(true) // Project layer + .mockReturnValueOnce(false) // User layer + .mockReturnValueOnce(false); // Global layer + jest.spyOn(fs, "readFileSync"); + const config = await Config.load(MY_APP); + config.mSecure = secureConfigs; + expect(config.api.secure.secureFieldsForLayer(projectConfigPath)).toEqual({ [projectConfigPath]: { [securePropPath]: "area51" } }); + expect(config.api.secure.secureFieldsForLayer(projectUserConfigPath)).toEqual(null); + config.mSecure = {}; + }); + it("should list all secure fields for a profile", async () => { jest.spyOn(Config, "search").mockReturnValue(projectConfigPath).mockReturnValueOnce(projectUserConfigPath); jest.spyOn(fs, "existsSync") @@ -282,7 +297,7 @@ describe("Config secure tests", () => { it("rmUnusedProps should delete properties for files that do not exist", () => { const config = new (Config as any)(); - config.mSecure = {...secureConfigs}; + config.mSecure = { ...secureConfigs }; jest.spyOn(fs, "existsSync").mockReturnValueOnce(true).mockReturnValueOnce(false); const prunedFiles = config.api.secure.rmUnusedProps(); expect(prunedFiles).toEqual(["fakePath"]); diff --git a/packages/imperative/src/config/src/ProfileInfo.ts b/packages/imperative/src/config/src/ProfileInfo.ts index 1afe64e2e1..88ffbe623e 100644 --- a/packages/imperative/src/config/src/ProfileInfo.ts +++ b/packages/imperative/src/config/src/ProfileInfo.ts @@ -1400,7 +1400,7 @@ export class ProfileInfo { const config = this.getTeamConfig(); const layer = opts ? config.findLayer(opts.user, opts.global) : config.layerActive(); const fields = config.api.secure.findSecure(layer.properties.profiles, "profiles"); - const vault = config.api.secure.getSecureFieldsForLayer(layer.path); + const vault = config.api.secure.secureFieldsForLayer(layer.path); const response: IProfArgAttrs[] = []; diff --git a/packages/imperative/src/config/src/api/ConfigSecure.ts b/packages/imperative/src/config/src/api/ConfigSecure.ts index 0c67f833aa..b1bcc7ba27 100644 --- a/packages/imperative/src/config/src/api/ConfigSecure.ts +++ b/packages/imperative/src/config/src/api/ConfigSecure.ts @@ -248,7 +248,7 @@ export class ConfigSecure extends ConfigApi { * @param layerPath Path of the layer to get secure properties for * @returns the secure properties for the given layer, or null if not found */ - public getSecureFieldsForLayer(layerPath: string): IConfigSecureProperties { + public secureFieldsForLayer(layerPath: string): IConfigSecureProperties { const secureLayer = Object.keys(this.mConfig.mSecure).find(osLocation => osLocation === layerPath); return secureLayer ? { [secureLayer] : this.mConfig.mSecure[secureLayer] } : null; } From 71392878cdbf09c6104f004938a48bff53ac4728 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Mon, 26 Aug 2024 11:38:28 -0400 Subject: [PATCH 6/7] fix: simplify both APIs adn added unit test :yum: Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- .../__tests__/Config.secure.unit.test.ts | 2 +- .../ProfileInfo.TeamConfig.unit.test.ts | 61 ++++++++++++++++--- .../ProfInfoApp.config.json | 5 ++ .../imperative/src/config/src/ProfileInfo.ts | 36 +++++------ .../src/config/src/api/ConfigSecure.ts | 2 +- 5 files changed, 77 insertions(+), 29 deletions(-) diff --git a/packages/imperative/src/config/__tests__/Config.secure.unit.test.ts b/packages/imperative/src/config/__tests__/Config.secure.unit.test.ts index bce9b9693a..276531d6a9 100644 --- a/packages/imperative/src/config/__tests__/Config.secure.unit.test.ts +++ b/packages/imperative/src/config/__tests__/Config.secure.unit.test.ts @@ -177,7 +177,7 @@ describe("Config secure tests", () => { jest.spyOn(fs, "readFileSync"); const config = await Config.load(MY_APP); config.mSecure = secureConfigs; - expect(config.api.secure.secureFieldsForLayer(projectConfigPath)).toEqual({ [projectConfigPath]: { [securePropPath]: "area51" } }); + expect(config.api.secure.secureFieldsForLayer(projectConfigPath)).toEqual({ [securePropPath]: "area51" }); expect(config.api.secure.secureFieldsForLayer(projectUserConfigPath)).toEqual(null); config.mSecure = {}; }); diff --git a/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts b/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts index ab7a40eac2..5df7c00507 100644 --- a/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts +++ b/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts @@ -33,7 +33,6 @@ import { IExtendersJsonOpts } from "../src/doc/IExtenderOpts"; import { ConfigSchema } from "../src/ConfigSchema"; import { Logger } from "../../logger/src/Logger"; - const testAppNm = "ProfInfoApp"; const testEnvPrefix = testAppNm.toUpperCase(); const profileTypes = ["zosmf", "tso", "base", "dummy"]; @@ -331,7 +330,7 @@ describe("TeamConfig ProfileInfo tests", () => { it("should return true if credentials are not secure", async () => { // ensure that we are not in the team project directory const profInfo = createNewProfInfo(origDir); - (profInfo as any).mCredentials = {isSecured: false}; + (profInfo as any).mCredentials = { isSecured: false }; const response = await profInfo.profileManagerWillLoad(); expect(response).toEqual(true); }); @@ -366,6 +365,52 @@ describe("TeamConfig ProfileInfo tests", () => { }); }); + describe("secureFieldsWithDetails", () => { + it("should return an empty array if there are no secure fields in the given layer or if the layer does not exist", async () => { + const profInfo = createNewProfInfo(teamProjDir); + await profInfo.readProfilesFromDisk(); + + // Project User does not exist + expect(profInfo.secureFieldsWithDetails({ user: true, global: false })).toEqual([]); + + // Project Team dos exist, but has no secure properties + expect(profInfo.secureFieldsWithDetails({ user: false, global: false })).toEqual([]); + }); + fit("should return secure fields for the active layer even if they have no secure values stored in the vault", async () => { + const profInfo = createNewProfInfo(teamProjDir); + await profInfo.readProfilesFromDisk(); + + const securePropPath = "profiles.LPAR007.properties."; + const teamProjDirJson = path.join(teamProjDir, testAppNm + ".config.json"); + profInfo.getTeamConfig().mSecure = { + [teamProjDirJson]: { + [securePropPath + "string"]: "area51", + [securePropPath + "boolean"]: true, + [securePropPath + "number"]: 1234, + }, + }; + + const getPropAttr = (name: string | any, value: any, type?: string | null): IProfArgAttrs => ({ + argLoc: { osLoc: [teamProjDirJson], locType: 1, jsonLoc: securePropPath + name }, + argName: name, + argValue: value, + dataType: type !== undefined ? type : name, + }); + + expect(profInfo.secureFieldsWithDetails()).toEqual([ + getPropAttr("string", "area51"), + getPropAttr("boolean", true), + getPropAttr("number", 1234), + getPropAttr("missing", undefined, null), + getPropAttr("host", undefined, "string"), + getPropAttr("port", undefined, "number"), + getPropAttr("responseFormatHeader", undefined, "boolean"), + ]); + + profInfo.getTeamConfig().mSecure = {}; + }); + }); + describe("getAllProfiles", () => { it("should return all profiles if no type is specified", async () => { const expectedDefaultProfiles = 4; @@ -1116,7 +1161,7 @@ describe("TeamConfig ProfileInfo tests", () => { expect(storeSpy).toHaveBeenCalledWith({ config: profInfo.getTeamConfig(), profileName: "LPAR4", profileType: "dummy", defaultBaseProfileName: "base_glob", - propsToStore: [ "DOES_NOT_EXIST" ], sessCfg: { "DOES_NOT_EXIST": true }, setSecure : undefined, + propsToStore: ["DOES_NOT_EXIST"], sessCfg: { "DOES_NOT_EXIST": true }, setSecure: undefined, }); }); @@ -1196,7 +1241,7 @@ describe("TeamConfig ProfileInfo tests", () => { expect(storeSpy).toHaveBeenCalledWith({ config: profInfo.getTeamConfig(), profileName: "typeless", profileType: null, defaultBaseProfileName: "base_glob", - propsToStore: [ "areBirdsReal" ], sessCfg: { "areBirdsReal": true }, setSecure : undefined, + propsToStore: ["areBirdsReal"], sessCfg: { "areBirdsReal": true }, setSecure: undefined, }); }); @@ -1242,7 +1287,7 @@ describe("TeamConfig ProfileInfo tests", () => { expect(storeSpy).toHaveBeenCalledWith({ config: profInfo.getTeamConfig(), profileName: "typeless_new", profileType: null, defaultBaseProfileName: "base_glob", - propsToStore: [ "areBirdsReal" ], sessCfg: { "areBirdsReal": true }, setSecure : undefined, + propsToStore: ["areBirdsReal"], sessCfg: { "areBirdsReal": true }, setSecure: undefined, }); }); }); @@ -1683,8 +1728,10 @@ describe("TeamConfig ProfileInfo tests", () => { } } }, - res: { success: false, info: "Both the old and new schemas are unversioned for some-type, but the schemas are different. " - .concat("The new schema was not written to disk, but will still be accessible in-memory.") } + res: { + success: false, info: "Both the old and new schemas are unversioned for some-type, but the schemas are different. " + .concat("The new schema was not written to disk, but will still be accessible in-memory.") + } } ); }); diff --git a/packages/imperative/src/config/__tests__/__resources__/ProfInfoApp_team_config_proj/ProfInfoApp.config.json b/packages/imperative/src/config/__tests__/__resources__/ProfInfoApp_team_config_proj/ProfInfoApp.config.json index 0823095788..b5a8c4b30f 100644 --- a/packages/imperative/src/config/__tests__/__resources__/ProfInfoApp_team_config_proj/ProfInfoApp.config.json +++ b/packages/imperative/src/config/__tests__/__resources__/ProfInfoApp_team_config_proj/ProfInfoApp.config.json @@ -72,6 +72,11 @@ }, "typeless": { "properties": {} + }, + "LPAR007": { + "type": "zosmf", + "properties": {}, + "secure": ["string", "boolean", "number", "missing", "host", "port", "responseFormatHeader"] } }, "defaults": { diff --git a/packages/imperative/src/config/src/ProfileInfo.ts b/packages/imperative/src/config/src/ProfileInfo.ts index 88ffbe623e..916c9be357 100644 --- a/packages/imperative/src/config/src/ProfileInfo.ts +++ b/packages/imperative/src/config/src/ProfileInfo.ts @@ -1406,25 +1406,21 @@ export class ProfileInfo { // Search the vault for each secure field fields.forEach(fieldPath => { - // Scan the cached contents of the vault - for (const [loc, val] of Object.entries(vault)) { - // Search inside the secure fields for this layer - Object.entries(val).map(([propPath, propValue]) => { - if (propPath === fieldPath) { - response.push({ - argName: fieldPath.split(".properties.")[1], - // name: , - dataType: this.argDataType(typeof propValue), - argValue: propValue as IProfDataType, - argLoc: { - locType: ProfLocType.TEAM_CONFIG, - osLoc: [loc], - jsonLoc: fieldPath - }, - }); - } - }); - } + // Search inside the secure fields for this layer + Object.entries(vault).map(([propPath, propValue]) => { + if (propPath === fieldPath) { + response.push({ + argName: fieldPath.split(".properties.")[1], + dataType: this.argDataType(typeof propValue), + argValue: propValue as IProfDataType, + argLoc: { + locType: ProfLocType.TEAM_CONFIG, + osLoc: [layer.path], + jsonLoc: fieldPath + }, + }); + } + }); }); fields.forEach(fieldPath => { @@ -1435,7 +1431,7 @@ export class ProfileInfo { argValue: undefined, argLoc: { locType: ProfLocType.TEAM_CONFIG, - osLoc: [], + osLoc: [layer.path], jsonLoc: fieldPath } }); diff --git a/packages/imperative/src/config/src/api/ConfigSecure.ts b/packages/imperative/src/config/src/api/ConfigSecure.ts index b1bcc7ba27..ba43bfc143 100644 --- a/packages/imperative/src/config/src/api/ConfigSecure.ts +++ b/packages/imperative/src/config/src/api/ConfigSecure.ts @@ -250,7 +250,7 @@ export class ConfigSecure extends ConfigApi { */ public secureFieldsForLayer(layerPath: string): IConfigSecureProperties { const secureLayer = Object.keys(this.mConfig.mSecure).find(osLocation => osLocation === layerPath); - return secureLayer ? { [secureLayer] : this.mConfig.mSecure[secureLayer] } : null; + return secureLayer ? this.mConfig.mSecure[secureLayer] : null; } /** From ed4498a4cca7e3b694a1bb33394818d008032e45 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Mon, 26 Aug 2024 14:46:08 -0400 Subject: [PATCH 7/7] fix: add types to all missing arguments based on schema definition Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- .../ProfileInfo.TeamConfig.unit.test.ts | 14 ++++--- .../ProfInfoApp.config.json | 10 ++++- .../imperative/src/config/src/ProfileInfo.ts | 41 +++++++++++-------- 3 files changed, 41 insertions(+), 24 deletions(-) diff --git a/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts b/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts index 88439d924b..420972d432 100644 --- a/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts +++ b/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts @@ -370,12 +370,16 @@ describe("TeamConfig ProfileInfo tests", () => { const profInfo = createNewProfInfo(teamProjDir); await profInfo.readProfilesFromDisk(); + // Temporarily assume that there are no secure properties for this test only + profInfo.getTeamConfig().mLayers[1].properties.profiles["LPAR007"].secure = []; + // Project User does not exist expect(profInfo.secureFieldsWithDetails({ user: true, global: false })).toEqual([]); // Project Team dos exist, but has no secure properties expect(profInfo.secureFieldsWithDetails({ user: false, global: false })).toEqual([]); }); + it("should return secure fields for the active layer even if they have no secure values stored in the vault", async () => { const profInfo = createNewProfInfo(teamProjDir); await profInfo.readProfilesFromDisk(); @@ -401,10 +405,10 @@ describe("TeamConfig ProfileInfo tests", () => { getPropAttr("string", "area51"), getPropAttr("boolean", true), getPropAttr("number", 1234), - getPropAttr("missing", undefined, null), + getPropAttr("missing-after-this", undefined, null), getPropAttr("host", undefined, "string"), getPropAttr("port", undefined, "number"), - getPropAttr("responseFormatHeader", undefined, "boolean"), + getPropAttr("rejectUnauthorized", undefined, "boolean"), ]); profInfo.getTeamConfig().mSecure = {}; @@ -420,7 +424,7 @@ describe("TeamConfig ProfileInfo tests", () => { const expectedDefaultProfileNameDummy = "LPAR4"; let actualDefaultProfiles = 0; let expectedProfileNames = ["LPAR1", "LPAR2", "LPAR3", "LPAR1.tsoProfName", "LPAR1.tsoProfName.tsoSubProfName", - "base_glob", "LPAR4", "LPAR5"]; + "base_glob", "LPAR4", "LPAR5", "LPAR007"]; const profInfo = createNewProfInfo(teamProjDir); await profInfo.readProfilesFromDisk(); @@ -460,7 +464,7 @@ describe("TeamConfig ProfileInfo tests", () => { const desiredProfType = "zosmf"; const expectedName = "LPAR1"; const expectedDefaultProfiles = 1; - let expectedProfileNames = ["LPAR1", "LPAR2", "LPAR3", "LPAR2_home", "LPAR5"]; + let expectedProfileNames = ["LPAR1", "LPAR2", "LPAR3", "LPAR2_home", "LPAR5", "LPAR007"]; let actualDefaultProfiles = 0; const profInfo = createNewProfInfo(teamProjDir); @@ -494,7 +498,7 @@ describe("TeamConfig ProfileInfo tests", () => { const desiredProfType = "zosmf"; const expectedName = "LPAR1"; const expectedDefaultProfiles = 1; - let expectedProfileNames = ["LPAR1", "LPAR2", "LPAR3", "LPAR5"]; + let expectedProfileNames = ["LPAR1", "LPAR2", "LPAR3", "LPAR5", "LPAR007"]; let actualDefaultProfiles = 0; const profInfo = createNewProfInfo(teamProjDir); diff --git a/packages/imperative/src/config/__tests__/__resources__/ProfInfoApp_team_config_proj/ProfInfoApp.config.json b/packages/imperative/src/config/__tests__/__resources__/ProfInfoApp_team_config_proj/ProfInfoApp.config.json index b5a8c4b30f..669c188f06 100644 --- a/packages/imperative/src/config/__tests__/__resources__/ProfInfoApp_team_config_proj/ProfInfoApp.config.json +++ b/packages/imperative/src/config/__tests__/__resources__/ProfInfoApp_team_config_proj/ProfInfoApp.config.json @@ -76,7 +76,15 @@ "LPAR007": { "type": "zosmf", "properties": {}, - "secure": ["string", "boolean", "number", "missing", "host", "port", "responseFormatHeader"] + "secure": [ + "string", + "boolean", + "number", + "missing-after-this", + "host", + "port", + "rejectUnauthorized" + ] } }, "defaults": { diff --git a/packages/imperative/src/config/src/ProfileInfo.ts b/packages/imperative/src/config/src/ProfileInfo.ts index 916c9be357..84641b89f5 100644 --- a/packages/imperative/src/config/src/ProfileInfo.ts +++ b/packages/imperative/src/config/src/ProfileInfo.ts @@ -1170,7 +1170,7 @@ export class ProfileInfo { if (semver.major(typeInfo.schema.version) != semver.major(prevTypeVersion)) { // Warn user if new major schema version is specified infoMsg = - `Profile type ${profileType} was updated from schema version ${prevTypeVersion} to ${typeInfo.schema.version}.\n` + + `Profile type ${profileType} was updated from schema version ${prevTypeVersion} to ${typeInfo.schema.version}.\n` + `The following applications may be affected: ${typeMetadata.from.filter((src) => src !== typeInfo.sourceApp)}`; } } else if (semver.major(prevTypeVersion) > semver.major(typeInfo.schema.version)) { @@ -1405,29 +1405,34 @@ export class ProfileInfo { const response: IProfArgAttrs[] = []; // Search the vault for each secure field - fields.forEach(fieldPath => { - // Search inside the secure fields for this layer - Object.entries(vault).map(([propPath, propValue]) => { - if (propPath === fieldPath) { - response.push({ - argName: fieldPath.split(".properties.")[1], - dataType: this.argDataType(typeof propValue), - argValue: propValue as IProfDataType, - argLoc: { - locType: ProfLocType.TEAM_CONFIG, - osLoc: [layer.path], - jsonLoc: fieldPath - }, - }); - } + if (vault) { + fields.forEach(fieldPath => { + // Search inside the secure fields for this layer + Object.entries(vault).map(([propPath, propValue]) => { + if (propPath === fieldPath) { + const dataType = ConfigSchema.findPropertyType(fieldPath, layer.properties, this.buildSchema([], layer)) as IProfDataType; + + response.push({ + argName: fieldPath.split(".properties.")[1], + dataType: dataType ?? this.argDataType(typeof propValue), + argValue: propValue as IProfDataType, + argLoc: { + locType: ProfLocType.TEAM_CONFIG, + osLoc: [layer.path], + jsonLoc: fieldPath + }, + }); + } + }); }); - }); + } fields.forEach(fieldPath => { if (response.find(details => details.argLoc.jsonLoc === fieldPath) == null) { + const dataType = ConfigSchema.findPropertyType(fieldPath, layer.properties, this.buildSchema([], layer)) as IProfDataType ?? null; response.push({ argName: fieldPath.split(".properties.")[1], - dataType: undefined, + dataType, argValue: undefined, argLoc: { locType: ProfLocType.TEAM_CONFIG,