Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance UX for managing credentials #2445

Closed
wants to merge 11 commits into from
1 change: 1 addition & 0 deletions packages/zowe-explorer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
JillieBeanSim marked this conversation as resolved.
Show resolved Hide resolved

### Bug fixes

Expand Down
7 changes: 5 additions & 2 deletions packages/zowe-explorer/__mocks__/@zowe/imperative.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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");
Expand Down Expand Up @@ -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),
Expand All @@ -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<vscode.QuickPickItem>);
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<vscode.QuickPickItem>);
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<vscode.QuickPickItem>);
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", {
Expand Down
2 changes: 1 addition & 1 deletion packages/zowe-explorer/i18n/sample/package.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
66 changes: 18 additions & 48 deletions packages/zowe-explorer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion packages/zowe-explorer/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
Expand Down
2 changes: 1 addition & 1 deletion packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
Loading