Do not show sign-out action for offline sessions (#25577)

Closes: #24763

Signed-off-by: Hynek Mlnarik <hmlnarik@redhat.com>
This commit is contained in:
Hynek Mlnařík
2023-12-15 12:55:11 +01:00
committed by GitHub
parent 2853136bbb
commit c6ce859493
4 changed files with 123 additions and 13 deletions

View File

@@ -5,6 +5,8 @@ import CommonPage from "../support/pages/CommonPage";
import ListingPage from "../support/pages/admin-ui/ListingPage";
import { keycloakBefore } from "../support/util/keycloak_hooks";
import PageObject from "../support/pages/admin-ui/components/PageObject";
import adminClient from "../support/util/AdminClient";
import { v4 as uuid } from "uuid";
const loginPage = new LoginPage();
const sidebarPage = new SidebarPage();
@@ -29,7 +31,8 @@ describe("Sessions test", () => {
commonPage
.tableUtils()
.checkRowItemExists(admin)
.checkRowItemExists(client);
.checkRowItemExists(client)
.assertRowItemActionExist(admin, "Sign out");
});
it("go to item accessed clients link", () => {
@@ -38,6 +41,69 @@ describe("Sessions test", () => {
});
});
describe("Offline sessions", () => {
const clientId = "offline-client-" + uuid();
const username = "user-" + uuid();
beforeEach(async () => {
await Promise.all([
adminClient.createClient({
protocol: "openid-connect",
clientId,
publicClient: false,
directAccessGrantsEnabled: true,
clientAuthenticatorType: "client-secret",
secret: "secret",
standardFlowEnabled: true,
}),
adminClient.createUser({
// Create user in master realm
username: username,
enabled: true,
credentials: [{ type: "password", value: "password" }],
}),
]);
await adminClient.auth({
username,
password: "password",
grantType: "password",
clientId,
clientSecret: "secret",
scopes: ["openid", "offline_access"],
});
});
after(() =>
Promise.all([
adminClient.deleteClient(clientId),
adminClient.deleteUser(username),
]),
);
it("check offline token", () => {
sidebarPage.waitForPageLoad();
listingPage.searchItem(clientId, false);
sidebarPage.waitForPageLoad();
// Log out the associated online session of the user
commonPage
.tableUtils()
.checkRowItemExists(username)
.selectRowItemAction(username, "Sign out");
listingPage.searchItem(clientId, false);
sidebarPage.waitForPageLoad();
// Now check that offline session exists (online one has been logged off above)
// and that it is not possible to sign it out
commonPage
.tableUtils()
.checkRowItemExists(username)
.assertRowItemActionDoesNotExist(username, "Sign out");
});
});
describe("Search", () => {
it("search existing session", () => {
listingPage.searchItem(admin, false);

View File

@@ -44,18 +44,47 @@ export default class TablePage extends CommonElements {
}
selectRowItemAction(itemName: string, actionItemName: string) {
this.#getRowItemAction(itemName, actionItemName).click();
return this;
}
assertRowItemActionExist(itemName: string, actionItemName: string) {
this.#getRowItemAction(itemName, actionItemName).should("exist");
return this;
}
assertRowItemActionDoesNotExist(itemName: string, actionItemName: string) {
cy.get(
(this.#tableInModal ? ".pf-c-modal-box.pf-m-md " : "") +
this.#tableRowItem,
)
.contains(itemName)
.parentsUntil("tbody")
.find(".pf-c-dropdown__toggle")
.click();
cy.get(this.dropdownMenuItem).contains(actionItemName).click();
.then(($tbody) => {
if ($tbody.find(".pf-c-dropdown__toggle").length > 0) {
$tbody.find(".pf-c-dropdown__toggle").click();
cy.get(this.dropdownMenuItem)
.contains(actionItemName)
.should("not.exist");
}
});
return this;
}
#getRowItemAction(itemName: string, actionItemName: string) {
return cy
.get(
(this.#tableInModal ? ".pf-c-modal-box.pf-m-md " : "") +
this.#tableRowItem,
)
.contains(itemName)
.parentsUntil("tbody")
.find(".pf-c-dropdown__toggle")
.click()
.get(this.dropdownMenuItem)
.contains(actionItemName);
}
typeValueToRowItem(row: number, column: number, value: string) {
cy.get(
(this.#tableInModal ? ".pf-c-modal-box.pf-m-md " : "") +

View File

@@ -6,6 +6,7 @@ import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/ro
import type { RoleMappingPayload } from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
import type { UserProfileConfig } from "@keycloak/keycloak-admin-client/lib/defs/userProfileMetadata";
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
import { Credentials } from "libs/keycloak-admin-client/lib/utils/auth";
import { merge } from "lodash-es";
class AdminClient {
@@ -23,6 +24,10 @@ class AdminClient {
});
}
async auth(credentials: Credentials) {
return this.#client.auth(credentials);
}
async loginUser(username: string, password: string, clientId: string) {
return this.#client.auth({
username: username,

View File

@@ -9,8 +9,7 @@ import {
import { CubesIcon } from "@patternfly/react-icons";
import { ReactNode, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { useMatch, useNavigate } from "react-router-dom";
import { Link, useMatch, useNavigate } from "react-router-dom";
import { adminClient } from "../admin-client";
import { toClient } from "../clients/routes/Client";
@@ -30,6 +29,7 @@ import { toUser, UserRoute } from "../user/routes/User";
import { toUsers } from "../user/routes/Users";
import { isLightweightUser } from "../user/utils";
import useFormatDate from "../utils/useFormatDate";
import { IRowData } from "@patternfly/react-table";
export type ColumnName =
| "username"
@@ -146,7 +146,12 @@ export default function SessionsTable({
},
});
async function onClickSignOut(session: UserSessionRepresentation) {
async function onClickSignOut(
event: React.MouseEvent,
rowIndex: number,
rowData: IRowData,
) {
const session = rowData.data as UserSessionRepresentation;
await adminClient.realms.deleteSession({ realm, session: session.id! });
if (session.userId === whoAmI.getUserId()) {
@@ -179,12 +184,17 @@ export default function SessionsTable({
)
}
columns={columns}
actions={[
{
title: t("signOut"),
onRowClick: onClickSignOut,
} as Action<UserSessionRepresentation>,
]}
actionResolver={(rowData: IRowData) => {
if (rowData.data.type === "OFFLINE") {
return [];
}
return [
{
title: t("signOut"),
onClick: onClickSignOut,
} as Action<UserSessionRepresentation>,
];
}}
emptyState={
<ListEmptyState
hasIcon