Rework the result for the session search to contain a single result per user sessions
Closes #29203 Signed-off-by: Alexander Schwartz <aschwart@redhat.com>
This commit is contained in:
parent
82a959fa78
commit
df47dee924
4 changed files with 34 additions and 37 deletions
1
.github/actions/conditional/conditions
vendored
1
.github/actions/conditional/conditions
vendored
|
@ -30,6 +30,7 @@ docs/guides/ guides
|
|||
docs/documentation/ documentation
|
||||
|
||||
js/ js
|
||||
rest/admin-ui-ext/ js
|
||||
|
||||
*.java codeql-java
|
||||
themes/ codeql-themes
|
||||
|
|
|
@ -5,8 +5,8 @@ import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
|
|||
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;
|
||||
|
@ -22,14 +22,12 @@ import jakarta.ws.rs.DefaultValue;
|
|||
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;
|
||||
|
@ -64,49 +62,40 @@ public class SessionsResource {
|
|||
@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()));
|
||||
|
|
|
@ -3,7 +3,11 @@ package org.keycloak.admin.ui.rest.model;
|
|||
|
||||
import java.util.Objects;
|
||||
|
||||
public class SessionId {
|
||||
/**
|
||||
* A tuple containing the clientId and the session type (online/offline).
|
||||
*
|
||||
*/
|
||||
public class ClientIdSessionType {
|
||||
|
||||
|
||||
public enum SessionType {
|
||||
|
@ -13,7 +17,7 @@ public class SessionId {
|
|||
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;
|
||||
}
|
||||
|
@ -30,8 +34,8 @@ public class SessionId {
|
|||
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
|
|
@ -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;
|
||||
|
@ -90,17 +90,20 @@ public class SessionRepresentation {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue