diff --git a/packages/zowe-explorer/CHANGELOG.md b/packages/zowe-explorer/CHANGELOG.md index e91bf6ae2b..347bd0a1a9 100644 --- a/packages/zowe-explorer/CHANGELOG.md +++ b/packages/zowe-explorer/CHANGELOG.md @@ -13,6 +13,7 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen ### New features and enhancements - Allow deleting migrated datasets [#2447](https://github.com/zowe/vscode-extension-for-zowe/issues/2447) +- 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 diff --git a/packages/zowe-explorer/__mocks__/@zowe/imperative.ts b/packages/zowe-explorer/__mocks__/@zowe/imperative.ts index c85cc88a6e..d625536cd2 100644 --- a/packages/zowe-explorer/__mocks__/@zowe/imperative.ts +++ b/packages/zowe-explorer/__mocks__/@zowe/imperative.ts @@ -356,6 +356,9 @@ 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"; + static readonly TOKEN_TYPE_APIML = "apimlAuthenticationToken"; } 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 65a2121ff8..9bf9776843 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, 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"; import { createValidIProfile } from "../../../__mocks__/mockCreators/shared"; jest.mock("fs"); @@ -410,13 +411,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), @@ -440,6 +441,105 @@ describe("ProfilesUtils unit tests", () => { expect(Gui.showMessage).toHaveBeenCalledWith("Credentials for testConfig were successfully updated"); }); + 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(), + 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([]); + 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" }, + }); + 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(), + 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(); + await profUtils.ProfilesUtils.promptCredentials(null); + expect(ssoLoginSpy).toHaveBeenCalledTimes(1); + }); + + 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([]), + 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); + 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(); + 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/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 2ed2cbc4ff..02b81b9f6f 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.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/package.json b/packages/zowe-explorer/package.json index c999eba9aa..12b8839928 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", @@ -1154,30 +1144,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", @@ -1319,30 +1299,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/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 612284de16..d8b3028639 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({ @@ -329,10 +330,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."), @@ -340,24 +341,79 @@ 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 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 authTypeChoices: Record = { + [imperative.SessConstants.AUTH_TYPE_BASIC]: { + 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.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.qp.certAuthLabel", "Certificate File"), + // detail: localize("promptCredentials.qp.certAuthDetail", "Select a PEM certificate file"), + // }, + }; + let loginTokenType: string; + try { + 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(); + 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]; + 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.qp.logOutLabel", "Log out of Authentication Service"), + }); + } + const qp = Gui.createQuickPick(); + qp.items = quickPickOptions; + qp.activeItems = [authTypeChoices[currentAuthType]]; + 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); + 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); } }