Show indicator for transient user in user sessions list in admin ui (28879)

For transient users a transient label is now shown in the realm sessions and client sessions list in the admin ui.

Fixes #28879

Co-authored-by: Thomas Darimont <thomas.darimont@googlemail.com>
Co-authored-by: Hynek Mlnařík <hmlnarik@users.noreply.github.com>
Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com>
This commit is contained in:
Thomas Darimont 2024-04-18 11:16:28 +02:00 committed by Hynek Mlnařík
parent f9e68cdc54
commit 68617180a2
6 changed files with 40 additions and 1 deletions

View file

@ -33,6 +33,7 @@ public class UserSessionRepresentation {
private long lastAccess; private long lastAccess;
private boolean rememberMe; private boolean rememberMe;
private Map<String, String> clients = new HashMap<>(); private Map<String, String> clients = new HashMap<>();
private boolean transientUser;
public String getId() { public String getId() {
return id; return id;
@ -97,4 +98,12 @@ public class UserSessionRepresentation {
public void setClients(Map<String, String> clients) { public void setClients(Map<String, String> clients) {
this.clients = clients; this.clients = clients;
} }
public boolean isTransientUser() {
return transientUser;
}
public void setTransientUser(boolean transientUser) {
this.transientUser = transientUser;
}
} }

View file

@ -1,12 +1,14 @@
import type UserSessionRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userSessionRepresentation"; import type UserSessionRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userSessionRepresentation";
import { import {
Button, Button,
Label,
List, List,
ListItem, ListItem,
ListVariant, ListVariant,
ToolbarItem, ToolbarItem,
Tooltip,
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import { CubesIcon } from "@patternfly/react-icons"; import { CubesIcon, InfoCircleIcon } from "@patternfly/react-icons";
import { MouseEvent, ReactNode, useMemo, useState } from "react"; import { MouseEvent, ReactNode, useMemo, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link, useMatch, useNavigate } from "react-router-dom"; import { Link, useMatch, useNavigate } from "react-router-dom";
@ -50,9 +52,24 @@ export type SessionsTableProps = {
const UsernameCell = (row: UserSessionRepresentation) => { const UsernameCell = (row: UserSessionRepresentation) => {
const { realm } = useRealm(); const { realm } = useRealm();
const { t } = useTranslation();
return ( return (
<Link to={toUser({ realm, id: row.userId!, tab: "sessions" })}> <Link to={toUser({ realm, id: row.userId!, tab: "sessions" })}>
{row.username} {row.username}
{row.transientUser && (
<>
{" "}
<Tooltip content={t("transientUserTooltip")}>
<Label
data-testid="user-details-label-transient-user"
icon={<InfoCircleIcon />}
isCompact
>
{t("transientUser")}
</Label>
</Tooltip>
</>
)}
</Link> </Link>
); );
}; };

View file

@ -6,4 +6,5 @@ export default interface UserSessionRepresentation {
start?: number; start?: number;
userId?: string; userId?: string;
username?: string; username?: string;
transientUser?: boolean;
} }

View file

@ -14,6 +14,7 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.models.light.LightweightUserAdapter;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
import jakarta.ws.rs.Consumes; import jakarta.ws.rs.Consumes;
@ -118,6 +119,7 @@ public class SessionsResource {
ClientModel client = clientSession.getClient(); ClientModel client = clientSession.getClient();
rep.getClients().put(client.getId(), client.getClientId()); rep.getClients().put(client.getId(), client.getClientId());
} }
rep.setTransientUser(LightweightUserAdapter.isLightweightUser(session.getUser().getId()));
return rep; return rep;
} }
} }

View file

@ -13,6 +13,7 @@ public class SessionRepresentation {
private String ipAddress; private String ipAddress;
private long start; private long start;
private long lastAccess; private long lastAccess;
private boolean transientUser;
private SessionType type; private SessionType type;
private Map<String, String> clients = new HashMap<>(); private Map<String, String> clients = new HashMap<>();
@ -81,6 +82,14 @@ public class SessionRepresentation {
this.clients = clients; this.clients = clients;
} }
public boolean isTransientUser() {
return transientUser;
}
public void setTransientUser(boolean transientUser) {
this.transientUser = transientUser;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;

View file

@ -679,6 +679,7 @@ public class ModelToRepresentation {
ClientModel client = clientSession.getClient(); ClientModel client = clientSession.getClient();
rep.getClients().put(client.getId(), client.getClientId()); rep.getClients().put(client.getId(), client.getClientId());
} }
rep.setTransientUser(LightweightUserAdapter.isLightweightUser(session.getUser().getId()));
return rep; return rep;
} }