Skip to content

Commit

Permalink
Merge pull request #2437 from zowe/refresh-after-logout
Browse files Browse the repository at this point in the history
Refresh Zowe Explorer after token auth logout
  • Loading branch information
JillieBeanSim authored Sep 11, 2023
2 parents d171e90 + d6d76c5 commit 755df8f
Show file tree
Hide file tree
Showing 13 changed files with 334 additions and 97 deletions.
1 change: 1 addition & 0 deletions packages/zowe-explorer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen

### Bug fixes

- Fixed issue with endless credential prompt loop when logging out. [#2262](https://github.com/zowe/vscode-extension-for-zowe/issues/2262)
- Bump `@zowe/secrets-for-zowe-sdk` to 7.18.3 to handle install errors gracefully and to allow running without MSVC redistributables.
- Fixed issue where data set content does not always appear as soon as the editor is opened. [#2427](https://github.com/zowe/vscode-extension-for-zowe/issues/2427)
- Adjust scope of "Security: Secure Credentials Enabled" setting to `machine-overridable` so it appears again in certain cloud IDEs.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1303,7 +1303,7 @@ describe("Profiles Unit Tests - function deleteProfile", () => {
});

describe("Profiles Unit Tests - function checkCurrentProfile", () => {
const environmentSetup = (globalMocks) => {
const environmentSetup = (globalMocks): void => {
globalMocks.testProfile.profile.password = null;
globalMocks.testProfile.profile.tokenType = "";
Object.defineProperty(Profiles.getInstance(), "profilesForValidation", {
Expand All @@ -1329,23 +1329,57 @@ describe("Profiles Unit Tests - function checkCurrentProfile", () => {
});
};

const setupProfilesCheck = (globalMocks): void => {
jest.spyOn(Profiles.getInstance(), "getDefaultProfile").mockReturnValue({ name: "base" } as any);
jest.spyOn(Profiles.getInstance(), "getProfileInfo").mockResolvedValue({
getTeamConfig: () => ({
properties: {
profiles: {
sestest: { ...globalMocks.testProfile.profile, secure: [] },
base: {
type: "base",
host: "test",
port: 1443,
rejectUnauthorized: false,
name: "base",
tokenType: "",
secure: [],
},
},
},
}),
} as any);
jest.spyOn(Profiles.getInstance(), "getLoadedProfConfig").mockResolvedValue(globalMocks.testProfile);
jest.spyOn(Profiles.getInstance(), "getSecurePropsForProfile").mockResolvedValue([]);
};

it("should show as active in status of profile", async () => {
const globalMocks = await createGlobalMocks();
environmentSetup(globalMocks);
setupProfilesCheck(globalMocks);
jest.spyOn(Profiles.getInstance(), "validateProfiles").mockReturnValue({ status: "active", name: "sestest" } as any);
jest.spyOn(Profiles.getInstance(), "promptCredentials").mockResolvedValue(["sestest", "12345", "base64Auth"]);
await expect(Profiles.getInstance().checkCurrentProfile(globalMocks.testProfile)).resolves.toEqual({ name: "sestest", status: "active" });
});
it("should show as unverified in status of profile", async () => {
const globalMocks = await createGlobalMocks();
environmentSetup(globalMocks);
setupProfilesCheck(globalMocks);
jest.spyOn(Profiles.getInstance(), "promptCredentials").mockResolvedValue(undefined);
await expect(Profiles.getInstance().checkCurrentProfile(globalMocks.testProfile)).resolves.toEqual({ name: "sestest", status: "unverified" });
});
it("should show as inactive in status of profile", async () => {
const globalMocks = await createGlobalMocks();
setupProfilesCheck(globalMocks);
await expect(Profiles.getInstance().checkCurrentProfile(globalMocks.testProfile)).resolves.toEqual({ name: "sestest", status: "inactive" });
});
it("should throw an error if using token auth and is logged out or has expired token", async () => {
const globalMocks = await createGlobalMocks();
jest.spyOn(utils, "errorHandling").mockImplementation();
jest.spyOn(utils, "isUsingTokenAuth").mockResolvedValue(true);
setupProfilesCheck(globalMocks);
await expect(Profiles.getInstance().checkCurrentProfile(globalMocks.testProfile)).resolves.toEqual({ name: "sestest", status: "unverified" });
});
});

describe("Profiles Unit Tests - function editSession", () => {
Expand Down Expand Up @@ -1582,6 +1616,47 @@ describe("Profiles Unit Tests - function ssoLogin", () => {
});
});

describe("Profiles Unit Tests - function ssoLogout", () => {
let testNode;
let globalMocks;
beforeEach(async () => {
globalMocks = await createGlobalMocks();
testNode = new (ZoweTreeNode as any)(
"fake",
vscode.TreeItemCollapsibleState.None,
undefined,
globalMocks.testSession,
globalMocks.testProfile
);
testNode.profile.profile.password = undefined;
testNode.profile.profile.user = "fake";
Object.defineProperty(Profiles.getInstance(), "allProfiles", {
value: [
{
name: "fake",
},
],
configurable: true,
});
jest.spyOn(Gui, "showMessage").mockImplementation();
});
it("should logout successfully and refresh zowe explorer", async () => {
const getTokenTypeNameMock = jest.fn();
const logoutMock = jest.fn();
jest.spyOn(ZoweExplorerApiRegister.getInstance(), "getCommonApi").mockImplementation(() => ({
logout: logoutMock,
getSession: jest.fn(),
getProfileTypeName: jest.fn(),
getTokenTypeName: getTokenTypeNameMock,
}));
const updateBaseProfileFileLogoutSpy = jest.spyOn(Profiles.getInstance() as any, "updateBaseProfileFileLogout").mockImplementation();
await expect(Profiles.getInstance().ssoLogout(testNode)).resolves.not.toThrow();
expect(getTokenTypeNameMock).toBeCalledTimes(1);
expect(logoutMock).toBeCalledTimes(1);
expect(updateBaseProfileFileLogoutSpy).toBeCalledTimes(1);
});
});

describe("Profiles Unit Tests - function updateBaseProfileFileLogin", () => {
it("should update the property of mProfileInfo", async () => {
const privateProfile = Profiles.getInstance() as any;
Expand Down Expand Up @@ -1701,3 +1776,26 @@ describe("Profiles Unit Tests - function loginCredentialPrompt", () => {
expect(showMessageSpy).toBeCalledTimes(1);
});
});

describe("Profiles Unit Tests - function getSecurePropsForProfile", () => {
afterEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
});
it("should retrieve the secure properties of a profile", async () => {
const globalMocks = await createGlobalMocks();
jest.spyOn(Profiles.getInstance(), "getProfileInfo").mockResolvedValue({
mergeArgsForProfile: () => ({
knownArgs: [
{
argName: "tokenValue",
secure: true,
} as any,
],
missingArgs: [],
}),
getAllProfiles: () => [],
} as any);
await expect(Profiles.getInstance().getSecurePropsForProfile(globalMocks.testProfile.name ?? "")).resolves.toEqual(["tokenValue"]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ describe("ZoweJobNode unit tests - Function checkCurrentProfile", () => {
it("Tests that checkCurrentProfile is executed successfully with inactive status", async () => {
const globalMocks = await createGlobalMocks();
const blockMocks = await createBlockMocks(globalMocks);
blockMocks.jobNode.contextValue = "SERVER";
blockMocks.jobNode.contextValue = "session";
globalMocks.mockCheckCurrentProfile.mockReturnValueOnce({
name: globalMocks.testProfile.name,
status: "inactive",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,17 @@ describe("ZosJobsProvider unit tests - Function getChildren", () => {

expect(elementGetChildrenSpy).toHaveBeenCalledTimes(1);
});
it("Tests that getChildren returns the empty array if status of profile is unverified", async () => {
const globalMocks = await createGlobalMocks();
jest.spyOn(Profiles.getInstance(), "checkCurrentProfile").mockResolvedValueOnce({ status: "unverified" } as any);
const blockMocks = createBlockMocks(globalMocks);
mocked(vscode.window.createTreeView).mockReturnValueOnce(blockMocks.treeView);
const testTree = new ZosJobsProvider();
testTree.mSessionNodes.push(blockMocks.jobSessionNode);
testTree.mSessionNodes[1].dirty = true;

await expect(testTree.getChildren(testTree.mSessionNodes[1])).resolves.toEqual([]);
});
});

describe("ZosJobsProvider unit tests - Function initializeFavChildNodeForProfile", () => {
Expand Down
21 changes: 21 additions & 0 deletions packages/zowe-explorer/__tests__/__unit__/uss/USSTree.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1424,6 +1424,27 @@ describe("USSTree Unit Tests - Function USSTree.getChildren()", () => {

expect(loadProfilesForFavoritesSpy).toHaveBeenCalledWith(log, favProfileNode);
});

it("Testing that getChildren() returns the empty array if the profile has unverified status", async () => {
const globalMocks = await createGlobalMocks();

const testDir = new ZoweUSSNode("aDir", vscode.TreeItemCollapsibleState.Collapsed, globalMocks.testTree.mSessionNodes[1], null, "test");
globalMocks.testTree.mSessionNodes[1].children.push(testDir);
const mockApiResponseItems = {
items: [
{
mode: "d",
mSessionName: "sestest",
name: "aDir",
},
],
};
const mockApiResponseWithItems = createFileResponse(mockApiResponseItems);
globalMocks.withProgress.mockReturnValue(mockApiResponseWithItems);
jest.spyOn(Profiles.getInstance(), "checkCurrentProfile").mockResolvedValueOnce({ status: "unverified" } as any);
const sessChildren = await globalMocks.testTree.getChildren(globalMocks.testTree.mSessionNodes[1]);
expect(sessChildren.length).toEqual(0);
});
});

// Idea is borrowed from: https://github.com/kulshekhar/ts-jest/blob/master/src/util/testing.ts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 { createValidIProfile } from "../../../__mocks__/mockCreators/shared";

jest.mock("fs");
jest.mock("vscode");
Expand Down Expand Up @@ -61,6 +62,25 @@ describe("ProfilesUtils unit tests", () => {
}

describe("errorHandling", () => {
const profileInfoMock = () => ({
getTeamConfig: () => ({
properties: {
profiles: {
sestest: createValidIProfile().profile,
base: {
type: "base",
host: "test",
port: 1443,
rejectUnauthorized: false,
name: "base",
tokenType: "",
secure: [],
},
},
},
}),
});

it("should log error details", async () => {
createBlockMocks();
const errorDetails = new Error("i haz error");
Expand Down Expand Up @@ -129,11 +149,13 @@ describe("ProfilesUtils unit tests", () => {
jest.spyOn(profUtils, "isTheia").mockReturnValue(false);
const showMessageSpy = jest.spyOn(Gui, "errorMessage").mockImplementation(() => Promise.resolve("Update Credentials"));
const promptCredsSpy = jest.fn();
Object.defineProperty(Profiles, "getInstance", {
value: () => ({
promptCredentials: promptCredsSpy,
}),
});
jest.spyOn(Profiles, "getInstance").mockReturnValue({
promptCredentials: promptCredsSpy,
getProfileInfo: profileInfoMock,
getLoadedProfConfig: () => ({ type: "zosmf" }),
getDefaultProfile: () => ({}),
getSecurePropsForProfile: () => [],
} as any);
await profUtils.errorHandling(errorDetails, label, moreInfo);
expect(showMessageSpy).toBeCalledTimes(1);
expect(promptCredsSpy).toBeCalledTimes(1);
Expand All @@ -152,11 +174,13 @@ describe("ProfilesUtils unit tests", () => {
const showErrorSpy = jest.spyOn(Gui, "errorMessage");
const showMessageSpy = jest.spyOn(Gui, "showMessage").mockImplementation(() => Promise.resolve("selection"));
const ssoLoginSpy = jest.fn();
Object.defineProperty(Profiles, "getInstance", {
value: () => ({
ssoLogin: ssoLoginSpy,
}),
});
jest.spyOn(Profiles, "getInstance").mockReturnValue({
getProfileInfo: profileInfoMock,
getLoadedProfConfig: () => ({ type: "zosmf" }),
getDefaultProfile: () => ({}),
getSecurePropsForProfile: () => ["tokenValue"],
ssoLogin: ssoLoginSpy,
} as any);
await profUtils.errorHandling(errorDetails, label, moreInfo);
expect(showMessageSpy).toBeCalledTimes(1);
expect(ssoLoginSpy).toBeCalledTimes(1);
Expand All @@ -182,11 +206,13 @@ describe("ProfilesUtils unit tests", () => {
const showErrorSpy = jest.spyOn(Gui, "errorMessage").mockImplementation(() => Promise.resolve(undefined));
const showMessageSpy = jest.spyOn(Gui, "showMessage");
const ssoLoginSpy = jest.fn();
Object.defineProperty(Profiles, "getInstance", {
value: () => ({
ssoLogin: ssoLoginSpy,
}),
});
jest.spyOn(Profiles, "getInstance").mockReturnValue({
getProfileInfo: profileInfoMock,
getLoadedProfConfig: () => ({ type: "zosmf" }),
getDefaultProfile: () => ({}),
getSecurePropsForProfile: () => ["tokenValue"],
ssoLogin: ssoLoginSpy,
} as any);
await profUtils.errorHandling(errorDetails, label, moreInfo);
expect(showErrorSpy).toBeCalledTimes(1);
expect(ssoLoginSpy).toBeCalledTimes(1);
Expand All @@ -212,11 +238,13 @@ describe("ProfilesUtils unit tests", () => {
const showErrorSpy = jest.spyOn(Gui, "errorMessage").mockResolvedValue(undefined);
const showMsgSpy = jest.spyOn(Gui, "showMessage");
const promptCredentialsSpy = jest.fn();
Object.defineProperty(Profiles, "getInstance", {
value: () => ({
promptCredentials: promptCredentialsSpy,
}),
});
jest.spyOn(Profiles, "getInstance").mockReturnValue({
promptCredentials: promptCredentialsSpy,
getProfileInfo: profileInfoMock,
getLoadedProfConfig: () => ({ type: "zosmf" }),
getDefaultProfile: () => ({}),
getSecurePropsForProfile: () => [],
} as any);
await profUtils.errorHandling(errorDetails, label, moreInfo);
expect(showErrorSpy).toBeCalledTimes(1);
expect(promptCredentialsSpy).not.toBeCalled();
Expand All @@ -236,11 +264,13 @@ describe("ProfilesUtils unit tests", () => {
jest.spyOn(profUtils, "isTheia").mockReturnValue(true);
const showErrorSpy = jest.spyOn(Gui, "errorMessage");
const promptCredentialsSpy = jest.fn();
Object.defineProperty(Profiles, "getInstance", {
value: () => ({
promptCredentials: promptCredentialsSpy,
}),
});
jest.spyOn(Profiles, "getInstance").mockReturnValue({
promptCredentials: promptCredentialsSpy,
getProfileInfo: profileInfoMock,
getLoadedProfConfig: () => ({ type: "zosmf" }),
getDefaultProfile: () => ({}),
getSecurePropsForProfile: () => [],
} as any);
await profUtils.errorHandling(errorDetails, label, moreInfo);
expect(showErrorSpy).toBeCalledTimes(1);
expect(promptCredentialsSpy).not.toBeCalled();
Expand Down Expand Up @@ -707,4 +737,13 @@ describe("ProfilesUtils unit tests", () => {
expect(defaultCredMgrSpy).toHaveBeenCalledWith(ProfilesCache.requireKeyring);
});
});

describe("isUsingTokenAuth", () => {
it("should check the profile in use if using token based auth instead of the base profile", async () => {
jest.spyOn(Profiles.getInstance(), "getDefaultProfile").mockReturnValueOnce({} as any);
jest.spyOn(Profiles.getInstance(), "getLoadedProfConfig").mockResolvedValue({ type: "test" } as any);
jest.spyOn(Profiles.getInstance(), "getSecurePropsForProfile").mockResolvedValue([]);
await expect(profUtils.isUsingTokenAuth("test")).resolves.toEqual(false);
});
});
});
2 changes: 2 additions & 0 deletions packages/zowe-explorer/i18n/sample/src/Profiles.i18n.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{
"profiles.operation.cancelled": "Operation Cancelled",
"checkCurrentProfile.tokenAuthError.msg": "Token auth error",
"checkCurrentProfile.tokenAuthError.additionalDetails": "Profile was found using token auth, please log in to continue.",
"profiles.createNewConnection": "$(plus) Create a new connection to z/OS",
"profiles.createTeamConfig": "$(plus) Create a new Team Configuration file",
"profiles.editConfig": "$(pencil) Edit Team Configuration file",
Expand Down
Loading

0 comments on commit 755df8f

Please sign in to comment.