8000 Rework the result for the session search to contain a single result per user sessions by ahus1 · Pull Request #29204 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actions/conditional/conditions
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ docs/guides/ guides
docs/documentation/ documentation

js/ js
rest/admin-ui-ext/ js

*.java codeql-java
themes/ codeql-themes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import org.eclipse.microprofile.openapi.annotations.media.Content;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.keycloak.admin.ui.rest.model.SessionId;
import org.keycloak.admin.ui.rest.model.SessionId.SessionType;
import org.keycloak.admin.ui.rest.model.ClientIdSessionType;
import org.keycloak.admin.ui.rest.model.ClientIdSessionType.SessionType;
import org.keycloak.admin.ui.rest.model.SessionRepresentation;
import org.keycloak.common.util.Time;
import org.keycloak.models.AuthenticatedClientSessionModel;
Expand All @@ -22,14 +22,12 @@
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import org.keycloak.utils.StringUtil;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.keycloak.admin.ui.rest.model.SessionId.SessionType.*;
import static org.keycloak.admin.ui.rest.model.ClientIdSessionType.SessionType.*;

public class SessionsResource {
private final KeycloakSession session;
Expand Down Expand Up @@ -64,49 +62,40 @@ public Stream<SessionRepresentation> realmSessions(@QueryParam("type") @DefaultV
@DefaultValue("0") int first, @QueryParam("max") @DefaultValue("10") int max) {
auth.realm().requireViewRealm();

Stream<SessionId> sessionIdStream = Stream.<SessionId>builder().build();
Stream<ClientIdSessionType> sessionIdStream = Stream.<ClientIdSessionType>builder().build();
if (type == ALL || type == REGULAR) {
final Map<String, Long> clientSessionStats = session.sessions().getActiveClientSessionStats(realm, false);
sessionIdStream = Stream.concat(sessionIdStream, clientSessionStats
.keySet().stream().map(i -> new SessionId(i, REGULAR)));
.keySet().stream().map(i -> new ClientIdSessionType(i, REGULAR)));
}
if (type == ALL || type == OFFLINE) {
sessionIdStream = Stream.concat(sessionIdStream, session.sessions().getActiveClientSessionStats(realm, true)
.keySet().stream().map(i -> new SessionId(i, OFFLINE)));
.keySet().stream().map(i -> new ClientIdSessionType(i, OFFLINE)));
}

Stream<SessionRepresentation> result = sessionIdStream.flatMap((sessionId) -> {
ClientModel clientModel = realm.getClientById(sessionId.getClientId());
switch (sessionId.getType()) {
Stream<SessionRepresentation> result = sessionIdStream.flatMap((clientIdSessionType) -> {
ClientModel clientModel = realm.getClientById(clientIdSessionType.getClientId());
switch (clientIdSessionType.getType()) {
case REGULAR:
return session.sessions().getUserSessionsStream(realm, clientModel)
.map(s -> toUserSessionRepresentation(s, sessionId.getClientId(), REGULAR));
.map(s -> toRepresentation(s, REGULAR));
case OFFLINE:
return session.sessions()
.getOfflineUserSessionsStream(realm, clientModel, null, null)
.map(s -> toUserSessionRepresentation(s, sessionId.getClientId(), OFFLINE));
.map(s -> toRepresentation(s, OFFLINE));
}
return Stream.<SessionRepresentation>builder().build();
}).distinct();
});

if (!search.equals("")) {
result = result.filter(s -> s.getUsername().contains(search) || s.getIpAddress().contains(search)
|| s.getClients().values().stream().anyMatch(c -> c.contains(search)));
if (!StringUtil.isBlank(search)) {
String searchTrimmed = search.trim();
result = result.filter(s -> s.getUsername().contains(searchTrimmed) || s.getIpAddress().contains(searchTrimmed)
|| s.getClients().values().stream().anyMatch(c -> c.contains(searchTrimmed)));
}
return result.skip(first).limit(max);
return result.distinct().skip(first).limit(max);
}

private SessionRepresentation toUserSessionRepresentation(final UserSessionModel userSession, String clientId, SessionType type) {
SessionRepresentation rep = toRepresentation(userSession, type);

// Update lastSessionRefresh with the timestamp from clientSession
userSession.getAuthenticatedClientSessions().entrySet().stream()
.filter(entry -> Objects.equals(clientId, entry.getKey()))
.findFirst().ifPresent(result -> rep.setLastAccess(Time.toMillis(result.getValue().getTimestamp())));
return rep;
}

public static SessionRepresentation toRepresentation(UserSessionModel session, SessionType type) {
private static SessionRepresentation toRepresentation(UserSessionModel session, SessionType type) {
SessionRepresentation rep = new SessionRepresentation();
rep.setId(session.getId());
rep.setStart(Time.toMillis(session.getStarted()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@

import java.util.Objects;

public class SessionId {
/**
* A tuple containing the clientId and the session type (online/offline).
*
*/
public class ClientIdSessionType {


public enum SessionType {
Expand All @@ -13,7 +17,7 @@ public enum SessionType {
private final String clientId;
private final SessionType type;

public SessionId(String clientId, SessionType type) {
public ClientIdSessionType(String clientId, SessionType type) {
this.clientId = clientId;
this.type = type;
}
Expand All @@ -30,8 +34,8 @@ public SessionType getType() {
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SessionId sessionId = (SessionId) o;
return Objects.equals(clientId, sessionId.clientId) && type == sessionId.type;
ClientIdSessionType clientIdSessionType = (ClientIdSessionType) o;
return Objects.equals(clientId, clientIdSessionType.clientId) && type == clientIdSessionType.type;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.keycloak.admin.ui.rest.model;

import org.keycloak.admin.ui.rest.model.SessionId.SessionType;
import org.keycloak.admin.ui.rest.model.ClientIdSessionType.SessionType;

import java.util.HashMap;
import java.util.Map;
Expand Down Expand Up @@ -90,17 +90,20 @@ public void setTransientUser(boolean transientUser) {
this.transientUser = transientUser;
}

/**
* Equality is determined by user session ID and the offline flag, which are also the primary key on this entity.
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof SessionRepresentation)) return false;
SessionRepresentation that = (SessionRepresentation) o;
return start == that.start && userId.equals(that.userId) && type == that.type;
return Objects.equals(id, that.id) && type == that.type;
}

@Override
public int hashCode() {
return Objects.hash(userId, start, type);
return Objects.hash(id, type);
}
}

0