mirror of
https://github.com/keycloak/keycloak.git
synced 2025-12-16 20:15:46 -06:00
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:
@@ -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);
|
||||
|
||||
@@ -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 " : "") +
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user