From 8c473af6478c508ad39d07aae3c688fb676a0e38 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Wed, 6 Sep 2023 17:07:09 -0400 Subject: [PATCH 1/7] Enhance UX for managing credentials Signed-off-by: Timothy Johnson --- .../i18n/sample/package.i18n.json | 2 +- .../sample/src/utils/ProfilesUtils.i18n.json | 6 ++ packages/zowe-explorer/package.json | 66 +++++------------- packages/zowe-explorer/package.nls.json | 2 +- .../zowe-explorer/src/utils/ProfilesUtils.ts | 69 +++++++++++++++---- 5 files changed, 83 insertions(+), 62 deletions(-) diff --git a/packages/zowe-explorer/i18n/sample/package.i18n.json b/packages/zowe-explorer/i18n/sample/package.i18n.json index 5930969b05..e788086fbf 100644 --- a/packages/zowe-explorer/i18n/sample/package.i18n.json +++ b/packages/zowe-explorer/i18n/sample/package.i18n.json @@ -2,7 +2,7 @@ "displayName": "Zowe Explorer", "description": "VS Code extension, powered by Zowe CLI, that streamlines interaction with mainframe data sets, USS files, and jobs", "viewsContainers.activitybar": "Zowe Explorer", - "zowe.promptCredentials": "Update Credentials", + "zowe.promptCredentials": "Manage Credentials", "zowe.extRefresh": "Refresh Zowe Explorer", "zowe.ds.explorer": "Data Sets", "zowe.uss.explorer": "Unix System Services (USS)", diff --git a/packages/zowe-explorer/i18n/sample/src/utils/ProfilesUtils.i18n.json b/packages/zowe-explorer/i18n/sample/src/utils/ProfilesUtils.i18n.json index db62def29c..d29d6a9e5a 100644 --- a/packages/zowe-explorer/i18n/sample/src/utils/ProfilesUtils.i18n.json +++ b/packages/zowe-explorer/i18n/sample/src/utils/ProfilesUtils.i18n.json @@ -12,6 +12,12 @@ "createNewConnection.option.prompt.profileName.placeholder": "Connection Name", "createNewConnection.option.prompt.profileName": "Enter a name for the connection.", "createNewConnection.undefined.passWord": "Operation Cancelled", + "promptCredentials.quickPick.basicAuthLabel": "$(circle-large) User and Password", + "promptCredentials.quickPick.basicAuthDescription": "Store username and password", + "promptCredentials.quickPick.tokenAuthLabel": "$(circle-large) Authentication Token", + "promptCredentials.quickPick.tokenAuthDescription": "Authenticate to service and store token", + "promptCredentials.quickPick.logOutLabel": "Log out of Authentication Service", + "promptCredentials.quickPick.title": "Select an authentication method", "promptCredentials.updatedCredentials": "Credentials for {0} were successfully updated", "initializeZoweFolder.location": "Zowe home directory is located at {0}", "writeOverridesFile.readFile.error": "Reading imperative.json failed. Will try to create file.", diff --git a/packages/zowe-explorer/package.json b/packages/zowe-explorer/package.json index b4c8a1a7cc..76e6a8b311 100644 --- a/packages/zowe-explorer/package.json +++ b/packages/zowe-explorer/package.json @@ -939,30 +939,20 @@ "group": "099_zowe_ussModification:@4" }, { - "when": "view == zowe.uss.explorer && viewItem =~ /_validate/ && !listMultiSelection", - "command": "zowe.uss.disableValidation", + "when": "view == zowe.uss.explorer && viewItem =~ /^(?!.*_fav.*)ussSession.*/ && !listMultiSelection", + "command": "zowe.promptCredentials", "group": "098_zowe_ussProfileAuthentication@1" }, { - "when": "view == zowe.uss.explorer && viewItem =~ /_noValidate/ && !listMultiSelection", - "command": "zowe.uss.enableValidation", + "when": "view == zowe.uss.explorer && viewItem =~ /_validate/ && !listMultiSelection", + "command": "zowe.uss.disableValidation", "group": "098_zowe_ussProfileAuthentication@2" }, { - "when": "view == zowe.uss.explorer && viewItem =~ /^(?!.*_fav.*)ussSession.*/ && !listMultiSelection", - "command": "zowe.promptCredentials", + "when": "view == zowe.uss.explorer && viewItem =~ /_noValidate/ && !listMultiSelection", + "command": "zowe.uss.enableValidation", "group": "098_zowe_ussProfileAuthentication@3" }, - { - "when": "view == zowe.uss.explorer && viewItem =~ /^(?!.*_fav.*)ussSession.*/ && !listMultiSelection", - "command": "zowe.uss.ssoLogin", - "group": "098_zowe_ussProfileAuthentication@4" - }, - { - "when": "view == zowe.uss.explorer && viewItem =~ /^(?!.*_fav.*)ussSession.*/ && !listMultiSelection", - "command": "zowe.uss.ssoLogout", - "group": "098_zowe_ussProfileAuthentication@5" - }, { "when": "viewItem =~ /^(?!.*_fav.*)ussSession.*/ && !listMultiSelection", "command": "zowe.uss.editSession", @@ -1149,30 +1139,20 @@ "group": "099_zowe_dsModification@5" }, { - "when": "view == zowe.ds.explorer && viewItem =~ /_validate/ && !listMultiSelection", - "command": "zowe.ds.disableValidation", + "when": "view == zowe.ds.explorer && viewItem =~ /^(?!.*_fav.*)session.*/ && !listMultiSelection", + "command": "zowe.promptCredentials", "group": "098_zowe_dsProfileAuthentication@6" }, { - "when": "view == zowe.ds.explorer && viewItem =~ /_noValidate/ && !listMultiSelection", - "command": "zowe.ds.enableValidation", + "when": "view == zowe.ds.explorer && viewItem =~ /_validate/ && !listMultiSelection", + "command": "zowe.ds.disableValidation", "group": "098_zowe_dsProfileAuthentication@7" }, { - "when": "view == zowe.ds.explorer && viewItem =~ /^(?!.*_fav.*)session.*/ && !listMultiSelection", - "command": "zowe.promptCredentials", + "when": "view == zowe.ds.explorer && viewItem =~ /_noValidate/ && !listMultiSelection", + "command": "zowe.ds.enableValidation", "group": "098_zowe_dsProfileAuthentication@8" }, - { - "when": "view == zowe.ds.explorer && viewItem =~ /^(?!.*_fav.*)session.*/ && !listMultiSelection", - "command": "zowe.ds.ssoLogin", - "group": "098_zowe_dsProfileAuthentication@9" - }, - { - "when": "view == zowe.ds.explorer && viewItem =~ /^(?!.*_fav.*)session.*/ && !listMultiSelection", - "command": "zowe.ds.ssoLogout", - "group": "098_zowe_dsProfileAuthentication@10" - }, { "when": "view == zowe.ds.explorer && viewItem =~ /^(?!.*_fav.*)session.*/ && !listMultiSelection", "command": "zowe.ds.editSession", @@ -1314,30 +1294,20 @@ "group": "099_zowe_jobsModification" }, { - "when": "view == zowe.jobs.explorer && viewItem =~ /_validate/ && !listMultiSelection", - "command": "zowe.jobs.disableValidation", + "when": "view == zowe.jobs.explorer && viewItem =~ /^(?!.*_fav.*)server.*/ && !listMultiSelection", + "command": "zowe.promptCredentials", "group": "098_zowe_jobsProfileAuthentication@3" }, { - "when": "view == zowe.jobs.explorer && viewItem =~ /_noValidate/ && !listMultiSelection", - "command": "zowe.jobs.enableValidation", + "when": "view == zowe.jobs.explorer && viewItem =~ /_validate/ && !listMultiSelection", + "command": "zowe.jobs.disableValidation", "group": "098_zowe_jobsProfileAuthentication@4" }, { - "when": "view == zowe.jobs.explorer && viewItem =~ /^(?!.*_fav.*)server.*/ && !listMultiSelection", - "command": "zowe.promptCredentials", + "when": "view == zowe.jobs.explorer && viewItem =~ /_noValidate/ && !listMultiSelection", + "command": "zowe.jobs.enableValidation", "group": "098_zowe_jobsProfileAuthentication@5" }, - { - "when": "view == zowe.jobs.explorer && viewItem =~ /^(?!.*_fav.*)server.*/ && !listMultiSelection", - "command": "zowe.jobs.ssoLogin", - "group": "098_zowe_jobsProfileAuthentication@6" - }, - { - "when": "view == zowe.jobs.explorer && viewItem =~ /^(?!.*_fav.*)server.*/ && !listMultiSelection", - "command": "zowe.jobs.ssoLogout", - "group": "098_zowe_jobsProfileAuthentication@7" - }, { "when": "view == zowe.jobs.explorer && viewItem =~ /^(?!.*_fav.*)server.*/ && !listMultiSelection", "command": "zowe.jobs.editSession", diff --git a/packages/zowe-explorer/package.nls.json b/packages/zowe-explorer/package.nls.json index 5930969b05..e788086fbf 100644 --- a/packages/zowe-explorer/package.nls.json +++ b/packages/zowe-explorer/package.nls.json @@ -2,7 +2,7 @@ "displayName": "Zowe Explorer", "description": "VS Code extension, powered by Zowe CLI, that streamlines interaction with mainframe data sets, USS files, and jobs", "viewsContainers.activitybar": "Zowe Explorer", - "zowe.promptCredentials": "Update Credentials", + "zowe.promptCredentials": "Manage Credentials", "zowe.extRefresh": "Refresh Zowe Explorer", "zowe.ds.explorer": "Data Sets", "zowe.uss.explorer": "Unix System Services (USS)", diff --git a/packages/zowe-explorer/src/utils/ProfilesUtils.ts b/packages/zowe-explorer/src/utils/ProfilesUtils.ts index 3152e66200..7a053f56a6 100644 --- a/packages/zowe-explorer/src/utils/ProfilesUtils.ts +++ b/packages/zowe-explorer/src/utils/ProfilesUtils.ts @@ -330,19 +330,64 @@ export class ProfilesUtils { } } - const creds = await Profiles.getInstance().promptCredentials(profile, true); - - if (creds != null) { - const successMsg = localize( - "promptCredentials.updatedCredentials", - "Credentials for {0} were successfully updated", - typeof profile === "string" ? profile : profile.name - ); - ZoweLogger.info(successMsg); - Gui.showMessage(successMsg); - // config file watcher isn't noticing changes for secure fields - await vscode.commands.executeCommand("zowe.extRefresh"); + const hasTokenValue = typeof profile === "string" || profile.profile?.tokenValue != null; + const authTypeChoices: Record = { + [imperative.SessConstants.AUTH_TYPE_BASIC]: { + label: localize("promptCredentials.quickPick.basicAuthLabel", "$(circle-large) User and Password"), + description: localize("promptCredentials.quickPick.basicAuthDescription", "Store username and password"), + }, + [imperative.SessConstants.AUTH_TYPE_TOKEN]: { + label: localize("promptCredentials.quickPick.tokenAuthLabel", "$(circle-large) Authentication Token"), + description: localize("promptCredentials.quickPick.tokenAuthDescription", "Authenticate to service and store token"), + }, + // [imperative.SessConstants.AUTH_TYPE_CERT_PEM]: { + // label: localize("promptCredentials.quickPick.certAuthLabel", "$(circle-large) Certificate File"), + // description: localize("promptCredentials.quickPick.certAuthDescription", "Select a PEM certificate file"), + // }, + }; + let currentAuthType = imperative.SessConstants.AUTH_TYPE_BASIC; + if (typeof profile !== "string" && profile.profile?.tokenType != null) { + currentAuthType = imperative.SessConstants.AUTH_TYPE_TOKEN; + } else if (typeof profile !== "string" && profile.profile?.certFile != null) { + // currentAuthType = imperative.SessConstants.AUTH_TYPE_CERT_PEM; + } + authTypeChoices[currentAuthType].label = authTypeChoices[currentAuthType].label.replace("$(circle-large)", "$(record)"); + const quickPickOptions: vscode.QuickPickItem[] = Object.values(authTypeChoices); + if (hasTokenValue) { + quickPickOptions.push(globals.SEPARATORS.BLANK, { + label: localize("promptCredentials.quickPick.logOutLabel", "Log out of Authentication Service"), + }); } + const qp = Gui.createQuickPick(); + qp.items = quickPickOptions; + qp.activeItems = [authTypeChoices[currentAuthType]]; + qp.title = localize("promptCredentials.quickPick.title", "Select an authentication method"); + return new Promise((resolve) => { + qp.onDidChangeSelection(async ([item]) => { + if (item === authTypeChoices[imperative.SessConstants.AUTH_TYPE_BASIC]) { + const creds = await Profiles.getInstance().promptCredentials(profile, true); + if (creds != null) { + const successMsg = localize( + "promptCredentials.updatedCredentials", + "Credentials for {0} were successfully updated", + typeof profile === "string" ? profile : profile.name + ); + ZoweLogger.info(successMsg); + Gui.showMessage(successMsg); + // config file watcher isn't noticing changes for secure fields + await vscode.commands.executeCommand("zowe.extRefresh"); + } + } else if (item === authTypeChoices[imperative.SessConstants.AUTH_TYPE_TOKEN]) { + await Profiles.getInstance().ssoLogin(node); + } else if (item === authTypeChoices[imperative.SessConstants.AUTH_TYPE_CERT_PEM]) { + // TODO Add support for certificates + } else if (hasTokenValue && item === quickPickOptions[quickPickOptions.length - 1]) { + await Profiles.getInstance().ssoLogout(node); + } + resolve(); + }); + qp.show(); + }); } public static async initializeZoweFolder(): Promise { From 9c2fe037a0d9172e84cc107852fbed2bf8c2d59f Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Fri, 8 Sep 2023 18:48:08 -0400 Subject: [PATCH 2/7] Add unit tests for Manage Credentials quickpick Signed-off-by: Timothy Johnson --- .../__mocks__/@zowe/imperative.ts | 6 +- .../__unit__/utils/ProfilesUtils.unit.test.ts | 69 ++++++++++++++++++- .../sample/src/utils/ProfilesUtils.i18n.json | 4 +- .../zowe-explorer/src/utils/ProfilesUtils.ts | 68 +++++++++--------- 4 files changed, 103 insertions(+), 44 deletions(-) diff --git a/packages/zowe-explorer/__mocks__/@zowe/imperative.ts b/packages/zowe-explorer/__mocks__/@zowe/imperative.ts index c85cc88a6e..d2b78dfb57 100644 --- a/packages/zowe-explorer/__mocks__/@zowe/imperative.ts +++ b/packages/zowe-explorer/__mocks__/@zowe/imperative.ts @@ -356,6 +356,8 @@ export class TextUtils { } } -export namespace SessConstants { - export declare const AUTH_TYPE_TOKEN = "token"; +export class SessConstants { + static readonly AUTH_TYPE_BASIC = "basic"; + static readonly AUTH_TYPE_TOKEN = "token"; + static readonly AUTH_TYPE_CERT_PEM = "cert-pem"; } diff --git a/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts index 1d2f7639d4..056c52cb5b 100644 --- a/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts @@ -11,7 +11,7 @@ import * as fs from "fs"; import * as path from "path"; -import { Gui, ProfilesCache, ZoweVsCodeExtension } from "@zowe/zowe-explorer-api"; +import { Gui, IZoweTreeNode, ProfilesCache } from "@zowe/zowe-explorer-api"; import * as util from "util"; import * as globals from "../../../src/globals"; import * as profUtils from "../../../src/utils/ProfilesUtils"; @@ -359,7 +359,10 @@ describe("ProfilesUtils unit tests", () => { value: jest.fn().mockResolvedValue("emptyConfig"), configurable: true, }); - jest.spyOn(ZoweVsCodeExtension as any, "promptUserPass").mockResolvedValue([]); + jest.spyOn(Gui, "createQuickPick").mockReturnValue({ + show: jest.fn(), + } as unknown as vscode.QuickPick); + jest.spyOn(Gui, "resolveQuickPick").mockResolvedValueOnce(undefined); await profUtils.ProfilesUtils.promptCredentials(null); expect(getProfileInfoSpy).toHaveBeenCalled(); }); @@ -380,13 +383,13 @@ describe("ProfilesUtils unit tests", () => { value: jest.fn().mockResolvedValue(""), configurable: true, }); - jest.spyOn(ZoweVsCodeExtension as any, "promptUserPass").mockResolvedValue([]); await profUtils.ProfilesUtils.promptCredentials(null); expect(Gui.showMessage).toHaveBeenCalledWith("Operation Cancelled"); }); it("shows an info message if the profile credentials were updated", async () => { const mockProfileInstance = new Profiles(zowe.imperative.Logger.getAppLogger()); + mockProfileInstance.getLoadedProfConfig = jest.fn().mockResolvedValue({ name: "testConfig" }); const prof = { getAllProfiles: jest.fn().mockReturnValue([]), isSecured: jest.fn().mockReturnValue(true), @@ -401,6 +404,10 @@ describe("ProfilesUtils unit tests", () => { value: jest.fn().mockResolvedValue("testConfig"), configurable: true, }); + jest.spyOn(Gui, "createQuickPick").mockReturnValue({ + show: jest.fn(), + } as unknown as vscode.QuickPick); + jest.spyOn(Gui, "resolveQuickPick").mockImplementationOnce((qp) => Promise.resolve(qp.activeItems[0])); Object.defineProperty(Gui, "showMessage", { value: jest.fn(), configurable: true, @@ -410,6 +417,62 @@ describe("ProfilesUtils unit tests", () => { expect(Gui.showMessage).toHaveBeenCalledWith("Credentials for testConfig were successfully updated"); }); + it("proceeds with SSO login if auth token option is selected", async () => { + const mockProfileInstance = new Profiles(zowe.imperative.Logger.getAppLogger()); + mockProfileInstance.getLoadedProfConfig = jest.fn().mockResolvedValue({ + profile: { tokenType: "fakeToken" }, + }); + const prof = { + getAllProfiles: jest.fn().mockReturnValue([]), + isSecured: jest.fn().mockReturnValue(true), + readProfilesFromDisk: jest.fn(), + }; + jest.spyOn(ProfilesCache.prototype, "getProfileInfo").mockResolvedValue(prof as unknown as zowe.imperative.ProfileInfo); + jest.spyOn(ProfilesCache.prototype, "getLoadedProfConfig").mockResolvedValue({ + profile: prof, + } as unknown as zowe.imperative.IProfileLoaded); + jest.spyOn(Profiles, "getInstance").mockReturnValue(mockProfileInstance); + Object.defineProperty(vscode.window, "showInputBox", { + value: jest.fn().mockResolvedValue("testConfig"), + configurable: true, + }); + jest.spyOn(Gui, "createQuickPick").mockReturnValue({ + show: jest.fn(), + } as unknown as vscode.QuickPick); + jest.spyOn(Gui, "resolveQuickPick").mockImplementationOnce((qp) => Promise.resolve(qp.activeItems[0])); + const ssoLoginSpy = jest.spyOn(Profiles.prototype, "ssoLogin").mockResolvedValueOnce(); + await profUtils.ProfilesUtils.promptCredentials(null); + expect(ssoLoginSpy).toHaveBeenCalledTimes(1); + }); + + it("proceeds with SSO logout if log out option is selected", async () => { + const mockProfileInstance = new Profiles(zowe.imperative.Logger.getAppLogger()); + const prof = { + getAllProfiles: jest.fn().mockReturnValue([]), + isSecured: jest.fn().mockReturnValue(true), + readProfilesFromDisk: jest.fn(), + }; + jest.spyOn(ProfilesCache.prototype, "getProfileInfo").mockResolvedValue(prof as unknown as zowe.imperative.ProfileInfo); + jest.spyOn(ProfilesCache.prototype, "getLoadedProfConfig").mockResolvedValue({ + profile: prof, + } as unknown as zowe.imperative.IProfileLoaded); + jest.spyOn(Profiles, "getInstance").mockReturnValue(mockProfileInstance); + jest.spyOn(Gui, "createQuickPick").mockReturnValue({ + show: jest.fn(), + } as unknown as vscode.QuickPick); + jest.spyOn(Gui, "resolveQuickPick").mockImplementationOnce((qp) => Promise.resolve(qp.items[3])); + const ssoLogoutSpy = jest.spyOn(Profiles.prototype, "ssoLogout").mockResolvedValueOnce(); + await profUtils.ProfilesUtils.promptCredentials({ + getProfile: jest.fn().mockReturnValue({ + profile: { + tokenType: "fakeToken", + tokenValue: "ThisIsAVeryLongToken", + }, + }), + } as any as IZoweTreeNode); + expect(ssoLogoutSpy).toHaveBeenCalledTimes(1); + }); + it("shows a message if Update Credentials operation is called when autoStore = false", async () => { const mockProfileInstance = new Profiles(zowe.imperative.Logger.getAppLogger()); Object.defineProperty(Profiles, "getInstance", { diff --git a/packages/zowe-explorer/i18n/sample/src/utils/ProfilesUtils.i18n.json b/packages/zowe-explorer/i18n/sample/src/utils/ProfilesUtils.i18n.json index d29d6a9e5a..3f31b3363d 100644 --- a/packages/zowe-explorer/i18n/sample/src/utils/ProfilesUtils.i18n.json +++ b/packages/zowe-explorer/i18n/sample/src/utils/ProfilesUtils.i18n.json @@ -12,9 +12,9 @@ "createNewConnection.option.prompt.profileName.placeholder": "Connection Name", "createNewConnection.option.prompt.profileName": "Enter a name for the connection.", "createNewConnection.undefined.passWord": "Operation Cancelled", - "promptCredentials.quickPick.basicAuthLabel": "$(circle-large) User and Password", + "promptCredentials.quickPick.basicAuthLabel": "User and Password", "promptCredentials.quickPick.basicAuthDescription": "Store username and password", - "promptCredentials.quickPick.tokenAuthLabel": "$(circle-large) Authentication Token", + "promptCredentials.quickPick.tokenAuthLabel": "Authentication Token", "promptCredentials.quickPick.tokenAuthDescription": "Authenticate to service and store token", "promptCredentials.quickPick.logOutLabel": "Log out of Authentication Service", "promptCredentials.quickPick.title": "Select an authentication method", diff --git a/packages/zowe-explorer/src/utils/ProfilesUtils.ts b/packages/zowe-explorer/src/utils/ProfilesUtils.ts index 7a053f56a6..bd3107b74d 100644 --- a/packages/zowe-explorer/src/utils/ProfilesUtils.ts +++ b/packages/zowe-explorer/src/utils/ProfilesUtils.ts @@ -313,10 +313,10 @@ export class ProfilesUtils { Gui.showMessage(msg); return; } - let profile: string | imperative.IProfileLoaded = node?.getProfile(); + let profile: imperative.IProfileLoaded = node?.getProfile(); if (profile == null) { // prompt for profile - profile = ( + const profileName = ( await Gui.showInputBox({ placeHolder: localize("createNewConnection.option.prompt.profileName.placeholder", "Connection Name"), prompt: localize("createNewConnection.option.prompt.profileName", "Enter a name for the connection."), @@ -324,36 +324,38 @@ export class ProfilesUtils { }) ).trim(); - if (!profile) { + if (profileName) { + profile = await Profiles.getInstance().getLoadedProfConfig(profileName); + } else { Gui.showMessage(localize("createNewConnection.undefined.passWord", "Operation Cancelled")); return; } } - const hasTokenValue = typeof profile === "string" || profile.profile?.tokenValue != null; const authTypeChoices: Record = { [imperative.SessConstants.AUTH_TYPE_BASIC]: { - label: localize("promptCredentials.quickPick.basicAuthLabel", "$(circle-large) User and Password"), + label: "$(circle-large) " + localize("promptCredentials.quickPick.basicAuthLabel", "User and Password"), description: localize("promptCredentials.quickPick.basicAuthDescription", "Store username and password"), }, [imperative.SessConstants.AUTH_TYPE_TOKEN]: { - label: localize("promptCredentials.quickPick.tokenAuthLabel", "$(circle-large) Authentication Token"), + label: "$(circle-large) " + localize("promptCredentials.quickPick.tokenAuthLabel", "Authentication Token"), description: localize("promptCredentials.quickPick.tokenAuthDescription", "Authenticate to service and store token"), }, // [imperative.SessConstants.AUTH_TYPE_CERT_PEM]: { - // label: localize("promptCredentials.quickPick.certAuthLabel", "$(circle-large) Certificate File"), + // label: "$(circle-large) " + localize("promptCredentials.quickPick.certAuthLabel", "Certificate File"), // description: localize("promptCredentials.quickPick.certAuthDescription", "Select a PEM certificate file"), // }, }; let currentAuthType = imperative.SessConstants.AUTH_TYPE_BASIC; - if (typeof profile !== "string" && profile.profile?.tokenType != null) { + if (profile.profile?.tokenType != null) { currentAuthType = imperative.SessConstants.AUTH_TYPE_TOKEN; - } else if (typeof profile !== "string" && profile.profile?.certFile != null) { + } else if (profile.profile?.certFile != null) { // currentAuthType = imperative.SessConstants.AUTH_TYPE_CERT_PEM; } authTypeChoices[currentAuthType].label = authTypeChoices[currentAuthType].label.replace("$(circle-large)", "$(record)"); + const isSsoLoggedIn = profile.profile?.tokenValue != null; const quickPickOptions: vscode.QuickPickItem[] = Object.values(authTypeChoices); - if (hasTokenValue) { + if (isSsoLoggedIn) { quickPickOptions.push(globals.SEPARATORS.BLANK, { label: localize("promptCredentials.quickPick.logOutLabel", "Log out of Authentication Service"), }); @@ -361,33 +363,25 @@ export class ProfilesUtils { const qp = Gui.createQuickPick(); qp.items = quickPickOptions; qp.activeItems = [authTypeChoices[currentAuthType]]; - qp.title = localize("promptCredentials.quickPick.title", "Select an authentication method"); - return new Promise((resolve) => { - qp.onDidChangeSelection(async ([item]) => { - if (item === authTypeChoices[imperative.SessConstants.AUTH_TYPE_BASIC]) { - const creds = await Profiles.getInstance().promptCredentials(profile, true); - if (creds != null) { - const successMsg = localize( - "promptCredentials.updatedCredentials", - "Credentials for {0} were successfully updated", - typeof profile === "string" ? profile : profile.name - ); - ZoweLogger.info(successMsg); - Gui.showMessage(successMsg); - // config file watcher isn't noticing changes for secure fields - await vscode.commands.executeCommand("zowe.extRefresh"); - } - } else if (item === authTypeChoices[imperative.SessConstants.AUTH_TYPE_TOKEN]) { - await Profiles.getInstance().ssoLogin(node); - } else if (item === authTypeChoices[imperative.SessConstants.AUTH_TYPE_CERT_PEM]) { - // TODO Add support for certificates - } else if (hasTokenValue && item === quickPickOptions[quickPickOptions.length - 1]) { - await Profiles.getInstance().ssoLogout(node); - } - resolve(); - }); - qp.show(); - }); + qp.placeholder = localize("promptCredentials.quickPick.title", "Select an authentication method"); + qp.show(); + const selectedItem = await Gui.resolveQuickPick(qp); + if (selectedItem === authTypeChoices[imperative.SessConstants.AUTH_TYPE_BASIC]) { + const creds = await Profiles.getInstance().promptCredentials(profile, true); + if (creds != null) { + const successMsg = localize("promptCredentials.updatedCredentials", "Credentials for {0} were successfully updated", profile.name); + ZoweLogger.info(successMsg); + Gui.showMessage(successMsg); + // config file watcher isn't noticing changes for secure fields + await vscode.commands.executeCommand("zowe.extRefresh"); + } + } else if (selectedItem === authTypeChoices[imperative.SessConstants.AUTH_TYPE_TOKEN]) { + await Profiles.getInstance().ssoLogin(node); + } else if (selectedItem === authTypeChoices[imperative.SessConstants.AUTH_TYPE_CERT_PEM]) { + // TODO Add support for certificates + } else if (isSsoLoggedIn && selectedItem === quickPickOptions[quickPickOptions.length - 1]) { + await Profiles.getInstance().ssoLogout(node); + } } public static async initializeZoweFolder(): Promise { From 502fee58e951d42ade2a95213002e77b27c61a49 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Mon, 11 Sep 2023 17:37:25 -0400 Subject: [PATCH 3/7] Hide token option if n/a and update changelog Signed-off-by: Timothy Johnson --- packages/zowe-explorer/CHANGELOG.md | 2 + .../__unit__/utils/ProfilesUtils.unit.test.ts | 56 +++++++++++++++---- .../sample/src/utils/ProfilesUtils.i18n.json | 2 +- .../zowe-explorer/src/utils/ProfilesUtils.ts | 42 ++++++++------ 4 files changed, 74 insertions(+), 28 deletions(-) diff --git a/packages/zowe-explorer/CHANGELOG.md b/packages/zowe-explorer/CHANGELOG.md index b0dd6cf074..87054b5da6 100644 --- a/packages/zowe-explorer/CHANGELOG.md +++ b/packages/zowe-explorer/CHANGELOG.md @@ -6,6 +6,8 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen ### New features and enhancements +- Replaced multiple options for updating credentials with a single "Manage Credentials" option that prompts for preferred authentication type. [#2263](https://github.com/zowe/vscode-extension-for-zowe/issues/2263) + ### Bug fixes ## `2.10.0` diff --git a/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts index 056c52cb5b..d9d12a0a0d 100644 --- a/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts @@ -11,7 +11,7 @@ import * as fs from "fs"; import * as path from "path"; -import { Gui, IZoweTreeNode, ProfilesCache } from "@zowe/zowe-explorer-api"; +import { Gui, IZoweTreeNode, ProfilesCache, ZoweVsCodeExtension } from "@zowe/zowe-explorer-api"; import * as util from "util"; import * as globals from "../../../src/globals"; import * as profUtils from "../../../src/utils/ProfilesUtils"; @@ -21,6 +21,7 @@ import { Profiles } from "../../../src/Profiles"; import { SettingsConfig } from "../../../src/utils/SettingsConfig"; import { ZoweLogger } from "../../../src/utils/LoggerUtils"; import { ZoweExplorerExtender } from "../../../src/ZoweExplorerExtender"; +import { ZoweExplorerApiRegister } from "../../../src/ZoweExplorerApiRegister"; jest.mock("fs"); jest.mock("vscode"); @@ -359,10 +360,7 @@ describe("ProfilesUtils unit tests", () => { value: jest.fn().mockResolvedValue("emptyConfig"), configurable: true, }); - jest.spyOn(Gui, "createQuickPick").mockReturnValue({ - show: jest.fn(), - } as unknown as vscode.QuickPick); - jest.spyOn(Gui, "resolveQuickPick").mockResolvedValueOnce(undefined); + jest.spyOn(ZoweVsCodeExtension as any, "promptUserPass").mockResolvedValue([]); await profUtils.ProfilesUtils.promptCredentials(null); expect(getProfileInfoSpy).toHaveBeenCalled(); }); @@ -404,10 +402,6 @@ describe("ProfilesUtils unit tests", () => { value: jest.fn().mockResolvedValue("testConfig"), configurable: true, }); - jest.spyOn(Gui, "createQuickPick").mockReturnValue({ - show: jest.fn(), - } as unknown as vscode.QuickPick); - jest.spyOn(Gui, "resolveQuickPick").mockImplementationOnce((qp) => Promise.resolve(qp.activeItems[0])); Object.defineProperty(Gui, "showMessage", { value: jest.fn(), configurable: true, @@ -417,7 +411,37 @@ describe("ProfilesUtils unit tests", () => { expect(Gui.showMessage).toHaveBeenCalledWith("Credentials for testConfig were successfully updated"); }); - it("proceeds with SSO login if auth token option is selected", async () => { + it("prompts for credentials if basic auth option is selected in quickpick", async () => { + const mockProfileInstance = new Profiles(zowe.imperative.Logger.getAppLogger()); + const prof = { + getAllProfiles: jest.fn().mockReturnValue([]), + isSecured: jest.fn().mockReturnValue(true), + readProfilesFromDisk: jest.fn(), + }; + jest.spyOn(ProfilesCache.prototype, "getProfileInfo").mockResolvedValue(prof as unknown as zowe.imperative.ProfileInfo); + jest.spyOn(ProfilesCache.prototype, "getLoadedProfConfig").mockResolvedValue({ + profile: prof, + } as unknown as zowe.imperative.IProfileLoaded); + jest.spyOn(Profiles, "getInstance").mockReturnValue(mockProfileInstance); + jest.spyOn(ZoweExplorerApiRegister, "getInstance").mockReturnValueOnce({ + getCommonApi: jest.fn(() => ({ + getTokenTypeName: jest.fn().mockReturnValue("fakeToken"), + })), + } as any); + Object.defineProperty(vscode.window, "showInputBox", { + value: jest.fn().mockResolvedValue("testConfig"), + configurable: true, + }); + jest.spyOn(Gui, "createQuickPick").mockReturnValue({ + show: jest.fn(), + } as unknown as vscode.QuickPick); + jest.spyOn(Gui, "resolveQuickPick").mockImplementationOnce((qp) => Promise.resolve(qp.activeItems[0])); + const promptCredsSpy = jest.spyOn(mockProfileInstance, "promptCredentials").mockResolvedValueOnce([]); + await profUtils.ProfilesUtils.promptCredentials(null); + expect(promptCredsSpy).toHaveBeenCalledTimes(1); + }); + + it("proceeds with SSO login if token auth option is selected in quickpick", async () => { const mockProfileInstance = new Profiles(zowe.imperative.Logger.getAppLogger()); mockProfileInstance.getLoadedProfConfig = jest.fn().mockResolvedValue({ profile: { tokenType: "fakeToken" }, @@ -432,6 +456,11 @@ describe("ProfilesUtils unit tests", () => { profile: prof, } as unknown as zowe.imperative.IProfileLoaded); jest.spyOn(Profiles, "getInstance").mockReturnValue(mockProfileInstance); + jest.spyOn(ZoweExplorerApiRegister, "getInstance").mockReturnValueOnce({ + getCommonApi: jest.fn(() => ({ + getTokenTypeName: jest.fn().mockReturnValue("fakeToken"), + })), + } as any); Object.defineProperty(vscode.window, "showInputBox", { value: jest.fn().mockResolvedValue("testConfig"), configurable: true, @@ -445,7 +474,7 @@ describe("ProfilesUtils unit tests", () => { expect(ssoLoginSpy).toHaveBeenCalledTimes(1); }); - it("proceeds with SSO logout if log out option is selected", async () => { + it("proceeds with SSO logout if log out option is selected in quickpick", async () => { const mockProfileInstance = new Profiles(zowe.imperative.Logger.getAppLogger()); const prof = { getAllProfiles: jest.fn().mockReturnValue([]), @@ -457,6 +486,11 @@ describe("ProfilesUtils unit tests", () => { profile: prof, } as unknown as zowe.imperative.IProfileLoaded); jest.spyOn(Profiles, "getInstance").mockReturnValue(mockProfileInstance); + jest.spyOn(ZoweExplorerApiRegister, "getInstance").mockReturnValueOnce({ + getCommonApi: jest.fn(() => ({ + getTokenTypeName: jest.fn().mockReturnValue("fakeToken"), + })), + } as any); jest.spyOn(Gui, "createQuickPick").mockReturnValue({ show: jest.fn(), } as unknown as vscode.QuickPick); diff --git a/packages/zowe-explorer/i18n/sample/src/utils/ProfilesUtils.i18n.json b/packages/zowe-explorer/i18n/sample/src/utils/ProfilesUtils.i18n.json index 3f31b3363d..bccc26b39f 100644 --- a/packages/zowe-explorer/i18n/sample/src/utils/ProfilesUtils.i18n.json +++ b/packages/zowe-explorer/i18n/sample/src/utils/ProfilesUtils.i18n.json @@ -17,7 +17,7 @@ "promptCredentials.quickPick.tokenAuthLabel": "Authentication Token", "promptCredentials.quickPick.tokenAuthDescription": "Authenticate to service and store token", "promptCredentials.quickPick.logOutLabel": "Log out of Authentication Service", - "promptCredentials.quickPick.title": "Select an authentication method", + "promptCredentials.quickPick.title": "Select authentication method for {0}", "promptCredentials.updatedCredentials": "Credentials for {0} were successfully updated", "initializeZoweFolder.location": "Zowe home directory is located at {0}", "writeOverridesFile.readFile.error": "Reading imperative.json failed. Will try to create file.", diff --git a/packages/zowe-explorer/src/utils/ProfilesUtils.ts b/packages/zowe-explorer/src/utils/ProfilesUtils.ts index bd3107b74d..89331f27cd 100644 --- a/packages/zowe-explorer/src/utils/ProfilesUtils.ts +++ b/packages/zowe-explorer/src/utils/ProfilesUtils.ts @@ -23,6 +23,7 @@ import { imperative, getImperativeConfig } from "@zowe/cli"; import { ZoweExplorerExtender } from "../ZoweExplorerExtender"; import { ZoweLogger } from "./LoggerUtils"; import { SettingsConfig } from "./SettingsConfig"; +import { ZoweExplorerApiRegister } from "../ZoweExplorerApiRegister"; // Set up localization nls.config({ @@ -346,26 +347,35 @@ export class ProfilesUtils { // description: localize("promptCredentials.quickPick.certAuthDescription", "Select a PEM certificate file"), // }, }; - let currentAuthType = imperative.SessConstants.AUTH_TYPE_BASIC; - if (profile.profile?.tokenType != null) { - currentAuthType = imperative.SessConstants.AUTH_TYPE_TOKEN; - } else if (profile.profile?.certFile != null) { - // currentAuthType = imperative.SessConstants.AUTH_TYPE_CERT_PEM; + try { + ZoweExplorerApiRegister.getInstance().getCommonApi(profile).getTokenTypeName(); + } catch { + // This profile does not support token authentication + delete authTypeChoices[imperative.SessConstants.AUTH_TYPE_TOKEN]; } - authTypeChoices[currentAuthType].label = authTypeChoices[currentAuthType].label.replace("$(circle-large)", "$(record)"); const isSsoLoggedIn = profile.profile?.tokenValue != null; const quickPickOptions: vscode.QuickPickItem[] = Object.values(authTypeChoices); - if (isSsoLoggedIn) { - quickPickOptions.push(globals.SEPARATORS.BLANK, { - label: localize("promptCredentials.quickPick.logOutLabel", "Log out of Authentication Service"), - }); + let selectedItem = quickPickOptions[0]; + if (quickPickOptions.length > 1) { + let currentAuthType = imperative.SessConstants.AUTH_TYPE_BASIC; + if (profile.profile?.tokenType != null) { + currentAuthType = imperative.SessConstants.AUTH_TYPE_TOKEN; + } else if (profile.profile?.certFile != null) { + // currentAuthType = imperative.SessConstants.AUTH_TYPE_CERT_PEM; + } + authTypeChoices[currentAuthType].label = authTypeChoices[currentAuthType].label.replace("$(circle-large)", "$(record)"); + if (isSsoLoggedIn) { + quickPickOptions.push(globals.SEPARATORS.BLANK, { + label: localize("promptCredentials.quickPick.logOutLabel", "Log out of Authentication Service"), + }); + } + const qp = Gui.createQuickPick(); + qp.items = quickPickOptions; + qp.activeItems = [authTypeChoices[currentAuthType]]; + qp.placeholder = localize("promptCredentials.quickPick.title", "Select authentication method for {0}", profile.name); + qp.show(); + selectedItem = await Gui.resolveQuickPick(qp); } - const qp = Gui.createQuickPick(); - qp.items = quickPickOptions; - qp.activeItems = [authTypeChoices[currentAuthType]]; - qp.placeholder = localize("promptCredentials.quickPick.title", "Select an authentication method"); - qp.show(); - const selectedItem = await Gui.resolveQuickPick(qp); if (selectedItem === authTypeChoices[imperative.SessConstants.AUTH_TYPE_BASIC]) { const creds = await Profiles.getInstance().promptCredentials(profile, true); if (creds != null) { From a7ed8ccd11b0839c16539b708393e284211a600b Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Tue, 12 Sep 2023 10:37:30 -0400 Subject: [PATCH 4/7] Show base profile name for APIML tokens Signed-off-by: Timothy Johnson --- .../i18n/sample/src/utils/ProfilesUtils.i18n.json | 4 ++-- packages/zowe-explorer/src/utils/ProfilesUtils.ts | 13 +++++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/zowe-explorer/i18n/sample/src/utils/ProfilesUtils.i18n.json b/packages/zowe-explorer/i18n/sample/src/utils/ProfilesUtils.i18n.json index bccc26b39f..f417c7801b 100644 --- a/packages/zowe-explorer/i18n/sample/src/utils/ProfilesUtils.i18n.json +++ b/packages/zowe-explorer/i18n/sample/src/utils/ProfilesUtils.i18n.json @@ -13,9 +13,9 @@ "createNewConnection.option.prompt.profileName": "Enter a name for the connection.", "createNewConnection.undefined.passWord": "Operation Cancelled", "promptCredentials.quickPick.basicAuthLabel": "User and Password", - "promptCredentials.quickPick.basicAuthDescription": "Store username and password", + "promptCredentials.quickPick.basicAuthDetail": "Store username and password", "promptCredentials.quickPick.tokenAuthLabel": "Authentication Token", - "promptCredentials.quickPick.tokenAuthDescription": "Authenticate to service and store token", + "promptCredentials.quickPick.tokenAuthDetail": "Authenticate to service and store token", "promptCredentials.quickPick.logOutLabel": "Log out of Authentication Service", "promptCredentials.quickPick.title": "Select authentication method for {0}", "promptCredentials.updatedCredentials": "Credentials for {0} were successfully updated", diff --git a/packages/zowe-explorer/src/utils/ProfilesUtils.ts b/packages/zowe-explorer/src/utils/ProfilesUtils.ts index 89331f27cd..fa1cd1d143 100644 --- a/packages/zowe-explorer/src/utils/ProfilesUtils.ts +++ b/packages/zowe-explorer/src/utils/ProfilesUtils.ts @@ -336,19 +336,24 @@ export class ProfilesUtils { const authTypeChoices: Record = { [imperative.SessConstants.AUTH_TYPE_BASIC]: { label: "$(circle-large) " + localize("promptCredentials.quickPick.basicAuthLabel", "User and Password"), - description: localize("promptCredentials.quickPick.basicAuthDescription", "Store username and password"), + detail: localize("promptCredentials.quickPick.basicAuthDetail", "Store username and password"), }, [imperative.SessConstants.AUTH_TYPE_TOKEN]: { label: "$(circle-large) " + localize("promptCredentials.quickPick.tokenAuthLabel", "Authentication Token"), - description: localize("promptCredentials.quickPick.tokenAuthDescription", "Authenticate to service and store token"), + detail: localize("promptCredentials.quickPick.tokenAuthDetail", "Authenticate to service and store token"), }, // [imperative.SessConstants.AUTH_TYPE_CERT_PEM]: { // label: "$(circle-large) " + localize("promptCredentials.quickPick.certAuthLabel", "Certificate File"), - // description: localize("promptCredentials.quickPick.certAuthDescription", "Select a PEM certificate file"), + // detail: localize("promptCredentials.quickPick.certAuthDetail", "Select a PEM certificate file"), // }, }; try { - ZoweExplorerApiRegister.getInstance().getCommonApi(profile).getTokenTypeName(); + const loginTokenType = ZoweExplorerApiRegister.getInstance().getCommonApi(profile).getTokenTypeName(); + if (loginTokenType === imperative.SessConstants.TOKEN_TYPE_APIML) { + // Token is stored outside this profile so show base profile name + const baseProfile = await Profiles.getInstance().fetchBaseProfile(); + authTypeChoices[imperative.SessConstants.AUTH_TYPE_TOKEN].description = baseProfile?.name; + } } catch { // This profile does not support token authentication delete authTypeChoices[imperative.SessConstants.AUTH_TYPE_TOKEN]; From ffeb9f10e4ac943d4349f013c3f830e27808fd61 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Tue, 12 Sep 2023 13:26:03 -0400 Subject: [PATCH 5/7] Update i18n files after merge Signed-off-by: Timothy Johnson --- packages/zowe-explorer/i18n/sample/src/Profiles.i18n.json | 3 +-- .../zowe-explorer/i18n/sample/src/dataset/actions.i18n.json | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/zowe-explorer/i18n/sample/src/Profiles.i18n.json b/packages/zowe-explorer/i18n/sample/src/Profiles.i18n.json index 5885df7642..8c54f161a0 100644 --- a/packages/zowe-explorer/i18n/sample/src/Profiles.i18n.json +++ b/packages/zowe-explorer/i18n/sample/src/Profiles.i18n.json @@ -27,9 +27,8 @@ "validateProfiles.cancelled": "Validating {0} was cancelled.", "validateProfiles.error": "Profile validation failed for {0}.", "ssoAuth.noBase": "This profile does not support token authentication.", - "ssoLogin.error": "Unable to log in with {0}. {1}", - "ssoLogin.unableToLogin": "Unable to log in with {0}. {1}", "ssoLogin.successful": "Login to authentication service was successful.", + "ssoLogin.error": "Unable to log in with {0}. {1}", "ssoLogout.successful": "Logout from authentication service was successful for {0}.", "ssoLogout.error": "Unable to log out with {0}. {1}", "getConfigLocationPrompt.placeholder.create": "Select the location where the config file will be initialized", diff --git a/packages/zowe-explorer/i18n/sample/src/dataset/actions.i18n.json b/packages/zowe-explorer/i18n/sample/src/dataset/actions.i18n.json index dc19e2af70..aa1718439c 100644 --- a/packages/zowe-explorer/i18n/sample/src/dataset/actions.i18n.json +++ b/packages/zowe-explorer/i18n/sample/src/dataset/actions.i18n.json @@ -29,9 +29,9 @@ "createMember.member.validation": "Enter valid member name", "createMember.creating": "Creating new data set member {0}", "createMember.error": "Unable to create member.", + "dataSet.opening": "$(sync~spin) Opening data set...", "openPS.error": "Invalid data set or member.", "openPS.openDataSet": "Opening {0}", - "dataSet.opening": "$(sync~spin) Opening data set...", "createFile.allocatingNewDataSet": "Attempting to allocate new data set", "handleUserSelection.qp.prompt": "Click on parameters to change them", "createFile.inputBox.placeHolder": "Name of Data Set", From f779c5b317c4b9fb514f1095864ec7131c2a0210 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Thu, 14 Sep 2023 12:57:35 -0400 Subject: [PATCH 6/7] Improve error handling and i18n string ids Signed-off-by: Timothy Johnson --- .../__mocks__/@zowe/imperative.ts | 1 + .../sample/src/utils/ProfilesUtils.i18n.json | 12 ++++---- .../zowe-explorer/src/utils/ProfilesUtils.ts | 29 ++++++++++--------- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/packages/zowe-explorer/__mocks__/@zowe/imperative.ts b/packages/zowe-explorer/__mocks__/@zowe/imperative.ts index d2b78dfb57..d625536cd2 100644 --- a/packages/zowe-explorer/__mocks__/@zowe/imperative.ts +++ b/packages/zowe-explorer/__mocks__/@zowe/imperative.ts @@ -360,4 +360,5 @@ export class SessConstants { static readonly AUTH_TYPE_BASIC = "basic"; static readonly AUTH_TYPE_TOKEN = "token"; static readonly AUTH_TYPE_CERT_PEM = "cert-pem"; + static readonly TOKEN_TYPE_APIML = "apimlAuthenticationToken"; } diff --git a/packages/zowe-explorer/i18n/sample/src/utils/ProfilesUtils.i18n.json b/packages/zowe-explorer/i18n/sample/src/utils/ProfilesUtils.i18n.json index f417c7801b..04c6197e3f 100644 --- a/packages/zowe-explorer/i18n/sample/src/utils/ProfilesUtils.i18n.json +++ b/packages/zowe-explorer/i18n/sample/src/utils/ProfilesUtils.i18n.json @@ -12,12 +12,12 @@ "createNewConnection.option.prompt.profileName.placeholder": "Connection Name", "createNewConnection.option.prompt.profileName": "Enter a name for the connection.", "createNewConnection.undefined.passWord": "Operation Cancelled", - "promptCredentials.quickPick.basicAuthLabel": "User and Password", - "promptCredentials.quickPick.basicAuthDetail": "Store username and password", - "promptCredentials.quickPick.tokenAuthLabel": "Authentication Token", - "promptCredentials.quickPick.tokenAuthDetail": "Authenticate to service and store token", - "promptCredentials.quickPick.logOutLabel": "Log out of Authentication Service", - "promptCredentials.quickPick.title": "Select authentication method for {0}", + "promptCredentials.qp.basicAuthLabel": "User and Password", + "promptCredentials.qp.basicAuthDetail": "Store username and password", + "promptCredentials.qp.tokenAuthLabel": "Authentication Token", + "promptCredentials.qp.tokenAuthDetail": "Authenticate to service and store token", + "promptCredentials.qp.logOutLabel": "Log out of Authentication Service", + "promptCredentials.qp.title": "Select authentication method for {0}", "promptCredentials.updatedCredentials": "Credentials for {0} were successfully updated", "initializeZoweFolder.location": "Zowe home directory is located at {0}", "writeOverridesFile.readFile.error": "Reading imperative.json failed. Will try to create file.", diff --git a/packages/zowe-explorer/src/utils/ProfilesUtils.ts b/packages/zowe-explorer/src/utils/ProfilesUtils.ts index 1951dacdc9..d0c585abc8 100644 --- a/packages/zowe-explorer/src/utils/ProfilesUtils.ts +++ b/packages/zowe-explorer/src/utils/ProfilesUtils.ts @@ -353,29 +353,30 @@ export class ProfilesUtils { const authTypeChoices: Record = { [imperative.SessConstants.AUTH_TYPE_BASIC]: { - label: "$(circle-large) " + localize("promptCredentials.quickPick.basicAuthLabel", "User and Password"), - detail: localize("promptCredentials.quickPick.basicAuthDetail", "Store username and password"), + label: "$(circle-large) " + localize("promptCredentials.qp.basicAuthLabel", "User and Password"), + detail: localize("promptCredentials.qp.basicAuthDetail", "Store username and password"), }, [imperative.SessConstants.AUTH_TYPE_TOKEN]: { - label: "$(circle-large) " + localize("promptCredentials.quickPick.tokenAuthLabel", "Authentication Token"), - detail: localize("promptCredentials.quickPick.tokenAuthDetail", "Authenticate to service and store token"), + label: "$(circle-large) " + localize("promptCredentials.qp.tokenAuthLabel", "Authentication Token"), + detail: localize("promptCredentials.qp.tokenAuthDetail", "Authenticate to service and store token"), }, // [imperative.SessConstants.AUTH_TYPE_CERT_PEM]: { - // label: "$(circle-large) " + localize("promptCredentials.quickPick.certAuthLabel", "Certificate File"), - // detail: localize("promptCredentials.quickPick.certAuthDetail", "Select a PEM certificate file"), + // label: "$(circle-large) " + localize("promptCredentials.qp.certAuthLabel", "Certificate File"), + // detail: localize("promptCredentials.qp.certAuthDetail", "Select a PEM certificate file"), // }, }; + let loginTokenType: string; try { - const loginTokenType = ZoweExplorerApiRegister.getInstance().getCommonApi(profile).getTokenTypeName(); - if (loginTokenType === imperative.SessConstants.TOKEN_TYPE_APIML) { - // Token is stored outside this profile so show base profile name - const baseProfile = await Profiles.getInstance().fetchBaseProfile(); - authTypeChoices[imperative.SessConstants.AUTH_TYPE_TOKEN].description = baseProfile?.name; - } + loginTokenType = ZoweExplorerApiRegister.getInstance().getCommonApi(profile).getTokenTypeName(); } catch { // This profile does not support token authentication delete authTypeChoices[imperative.SessConstants.AUTH_TYPE_TOKEN]; } + if (loginTokenType === imperative.SessConstants.TOKEN_TYPE_APIML) { + // Token is stored outside this profile so show base profile name + const baseProfile = await Profiles.getInstance().fetchBaseProfile(profile.name); + authTypeChoices[imperative.SessConstants.AUTH_TYPE_TOKEN].description = baseProfile?.name; + } const isSsoLoggedIn = profile.profile?.tokenValue != null; const quickPickOptions: vscode.QuickPickItem[] = Object.values(authTypeChoices); let selectedItem = quickPickOptions[0]; @@ -389,13 +390,13 @@ export class ProfilesUtils { authTypeChoices[currentAuthType].label = authTypeChoices[currentAuthType].label.replace("$(circle-large)", "$(record)"); if (isSsoLoggedIn) { quickPickOptions.push(globals.SEPARATORS.BLANK, { - label: localize("promptCredentials.quickPick.logOutLabel", "Log out of Authentication Service"), + label: localize("promptCredentials.qp.logOutLabel", "Log out of Authentication Service"), }); } const qp = Gui.createQuickPick(); qp.items = quickPickOptions; qp.activeItems = [authTypeChoices[currentAuthType]]; - qp.placeholder = localize("promptCredentials.quickPick.title", "Select authentication method for {0}", profile.name); + qp.placeholder = localize("promptCredentials.qp.title", "Select authentication method for {0}", profile.name); qp.show(); selectedItem = await Gui.resolveQuickPick(qp); } From 5a6fc3abdedf6d3503469b40820fcd00db82fefc Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Mon, 18 Sep 2023 10:51:34 -0400 Subject: [PATCH 7/7] Hide quickpick and fix Coverity issue Signed-off-by: Timothy Johnson --- .../__tests__/__unit__/utils/ProfilesUtils.unit.test.ts | 3 +++ packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts | 2 +- packages/zowe-explorer/src/utils/ProfilesUtils.ts | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts index 259df814e9..9bf9776843 100644 --- a/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts @@ -464,6 +464,7 @@ describe("ProfilesUtils unit tests", () => { }); jest.spyOn(Gui, "createQuickPick").mockReturnValue({ show: jest.fn(), + hide: jest.fn(), } as unknown as vscode.QuickPick); jest.spyOn(Gui, "resolveQuickPick").mockImplementationOnce((qp) => Promise.resolve(qp.activeItems[0])); const promptCredsSpy = jest.spyOn(mockProfileInstance, "promptCredentials").mockResolvedValueOnce([]); @@ -497,6 +498,7 @@ describe("ProfilesUtils unit tests", () => { }); jest.spyOn(Gui, "createQuickPick").mockReturnValue({ show: jest.fn(), + hide: jest.fn(), } as unknown as vscode.QuickPick); jest.spyOn(Gui, "resolveQuickPick").mockImplementationOnce((qp) => Promise.resolve(qp.activeItems[0])); const ssoLoginSpy = jest.spyOn(Profiles.prototype, "ssoLogin").mockResolvedValueOnce(); @@ -523,6 +525,7 @@ describe("ProfilesUtils unit tests", () => { } as any); jest.spyOn(Gui, "createQuickPick").mockReturnValue({ show: jest.fn(), + hide: jest.fn(), } as unknown as vscode.QuickPick); jest.spyOn(Gui, "resolveQuickPick").mockImplementationOnce((qp) => Promise.resolve(qp.items[3])); const ssoLogoutSpy = jest.spyOn(Profiles.prototype, "ssoLogout").mockResolvedValueOnce(); diff --git a/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts b/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts index 2e966abb34..8d369eb00d 100644 --- a/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts +++ b/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts @@ -305,7 +305,7 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod ), ]; const mvsApi = ZoweExplorerApiRegister.getMvsApi(cachedProfile); - if (!mvsApi.getSession(mvsApi?.profile)) { + if (!mvsApi.getSession(cachedProfile)) { throw new zowe.imperative.ImperativeError({ msg: localize("getDataSets.error.sessionMissing", "Profile auth error"), additionalDetails: localize("getDataSets.error.additionalDetails", "Profile is not authenticated, please log in to continue"), diff --git a/packages/zowe-explorer/src/utils/ProfilesUtils.ts b/packages/zowe-explorer/src/utils/ProfilesUtils.ts index 9f12f32e8b..d8b3028639 100644 --- a/packages/zowe-explorer/src/utils/ProfilesUtils.ts +++ b/packages/zowe-explorer/src/utils/ProfilesUtils.ts @@ -397,6 +397,7 @@ export class ProfilesUtils { qp.placeholder = localize("promptCredentials.qp.title", "Select authentication method for {0}", profile.name); qp.show(); selectedItem = await Gui.resolveQuickPick(qp); + qp.hide(); } if (selectedItem === authTypeChoices[imperative.SessConstants.AUTH_TYPE_BASIC]) { const creds = await Profiles.getInstance().promptCredentials(profile, true);